INitial commit of CAC code

This commit is contained in:
Alex Selimov 2024-02-17 21:51:18 -05:00
commit c8be709be9
66 changed files with 16539 additions and 0 deletions

22
docs/Commands/boundary.md Normal file
View file

@ -0,0 +1,22 @@
# boundary
```sh
boundary xyz
```
## Inputs
`xyz` - string describing the boundary conditions in which `x`, `y`, and `z` can have values of either `p` for periodic or `s` for shrink-wrapped.
## Examples
```sh
boundary ppp
boundary sps
```
## Description
Boundary sets the boundary conditions of the model being either periodic or shrink-wrapped.
Because boundary data is included within the restart file format, this command is not strictly required.
If included this command will override the boundary conditions defined within the data file.

23
docs/Commands/dump.md Normal file
View file

@ -0,0 +1,23 @@
# dump
```sh
dump id group style n fname
```
## Inputs
`id` - id for dump command
`group` - group to be dumped
`style` - dump style, currently only accepts `out`
`n` - dump every `n` steps. If `n` is `0` then the dump file is only written at the beginning and end of a minimize or run.
`fname` - file name to write to. Will strip extensions and replace with the timestep and .out.
## Example
```
```

52
docs/Commands/group.md Normal file
View file

@ -0,0 +1,52 @@
# group
```sh
group id selection shape ...
```
## Inputs
`id` - assigned id for the group.
`selection` - either `atoms`, `elements`, or `all`.
`shape` - either `block`, `sphere`, or `type`.
**Additional arguments depend on the group shape passed and are listed below:**
```sh
group id selection block xlo xhi ylo yhi zlo zhi
```
where `{x,y,z}lo` are the lower bounds along `x,y,z` and `{x,y,z}hi` are the upper bounds along `x,y,z`.
```sh
group id selection sphere x y z r
```
where `x y z` define the centroid of the sphere and `r` defines the radius of the sphere.
```sh
group id selection type n
```
where `n` is a number which selects the `n`th atom type.
## Examples
```sh
group tophalf all block -inf inf -inf inf inf*0.5 inf
group precip atoms sphere 100 78 78 10
group Ni atoms type 2
```
## Description
The group option is used in conjunction with other commands which require a group input.
**A default `all` group is defined which contains all atoms and elements.**
A subsection of all atoms or elements can be selected using the group command.
`id` is used to refer to the group in other commands.
`selection` determines which element types are selected within the group.
A group can be defined containing only `atoms`, `elements`, or `both`.
If element types are within the bounds of the group but are not specified with `selection` they are not defined as members of the group.
All positions for the group command can be specified using position specifications described [here](../Misc/position.md).

View file

@ -0,0 +1,25 @@
# min_style
```sh
min_style minimizer
```
## Inputs
`minimizer` - Either `fire` or `cg`.
## Examples
```sh
min_style fire
min_style cg
```
## Description
This code sets the minimizer for future `minimize` commands.
Currently CAC has implemented both conjugate gradient minimization, `cg`, and the fast inertial relaxation engine [1], `fire`.
## References
[1] Guénolé, Julien et al. "Assessment and optimization of the fast inertial relaxation engine (fire) for energy minimization in atomistic simulations and its implementation in lammps" Computational Materials Science (2020)

31
docs/Commands/minimize.md Normal file
View file

@ -0,0 +1,31 @@
# minimize
```sh
minimize ftol etol max_iter
```
## Inputs
`ftol` - force tolerance
`etol` - energy tolerance
`max_iter` - maximum number of iterations to calculate before exiting.
## Examples
```sh
minimize 10d-10 10d-10 10000
```
## Description
The minimize command is used to perform energy minimization on the CAC model.
The minimization method is chosen by the [min_style](./min_style.md) command.
Minimization is considered to be complete under the following conditions:
1. \( \frac{(E_i - E_{i-1})}{E_i} < etol \), where \( i \) is the current iteration.
2. No atom has a force component greater than `ftol`
3. The number of iterations is greater than `max_iter`

21
docs/Commands/neighbor.md Normal file
View file

@ -0,0 +1,21 @@
# neighbor
```sh
neighbor skin
```
## Inputs
`skin` - The skin distance in angstrom added to the cutoff radius for construction of verlet lists.
## Examples
```sh
neighbor 1.5
```
## Description
This command specifies the additional skin distance which is used when constructing the verlet neighbor lists.
When an atom has moved greater than the half of the skin distance, then the neighbor lists are reconstructed.

View file

@ -0,0 +1,29 @@
# potential
```sh
potential pot_type file elements ...
```
## Inputs
`pot_type` - Potential type, currently accepts either "eam" for setfl formatted eam/alloy potentials, or "fs" for eam finnis sinclair style potentials.
`file` - Potential file, currently only accepts .eam.alloy style potential files
`elements ...` - List of atomic elements mapping the atom type number to atomic species. Can be excluded resulting in mapping atomic types based on ordering within the potential file.
## Examples
```sh
potential CuNi.eam.alloy Ni Cu
potential Cu_mishin1.eam.alloy
```
## Description
This command specifies the potential information.
**Currently only .eam.alloy style potentials are accepted.**
The `elements ...` portion of the code is used to map the numeric atom type to the potential file.
For the first example, `Ni Cu` maps atoms with type 1 to Ni and atoms with type 2 to Cu.
Inputting `Cu Ni` would instead reverse the order and map atoms with type 1 to Cu and vice-versa.
This can be excluded, as in the second example.
If the CuNi.eam.alloy file is ordered with Ni first and Cu second, then `potential CuNi.eam.alloy Ni Cu` is equivalent to `potential CuNi.eam.alloy`.

View file

@ -0,0 +1,24 @@
# read_data
```
read_data filename
```
## Inputs
`filename` - Input file to be read in restart format.
## Examples
```sh
read_data model_1.restart
```
## Description
Model building capabilities are not included into the CAC simulation tool.
Instead the [CAC model builder (CACmb)](https://gitlab.com/aselimov/cacmb) must be used to create models in the .restart format.
The read_data command reads in one .restart formatted data file and distributes the model across all processors.

10
docs/Commands/run.md Normal file
View file

@ -0,0 +1,10 @@
# run
```sh
run ensemble timesteps
```
## Inputs
`ensemble` - currently either `nve` or `qd`.
`timesteps` - The number of timesteps to run of dynamics

View file

@ -0,0 +1,9 @@
# setforce
```
setforce group_id fx fy fz
```
## Inputs
`group_id` - id for student

26
docs/Commands/temp.md Normal file
View file

@ -0,0 +1,26 @@
# temp
```sh
temp command group_id args
```
## Inputs
`command` - Command for temp option, either `create` or `control`.
`group_id` - Group id to apply temperature control to
**Additional arguments depend on the command passed and are listed below**
```sh
temp create group_id T_target
```
- `T_target` - Target temperature
```sh
temp control group_id T_target time_constant
```
- `T_target` - Target temperature
- `time_constant` - time constant

22
docs/Commands/thermo.md Normal file
View file

@ -0,0 +1,22 @@
# thermo
```sh
thermo step
```
## Inputs
`step` - Output every `step` steps.
## Examples
```
thermo 50
```
## Descriptions
Currently the thermo output is fixed.
For energy minimization, the thermo output contains only the energy and second norm of the global force vector.
For dynamics, the thermo output also contains the kinetic energy and the temperature.

View file

@ -0,0 +1,32 @@
# thermo_style
```sh
thermo_style args
```
## Inputs
`args` - List of thermo_style options. Acceptable options are listed below:
## Examples
```
thermo 50
```
## Descriptions
The `thermo_style` command sets the thermo output for CAC.
By default the thermostyle command only outputs the potential energy and the global force norm.
Acceptable options for thermo_style are listed below:
- `pe`: potential energy
- `fnorm`: global force norm
- `temp`: temperature
- `temp_c`: temperature in CG
- `temp_a`: temperature in atoms
- `ke`: kinetic energy
- `ke_c`: kinetic energy in CG
- `ke_a`: kinetic energy in atoms
- `lx`, `ly`, `lz`: Box dimensions in x, y, z respectively
- `px`, `py`, `pz`: Box pressure in x, y, z respectively

0
docs/Misc/position.md Normal file
View file

BIN
docs/img/CuNi_int.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

BIN
docs/img/CuNi_xyz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/img/cacmb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/img/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

1
docs/img/demo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 135 KiB

BIN
docs/img/efilled_vtk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/img/rhomb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

16
docs/index.md Normal file
View file

@ -0,0 +1,16 @@
# CAC: An implementation of the Concurrent Atomistic-Continuum method
Copyright © 2017-2018 Georgia Institute of Technology. All Rights Reserved.
CAC is an implementation of the concurrent atomistic-continuum (CAC) method [1] that allows for both dynamics and energy minimization of CAC models.
CAC is written in Fortran 2008.
For a standalone tool used to build and analyze models for simulation with CAC please see the [CAC model builder tool](https://gitlab.com/aselimov/cacmb).
This user's manual is maintained by [Alex Selimov](https://alexselimov.xyz) and [Kevin Chu](https://kvnchv.github.io/cv/). Both are current Ph.D. students of Prof. David L. McDowell at the Georgia Institute of Technology.
Click [here](./intro/getstarted.md) for the getting started guide.
## References
[1] Chen, Youping et al. "Concurrent atomistic-continuum modeling of crystalline materials" Journal of Applied Physics (2019)

1
docs/intro/getstarted.md Normal file
View file

@ -0,0 +1 @@
# Getting Started

122
src/Makefile Normal file
View file

@ -0,0 +1,122 @@
.DEFAULT_GOAL := all
FC=mpif90
OBJDIR=obj
SRCS := $(wildcard *.f90)
OBJECTS := $(addprefix $(OBJDIR)/,$(SRCS:%.f90=%.o))
FFLAGS=-Wall -mcmodel=large -O2 -g -fbacktrace -fcheck=all -ffpe-trap=invalid,zero,overflow,underflow,denormal -J$(OBJDIR)
#
#FFLAGS=-O3 -g -J$(OBJDIR)
#----------------- DEPENDENCIES -----------------#
# GENERATED USING https://github.com/ZedThree/fort_depend.py **requires python3**
# > pip install fortdepend
# > fortdepend -o Makefile.dep -i mpi -b obj/
include Makefile.dep
#----------------- DEFAULTS -----------------#
all: CAC
.PHONY: deps
CAC: $(OBJECTS) $(OBJDIR)/main.o
$(FC) $(FFLAGS) $(OBJECTS) -o $@
$(OBJDIR)/%.o: %.f90
@mkdir -p $(@D)
$(FC) $(FFLAGS) -c -o $@ $<
.f90.o:
$(FC) $(FFLAGS) -c $<
#deps:
# @fortdepend -o Makefile.dep -i mpi -b obj -w
deps:
@makedepf90 -b obj *.f90 > Makefile.dep
#----------------- TEST RULES -----------------#
COMMS_TEST_OBJECTS := $(addprefix $(OBJDIR)/,$(COMMS_TEST))
MULTICOMM_TEST:=$(COMMS_TEST)
MULTICOMM_TEST_OBJECTS:=$(addprefix $(OBJDIR)/,$(MULTICOMM_TEST))
ELEMENTS_TEST=parameters.o elements.o box.o errors.o math.o logger.o
ELEMENTS_TEST_OBJECTS := $(addprefix $(OBJDIR)/,$(ELEMENTS_TEST))
DUMP_TEST=$(POT_TEST) dump.o
DUMP_TEST_OBJECTS := $(addprefix $(OBJDIR)/,$(DUMP_TEST))
.PHONY: tests
tests: test_cohesive_energy test_gsf test_pair_force test_multi_pair_force test_fs_pair_force test_cg_calc test_mixed_potentials
test_cohesive_energy: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/cohesive_energy
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/cohesive_energy
$(MAKE) -C ./tests/cohesive_energy
test_gsf: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/gsf
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/gsf
$(MAKE) -C ./tests/gsf
test_pair_force: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/pair_force
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/pair_force
$(MAKE) -C ./tests/pair_force
test_multi_pair_force: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/multi_pair_force
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/multi_pair_force
$(MAKE) -C ./tests/multi_pair_force
test_fs_pair_force: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/fs_pair_force
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/fs_pair_force
$(MAKE) -C ./tests/fs_pair_force
test_morse_force: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/morse_force
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/morse_force
$(MAKE) -C ./tests/morse_force
test_cg_calc: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/cg_calc
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/cg_calc
$(MAKE) -C ./tests/cg_calc
test_mixed_potential: $(OBJECTS)
$(eval TESTOBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS)))
@cp $(TESTOBJECTS) ./tests/mixed_potentials
@cp $(subst .o,.mod,$(TESTOBJECTS)) ./tests/mixed_potentials
$(MAKE) -C ./tests/mixed_potentials
#----------------- CLEAN UP -----------------#
.PHONY: clean
clean:
$(RM) *.mod *.o
$(RM) $(OBJDIR)/*.mod $(OBJDIR)/*.o CAC
@$(RM) -rf obj/
.PHONY: cleantest
cleantest:
$(RM) tests/*/*.o tests/*/*.mod tests/*/*_test
.PHONY: clean-all
clean-all: clean cleantest
# DEBUGGING VARIABLE PRINT
print-% : ; @echo $* = $($*)

39
src/Makefile.dep Normal file
View file

@ -0,0 +1,39 @@
obj/atom_types.o : atom_types.f90 obj/str.o obj/logger.o obj/parameters.o
obj/berendsen.o : berendsen.f90 obj/time.o obj/forces.o obj/compute.o obj/parameters.o obj/elements.o
obj/box.o : box.f90 obj/errors.o obj/parameters.o
obj/cg.o : cg.f90 obj/min_arrays.o obj/integration.o obj/potential.o obj/neighbors.o obj/atom_types.o obj/time.o obj/forces.o obj/elements.o obj/comms.o obj/parameters.o
obj/comms.o : comms.f90 obj/math.o obj/errors.o obj/box.o obj/elements.o obj/parameters.o
obj/compute.o : compute.f90 obj/box.o obj/group.o obj/elements.o obj/forces.o obj/atom_types.o obj/comms.o obj/parameters.o
obj/debug.o : debug.f90 obj/str.o obj/elements.o obj/forces.o obj/parameters.o
obj/deform.o : deform.f90 obj/comms.o obj/time.o obj/neighbors.o obj/elements.o obj/box.o obj/parameters.o
obj/displace.o : displace.f90 obj/neighbors.o obj/time.o obj/str.o obj/group.o obj/elements.o obj/parameters.o obj/forces.o
obj/dump.o : dump.f90 obj/time.o obj/group.o obj/math.o obj/forces.o obj/errors.o obj/box.o obj/elements.o obj/comms.o obj/parameters.o obj/atom_types.o
obj/dynamics.o : dynamics.f90 obj/deform.o obj/berendsen.o obj/temp.o obj/time.o obj/thermo.o obj/dump.o obj/potential.o obj/comms.o obj/forces.o obj/quenched_dynamics.o obj/neighbors.o obj/vel_verlet.o obj/parameters.o
obj/eam.o : eam.f90 obj/atom_types.o obj/errors.o obj/comms.o obj/neighbors.o obj/integration.o obj/elements.o obj/forces.o obj/math.o obj/parameters.o
obj/elements.o : elements.f90 obj/math.o obj/box.o obj/errors.o obj/parameters.o
obj/errors.o : errors.f90 obj/logger.o obj/parameters.o
obj/fire.o : fire.f90 obj/debug.o obj/potential.o obj/neighbors.o obj/atom_types.o obj/time.o obj/forces.o obj/elements.o obj/comms.o obj/parameters.o
obj/force_mod.o : force_mod.f90 obj/time.o obj/str.o obj/group.o obj/elements.o obj/parameters.o obj/forces.o
obj/forces.o : forces.f90 obj/integration.o obj/elements.o obj/parameters.o
obj/group.o : group.f90 obj/comms.o obj/str.o obj/box.o obj/elements.o obj/parameters.o
obj/input_parser.o : input_parser.f90 obj/langevin.o obj/modify.o obj/deform.o obj/set.o obj/displace.o obj/force_mod.o obj/minimize.o obj/group.o obj/dump.o obj/logger.o obj/neighbors.o obj/read_data.o obj/eam.o obj/potential.o obj/parameters.o obj/comms.o
obj/integration.o : integration.f90 obj/errors.o obj/elements.o obj/comms.o obj/parameters.o
obj/langevin.o : langevin.f90 obj/group.o obj/atom_types.o obj/time.o obj/forces.o obj/elements.o obj/comms.o obj/parameters.o
obj/logger.o : logger.f90 obj/parameters.o
obj/main.o : main.f90 obj/langevin.o obj/deform.o obj/debug.o obj/potential.o obj/berendsen.o obj/temp.o obj/thermo.o obj/dump.o obj/group.o obj/time.o obj/minimize.o obj/logger.o obj/neighbors.o obj/integration.o obj/input_parser.o obj/parameters.o obj/comms.o
obj/math.o : math.f90 obj/parameters.o
obj/min_arrays.o : min_arrays.f90 obj/errors.o obj/elements.o obj/parameters.o
obj/minimize.o : minimize.f90 obj/debug.o obj/logger.o obj/errors.o obj/thermo.o obj/dump.o obj/potential.o obj/comms.o obj/forces.o obj/neighbors.o obj/cg.o obj/fire.o obj/parameters.o
obj/modify.o : modify.f90 obj/potential.o obj/elements.o obj/neighbors.o obj/parameters.o
obj/morse.o : morse.f90 obj/atom_types.o obj/errors.o obj/comms.o obj/neighbors.o obj/integration.o obj/elements.o obj/forces.o obj/math.o obj/parameters.o
obj/neighbors.o : neighbors.f90 obj/min_arrays.o obj/temp.o obj/group.o obj/elements.o obj/forces.o obj/logger.o obj/comms.o obj/math.o obj/integration.o obj/parameters.o
obj/parameters.o : parameters.f90
obj/potential.o : potential.f90 obj/force_mod.o obj/atom_types.o obj/str.o obj/comms.o obj/forces.o obj/morse.o obj/eam.o obj/parameters.o
obj/quenched_dynamics.o : quenched_dynamics.f90 obj/vel_verlet.o obj/time.o obj/potential.o obj/neighbors.o obj/elements.o obj/comms.o obj/parameters.o
obj/read_data.o : read_data.f90 obj/time.o obj/str.o obj/logger.o obj/comms.o obj/dynamics.o obj/potential.o obj/integration.o obj/box.o obj/elements.o obj/parameters.o obj/math.o
obj/set.o : set.f90 obj/logger.o obj/group.o obj/elements.o
obj/str.o : str.f90
obj/temp.o : temp.f90 obj/atom_types.o obj/time.o obj/compute.o obj/group.o obj/elements.o obj/comms.o obj/parameters.o
obj/thermo.o : thermo.f90 obj/str.o obj/compute.o obj/forces.o obj/elements.o
obj/time.o : time.f90 obj/errors.o obj/logger.o obj/parameters.o
obj/vel_verlet.o : vel_verlet.f90 obj/time.o obj/potential.o obj/neighbors.o obj/elements.o obj/comms.o obj/parameters.o

87
src/atom_types.f90 Normal file
View file

@ -0,0 +1,87 @@
module atom_types
!This module contains the atom type variables
use parameters
use logger
use str
implicit none
!Max number of atom types allowed
integer, parameter :: max_atom_types=10
!number of different potential types, eam all count for the same
integer, parameter :: max_pot_types=2
!Atom type variables
integer, save :: natom_types
real(kind=wp), dimension(max_atom_types), save :: masses
character(len=2), dimension(max_atom_types), save :: atom_names
!Have to move some potential variables here
integer, save, dimension(max_atom_types, max_atom_types) :: types_to_pot_type
logical, dimension(max_pot_types) :: potential_types
logical :: atom_types_set
public
contains
subroutine init_atom_types
!Initialization subroutine
atom_types_set = .false.
types_to_pot_type=0
end subroutine init_atom_types
subroutine set_atom_types(num,names)
!This subroutine sets the atom types for use with the potential functions
integer, intent(in) :: num
character(len=2), dimension(:) :: names
integer :: i
natom_types = num
do i = 1, natom_types
atom_names(i) = names(i)
end do
atom_types_set = .true.
return
end subroutine set_atom_types
subroutine parse_types(line)
character(len=*), intent(in) :: line
character(len=read_len) :: tmptxt
integer :: i
i = tok_count(line)
natom_types = i-1
read(line,*) tmptxt, (atom_names(i), i = 1, natom_types)
atom_types_set = .true.
return
end subroutine parse_types
subroutine parse_mass(line)
character(len=*), intent(in) :: line
character(len=read_len) :: tmptxt
integer :: i
real(kind=wp) :: m
read(line,*) tmptxt, i, m
masses(i) = m
write(tmptxt, *) "Masses are now: ", masses(1:natom_types)
call log_msg(tmptxt)
return
end subroutine parse_mass
subroutine log_types
!This command logs the atom_types to the log file
integer :: i
character(len = read_len) :: msg
msg = ''
do i = 1, natom_types
write(msg, *) trim(adjustl(msg)), i, atom_names(i)
end do
call log_msg("Atom types are mapped as "//msg)
end subroutine log_types
end module atom_types

122
src/berendsen.f90 Normal file
View file

@ -0,0 +1,122 @@
module berendsen
use elements
use parameters
use compute
use forces
use time
implicit none
real(kind=wp), save, private:: Ptarget(3), time_constant
logical, save :: pflag, need_barostat(3)
public
contains
subroutine parse_berendsen(line)
!This subroutine parses the press command
character(len=*), intent(in) :: line
character(len = read_len) :: tmptxt, msg, press_targets(3)
integer :: iospara, i
read(line, *, iomsg = msg, iostat = iospara) tmptxt, time_constant, (press_targets(i), i = 1, 3)
if(iospara > 0) call read_error(msg, iospara)
need_barostat=.false.
do i = 1, 3
if ((trim(adjustl(press_targets(i))) == 'null').or.(trim(adjustl(press_targets(i))) == 'NULL')) then
Ptarget(i)=0
else
read(press_targets(i), *) Ptarget(i)
need_barostat(i)=.true.
end if
end do
pflag = .true.
write(msg, *) "Using Berendsen barostat to target pressure ", Ptarget, " for dimensions", need_barostat
call log_msg(msg)
return
end subroutine parse_berendsen
subroutine rescale_box
!This function actually rescales the box for barostatting.
integer :: i, j
real(kind = wp) :: pressure(3,3), identity(3,3), mu(3,3), cntr(3), cdamp
!First compute the box pressure
pressure = compute_box_press()
!Initialize some needed variables
identity(:,:) = 0.0_wp
forall(i=1:3) identity(i,i) = 1.0_wp
mu(:,:) = 0.0_wp
!Now calculate the scaling factor
cdamp = time_step/time_constant
forall(i=1:3)mu(i,i) = (identity(i,i) - cdamp*(Ptarget(i) - pressure(i,i)))**(1.0_wp/3.0_wp)
!We set the scaling factor to 1 in non-periodic dimensions
do i = 1, 3
if ((.not.period(i)).or.(.not.need_barostat(i))) mu(i,i) = 1.0_wp
end do
!Now get the box center
cntr = 0.0_wp
do i = 1,3
cntr(i) = box_length(i)/2 + box_bd(2*i-1)
end do
!Now scale the box boundaries
do i = 1, 6
j = (i+1)/2
box_bd(i) = cntr(j) + mu(j,j)*(box_bd(i)-cntr(j))
end do
!update box length
do i = 1, 3
box_length(i) = box_bd(2*i) - box_bd(2*i-1)
end do
!Now scale the atomic and nodal positions
if(atom_num > 0) then
do i = 1, atom_num_l
do j = 1, 3
r_atom(j,i) = cntr(j) + mu(j,j)*(r_atom(j,i)-cntr(j))
end do
end do
end if
if(ele_num > 0) then
do i = 1, node_num_l
do j=1, 3
r(j,:,i) = cntr(j) + mu(j,j)*(r(j,:,i)-cntr(j))
end do
end do
end if
!Now scale the box matrix
box_mat = matmul(box_mat,mu)
!Now update the processor boundaries
pro_length(:) = 0.0_wp
pro_bd(:) = 0.0_wp
do i = 1, 3
!Define processor boundaries
pro_length(i) = box_length(i) / num_pro(i)
pro_bd(2*i-1) = box_bd(2*i-1) + grid_coords(i) * pro_length(i)
pro_bd(2*i) = box_bd(2*i-1) + (grid_coords(i) + 1) * pro_length(i)
if(grid_coords(i) == 0) then
!If the processor has coordinate 0 it's the bottom one so set the boundary equal to the box boundary
pro_bd(2*i-1) = box_bd(2*i-1)
end if
if(grid_coords(i) == num_pro(i) - 1) then
!If the processor has coordinate num_pro(i) - 1 it's the top one so set the boundary equal to top boundary
pro_bd(2*i) = box_bd(2*i)
end if
pro_length(i) = pro_bd(2*i) - pro_bd(2*i-1)
end do
call processor_bds
return
end subroutine rescale_box
end module berendsen

197
src/box.f90 Normal file
View file

@ -0,0 +1,197 @@
module box
!This module contains all information related to simulation box definitions
use parameters
use errors
implicit none
!Box definition variables
real(kind=wp) :: box_bd(6), orig_box_bd(6), box_length(3), orig_box_length(3)
real(kind=wp) :: box_mat_ori(3,3), box_mat(3,3)
!Boundary definitions
character(len=3) :: boundary
logical :: periodic
logical, dimension(3) :: period
real(kind=wp), dimension(3) :: center_mass, center_mass_ori, center_mass_disp
public
contains
subroutine parse_boundary(line)
!This subroutine parses the optional boundary command
character(len=*), intent(in) :: line
integer :: iospara, i
character(len=read_len) :: msg, tmptxt
read(line, *, iostat = iospara, iomsg = msg) tmptxt, boundary
if(iospara > 0) call read_error(msg, iospara)
do i = 1, 3
select case(boundary(i:i))
case('s', 'S')
period(i) = .false.
case('p','P')
period(i) = .true.
case default
call command_error("Boundary must be either p or s")
end select
end do
if(any(period)) then
periodic = .true.
if(.not.allocated(pb_node))then
allocate(pb_node(3,max_basisnum, node_num_lr))
end if
else
periodic = .false.
end if
end subroutine parse_boundary
subroutine cross_pb(r, info)
!This subroutine sets which periodic boundaries the nodes have crossed
real(kind = wp), dimension(3), intent(inout) :: r
integer, dimension(3), intent(out) :: info
integer :: i
info(:) = 0
do i = 1, 3
if(period(i).eqv..true.) then
if(r(i) < box_bd(i*2-1)) then
r(i) = r(i) + box_length(i)
info(i) = 1
else if(r(i) > box_bd(i*2)) then
r(i) = r(i) - box_length(i)
info(i) = -1
end if
end if
end do
return
end subroutine cross_pb
subroutine restore_pb(r_in, pb_move, ipb)
real(kind = wp), dimension(3), intent(inout) :: r_in
integer, dimension(3), intent(in) :: pb_move
logical, optional, intent(out) :: ipb
integer :: i
if(present(ipb)) ipb = .false.
do i = 1, 3
if((period(i).eqv..true.).and. (pb_move(i) /= 0)) then
r_in(i) = r_in(i) - pb_move(i) * box_length(i)
if(present(ipb)) ipb = .true.
end if
end do
return
end subroutine restore_pb
function in_box_bd(r)
!this function just checks to make sure position r is within the overall simulation box
real(kind=wp), intent(in) :: r(3)
logical :: in_box_bd
integer :: i
in_box_bd = .true.
do i = 1,3
if (r(i) < box_bd(2*i-1)) then
!Lower bound
in_box_bd = .false.
return
else if (r(i) > box_bd(2*i)) then
!Upper bound
in_box_bd = .false.
return
end if
end do
return
end function in_box_bd
recursive subroutine parse_pos(i, pos_string, pos)
!This subroutine parses the pos command allowing for command which include inf
integer, intent(in) :: i !The dimension of the position
character(len=*), intent(in) :: pos_string !The position string
real(kind=dp), intent(out) :: pos !The output parsed position value
integer :: iospara
real(kind=dp) :: rand, rone, rtwo
character(len=100) :: cone, ctwo
iospara = 0
if(trim(adjustl(pos_string)) == 'inf') then
pos=box_bd(2*i)
else if(trim(adjustl(pos_string)) == '-inf') then
pos=box_bd(2*i-1)
else if (trim(adjustl(pos_string)) == 'rand') then
call random_number(rand)
pos = (box_bd(2*i)-box_bd(2*i-1))*rand + box_bd(2*i-1)
else if (index(pos_string,'rand')>0) then
call random_number(rand)
cone = pos_string(index(pos_string, '[')+1:index(pos_string,':')-1)
call parse_pos(i, cone, rone)
ctwo = pos_string(index(pos_string, ':')+1:index(pos_string,']')-1)
call parse_pos(i, ctwo, rtwo)
pos = (rtwo - rone)*rand + rone
else if ((index(pos_string,'-') > 0).and.(index(pos_string,'inf')>0)) then
!Now extract the number we are reducing from infinity
if(index(pos_string,'inf') < index(pos_string,'-')) then
read(pos_string(index(pos_string,'-')+1:), *, iostat=iospara) pos
else
read(pos_string(1:index(pos_string,'-')-1), *, iostat=iospara) pos
end if
pos = box_bd(2*i) - pos
else if ((index(pos_string,'+') > 0).and.(index(pos_string,'inf')>0)) then
!Now extract the number we are reducing from infinity
if(index(pos_string,'inf') < index(pos_string,'+')) then
read(pos_string(index(pos_string,'+')+1:), *, iostat=iospara) pos
else
read(pos_string(1:index(pos_string,'+')-1), *, iostat=iospara) pos
end if
pos = box_bd(2*i-1) + pos
else if ((index(pos_string,'*') > 0).and.(index(pos_string,'inf')>0)) then
!Now extract the number we are reducing from infinity
if(index(pos_string,'inf') < index(pos_string,'*')) then
read(pos_string(index(pos_string,'*')+1:), *, iostat=iospara) pos
else
read(pos_string(1:index(pos_string,'*')-1), *, iostat=iospara) pos
end if
pos = (box_bd(2*i)-box_bd(2*i-1))*pos + box_bd(2*i-1)
else
read(pos_string, *, iostat=iospara) pos
end if
if (iospara > 0) then
print *, "Error reading position argument ", trim(adjustl(pos_string)), ". Please reformat and try again."
end if
end subroutine parse_pos
subroutine update_box_mat
!This subroutine updates the box matrix
integer :: i
box_mat = 0.0_wp
do i = 1, 3
box_mat(i,i) = box_length(i)
end do
return
end subroutine update_box_mat
end module box

560
src/cg.f90 Normal file
View file

@ -0,0 +1,560 @@
module cg
use parameters
use comms
use elements
use forces
use time
use atom_types
use neighbors
use potential
use integration
use min_arrays
implicit none
integer :: neval, nlimit
real(kind=wp), private, save :: alpha_max, alpha_reduce, backtrack_slope, quadratic_tol, emach, eps_quad, dmax, gg
public
contains
subroutine cg_defaults
alpha_max = 1.0_wp
alpha_reduce = 0.25_wp
backtrack_slope = 0.4_wp
quadratic_tol = 0.1_wp
emach = 1.0d-8
eps_quad = 1.0d-28
dmax = 0.1
nlimit = min(huge(1),atom_num+node_num)
end subroutine cg_defaults
subroutine cg_init
integer :: ia, ibasis, ip, ie, inod
real(kind = wp) :: ggme
!Initialize cg variables
call alloc_min_arrays
if(atom_num > 0) then
hatom = force_atom(:,1:atom_num_l)
gatom = hatom
end if
if(ele_num > 0) then
hnode = force_eq(:,:,1:node_num_l)
gnode = hnode
end if
!Initialize number of evaluations to 0
neval = 0
!Initialize force norm_squared
ggme = 0
do ia = 1, atom_num_l
ggme = ggme + force_atom(1, ia)*force_atom(1,ia) + force_atom(2,ia)*force_atom(2,ia) &
+ force_atom(3,ia)*force_atom(3,ia)
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod,ie)
do ibasis = 1, basis_num(node_cg(ip))
ggme = ggme + force_eq(1, ibasis,ip)*force_eq(1,ibasis,ip) &
+ force_eq(2,ibasis,ip)*force_eq(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*force_eq(3,ibasis,ip)
end do
end do
end if
end do
call mpi_allreduce(ggme, gg, 1, mpi_wp, mpi_sum, world, ierr)
end subroutine cg_init
subroutine cg_clean
if(atom_num > 0) deallocate(hatom, gatom, rzeroatom)
if(ele_num > 0) deallocate(hnode, gnode, rzeronode)
end subroutine cg_clean
subroutine reset_cg_dir
!This simple subroutine just resets the cg search direction
if(atom_num > 0) then
hatom = force_atom(:,1:atom_num_l)
gatom = hatom
end if
if(ele_num > 0) then
hnode = force_eq(:,:,1:node_num_l)
gnode = hnode
end if
end subroutine reset_cg_dir
subroutine resize_min_array
!This subroutine resizes the min arrays if needed
real(kind=wp), allocatable :: gatom_array(:,:), hatom_array(:,:), gnode_array(:,:,:), hnode_array(:,:,:)
if (atom_num > 0) then
allocate(gatom_array(3, atom_num_l), hatom_array(3, atom_num_l))
gatom_array=0.0_wp
call move_alloc(gatom_array, gatom)
hatom_array=0.0_wp
call move_alloc(hatom_array, hatom)
hatom = force_atom(:,1:atom_num_l)
gatom = hatom
end if
if (ele_num > 0) then
allocate(gnode_array(3, max_basisnum, node_num_l), hnode_array(3, max_basisnum, node_num_l))
gnode_array=0.0_wp
call move_alloc(gnode_array, gnode)
hnode_array=0.0_wp
call move_alloc(hnode_array, hnode)
hnode = force_eq(:,:,1:node_num_l)
gnode = hnode
end if
end subroutine resize_min_array
subroutine cg_iterate(code)
integer, intent(out) :: code
integer :: ie, inod, ibasis, ip, ia
real(kind= wp) :: beta, dotme(2), dotall(2), fdotf, eprevious
!Line minimization along direction h from current position
eprevious = compute_pe(1)
call linesearch_backtrack(code)
if(code > 0) return
!Calculate force dot products
dotme(:) = 0
do ia = 1, atom_num_l
dotme(1) = dotme(1) + force_atom(1, ia)*force_atom(1,ia) + force_atom(2,ia)*force_atom(2,ia) &
+ force_atom(3,ia)*force_atom(3,ia)
dotme(2) = dotme(2) + force_atom(1, ia)*gatom(1,ia) + force_atom(2,ia)*gatom(2,ia) &
+ force_atom(3,ia)*gatom(3,ia)
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(node_cg(ip))
dotme(1) = dotme(1) + force_eq(1, ibasis,ip)*force_eq(1,ibasis,ip) &
+ force_eq(2,ibasis,ip)*force_eq(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*force_eq(3,ibasis,ip)
dotme(2) = dotme(2) + force_eq(1, ibasis,ip)*gnode(1,ibasis,ip) &
+ force_eq(2,ibasis,ip)*gnode(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*gnode(3,ibasis,ip)
end do
end do
end if
end do
call mpi_allreduce(dotme, dotall, 2, mpi_wp, mpi_sum, world, ierr)
fdotf = dotall(1)
! update new search direction h from new f = -Grad(x) and old g
! this is Polak-Ribieri formulation
! beta = dotall[0]/gg would be Fletcher-Reeves
! reinitialize CG every ndof iterations by setting beta = 0.0
beta = max(0.0_wp,(dotall(1)-dotall(2))/gg)
if( mod((iter+1),nlimit) == 0) beta = 0.0_wp
gg = dotall(1)
!Update g and h
do ia = 1, atom_num_l
gatom(:,ia) = force_atom(:,ia)
hatom(:,ia) = gatom(:, ia) + beta*hatom(:,ia)
end do
do ip = 1, node_num_l
do ibasis = 1, basis_num(node_cg(ip))
gnode(:,ibasis,ip) = force_eq(:,ibasis,ip)
hnode(:,ibasis,ip) = gnode(:, ibasis,ip) + beta*hnode(:,ibasis,ip)
end do
end do
!Reinitialize CG if new search direction is not downhill
dotme(1) = 0.0_wp
do ia = 1, atom_num_l
dotme(1) = dotme(1) + gatom(1,ia)*hatom(1,ia) + gatom(2,ia)*hatom(2,ia) + gatom(3,ia)*hatom(3,ia)
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
dotme(1) = dotme(1) + gnode(1,ibasis,ip)*hnode(1,ibasis,ip) + gnode(2,ibasis,ip)*hnode(2,ibasis,ip) &
+ gnode(3,ibasis,ip)*hnode(3,ibasis,ip)
end do
end do
end if
end do
call mpi_allreduce(dotme(1), dotall(1), 1, mpi_wp, mpi_sum, world, ierr)
if(dotall(1) <= 0.0) then
if(atom_num > 0) hatom=gatom
if(ele_num > 0) hnode=gnode
end if
end subroutine cg_iterate
subroutine linesearch_backtrack(code)
!linemin: backtracking line search (Proc 3.1, p 41 in Nocedal and Wright)
!uses no gradient info, but should be very robust
!start at maxdist, backtrack until energy decrease is sufficient
integer, intent(out) :: code
integer :: ia, ip, ibasis, inod, ie
real(kind=wp) :: fdothall, fdothme, hme, hmaxall, de_ideal, de, ecurrent, eoriginal, alpha
!First calculate fdothall which is the projection of search dir along downhill gradient
fdothme = 0.0_wp
do ia = 1, atom_num_l
fdothme = fdothme + force_atom(1,ia)*hatom(1,ia) + force_atom(2,ia)*hatom(2,ia) + force_atom(3, ia)*hatom(3,ia)
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
fdothme = fdothme + force_eq(1,ibasis,ip)*hnode(1,ibasis,ip) + force_eq(2,ibasis,ip)*hnode(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*hnode(3,ibasis,ip)
end do
end do
end if
end do
call mpi_allreduce(fdothme, fdothall, 1, mpi_wp, mpi_sum, world, ierr)
!If search direction isn't downhill return with error
if(fdothall <= 0.0_wp) then
code = 4
return
end if
! set alpha so no dof is changed by more than max allowed amount
! for atom coords, max amount = dmax
! for extra per-atom dof, max amount = extra_max[]
! for extra global dof, max amount is set by fix
! also insure alpha <= ALPHA_MAX
! else will have to backtrack from huge value when forces are tiny
! if all search dir components are already 0.0, exit with error
hme = 0.0_wp
do ia = 1, atom_num_l
hme = max(hme, hatom(1,ia), hatom(2,ia), hatom(3,ia))
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(node_cg(ip))
hme = max(hme, hnode(1,ibasis,ip), hnode(2,ibasis,ip), hnode(3,ibasis,ip))
end do
end do
end if
end do
call mpi_allreduce(hme, hmaxall, 1, mpi_wp, mpi_max, world, ierr)
if(is_equal(hmaxall, 0.0_wp)) then
code = 5
return
end if
!Set starting alpha
alpha = min(alpha_max, dmax/hmaxall)
!Save initial coordinates
if(atom_num_l > 0) rzeroatom(:,1:atom_num_l) = r_atom(:,1:atom_num_l)
if(node_num_l > 0) rzeronode(:,:,1:node_num_l) = r(:,:,1:node_num_l)
eoriginal = compute_pe(1)
!Backtrack until decrease in energy is sufficient
do while(.true.)
!Step forward along h
if (alpha > 0.0_wp) then
do ia = 1, atom_num_l
r_atom(:,ia) = rzeroatom(:,ia) + alpha*hatom(:,ia)
end do
do ip = 1, node_num_l
do ibasis = 1, basis_num(node_cg(ip))
r(:,ibasis,ip) = rzeronode(:,ibasis, ip) + alpha*hnode(:,ibasis, ip)
end do
end do
neval = neval + 1
call update_neighbor(iter, .true.)
call update_force
ecurrent=compute_pe(1)
end if
!Quit with success if energy change is better than ideal
de_ideal = -backtrack_slope*alpha*fdothall
de = ecurrent-eoriginal
if(de <= de_ideal) then
code = 0
return
end if
!Reduce alpha
alpha = alpha * alpha_reduce
!Backtracked too much, reset to starting point
! if de is positive exit with error'
! if de is negative than we have reached energy tol
if((alpha <= lim_zero) .or. (de_ideal >= EMACH)) then
if(atom_num_l > 0) r_atom(:, 1:atom_num_l) = rzeroatom(:, 1:atom_num_l)
if(ele_num_l > 0) r(:,:,1:node_num_l) = rzeronode(:,:,1:node_num_l)
call update_neighbor(iter, .true.)
call update_force
if(de < 0.0) then
code = 1
return
else
code = 6
return
end if
end if
end do
end subroutine linesearch_backtrack
subroutine linesearch_quadratic(code)
!----------------------------------------------------------------------
! linemin: quadratic line search (adapted from Dennis and Schnabel)
! The objective function is approximated by a quadratic
! function in alpha, for sufficiently small alpha.
! This idea is the same as that used in the well-known secant
! method. However, since the change in the objective function
! (difference of two finite numbers) is not known as accurately
! as the gradient (which is close to zero), all the expressions
! are written in terms of gradients. In this way, we can converge
! the LAMMPS forces much closer to zero.
!
! We know E,Eprev,fh,fhprev. The Taylor series about alpha_prev
! truncated at the quadratic term is:
!
! E = Eprev - del_alpha*fhprev + (1/2)del_alpha^2*Hprev
!
! and
!
! fh = fhprev - del_alpha*Hprev
!
! where del_alpha = alpha-alpha_prev
!
! We solve these two equations for Hprev and E=Esolve, giving:
!
! Esolve = Eprev - del_alpha*(f+fprev)/2
!
! We define relerr to be:
!
! relerr = |(Esolve-E)/Eprev|
! = |1.0 - (0.5*del_alpha*(f+fprev)+E)/Eprev|
!
! If this is accurate to within a reasonable tolerance, then
! we go ahead and use a secant step to fh = 0:
!
! alpha0 = alpha - (alpha-alphaprev)*fh/delfh;
!
!-------------------------------------------------------------------------
integer, intent(out) :: code
integer :: ia, ip, ibasis, ie, inod
real(kind=wp) :: fdothall, fdothme, hme, hmaxall, de_ideal, de, ecurrent, eoriginal, alpha, alphaprev, fhprev, &
engprev, dotme(2), dotall(2), fh, ff, delfh, relerr, alpha0, alphamax
!First calculate fdothall which is the projection of search dir along downhill gradient
fdothme = 0.0_wp
do ia = 1, atom_num_l
fdothme = fdothme + force_atom(1,ia)*hatom(1,ia) + force_atom(2,ia)*hatom(2,ia) + force_atom(3, ia)*hatom(3,ia)
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
fdothme = fdothme + force_eq(1,ibasis,ip)*hnode(1,ibasis,ip) &
+ force_eq(2,ibasis,ip)*hnode(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*hnode(3,ibasis,ip)
end do
end do
end if
end do
call mpi_allreduce(fdothme, fdothall, 1, mpi_wp, mpi_sum, world, ierr)
!If search direction isn't downhill return with error
if(fdothall <= 0.0_wp) then
code = 4
return
end if
! set alpha so no dof is changed by more than max allowed amount
! for atom coords, max amount = dmax
! for extra per-atom dof, max amount = extra_max[]
! for extra global dof, max amount is set by fix
! also insure alpha <= ALPHA_MAX
! else will have to backtrack from huge value when forces are tiny
! if all search dir components are already 0.0, exit with error
hme = 0.0_wp
do ia = 1, atom_num_l
hme = max(hme, hatom(1,ia), hatom(2,ia), hatom(3,ia))
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
hme = max(hme, hnode(1,ibasis,ip), hnode(2,ibasis,ip), hnode(3,ibasis,ip))
end do
end do
end if
end do
call mpi_allreduce(hme, hmaxall, 1, mpi_wp, mpi_max, world, ierr)
if(is_equal(hmaxall, 0.0_wp)) then
code = 5
return
end if
!Get alphamax
alphamax = min(alpha_max, dmax/hmaxall)
!Save initial coordinates
if(atom_num_l > 0) rzeroatom(:,1:atom_num_l) = r_atom(:,1:atom_num_l)
if(node_num_l > 0) rzeronode(:,:,1:node_num_l) = r(:,:,1:node_num_l)
eoriginal = compute_pe(1)
alpha = alphamax
fhprev = fdothall
engprev = eoriginal
alphaprev = 0.0_wp
!Backtrack until decrease in energy is sufficient
do while(.true.)
!Step forward along h
if (alpha > 0.0_wp) then
do ia = 1, atom_num_l
r_atom(:,ia) = rzeroatom(:,ia) + alpha*hatom(:,ia)
end do
do ip = 1, node_num_l
do ibasis = 1, basis_num(node_cg(ip))
r(:,ibasis,ip) = rzeronode(:,ibasis, ip) + alpha*hnode(:,ibasis, ip)
end do
end do
neval = neval + 1
call update_neighbor(iter, .true.)
call update_force
ecurrent=compute_pe(1)
end if
! Compute new fh, alpha, delfh
dotme(:) = 0
do ia = 1, atom_num_l
dotme(1) = dotme(1) + force_atom(1, ia)*force_atom(1,ia) + force_atom(2,ia)*force_atom(2,ia) &
+ force_atom(3,ia)*force_atom(3,ia)
dotme(2) = dotme(2) + force_atom(1, ia)*hatom(1,ia) + force_atom(2,ia)*hatom(2,ia) &
+ force_atom(3,ia)*hatom(3,ia)
end do
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
dotme(1) = dotme(1) + force_eq(1, ibasis,ip)*force_eq(1,ibasis,ip) &
+ force_eq(2,ibasis,ip)*force_eq(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*force_eq(3,ibasis,ip)
dotme(2) = dotme(2) + force_eq(1, ibasis,ip)*hnode(1,ibasis,ip) &
+ force_eq(2,ibasis,ip)*hnode(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*hnode(3,ibasis,ip)
end do
end do
end if
end do
call mpi_allreduce(dotme, dotall, 2, mpi_wp, mpi_sum, world, ierr)
ff = dotall(1)
fh = dotall(2)
delfh = fh-fhprev
!If fh or delfh is epsilon, reset to starting point and exit with error
if((abs(fh) < eps_quad) .or. (abs(delfh) < eps_quad)) then
if(atom_num_l > 0) r_atom(:, 1:atom_num_l) = rzeroatom(:, 1:atom_num_l)
if(node_num_l > 0) r(:,:,1:node_num_l) = rzeronode(:, :, 1:node_num_l)
call update_neighbor(iter, .true.)
call update_force
code = 11
return
end if
!!Check if ready for quadratic projection, quivalent to secant method
!alpha0 = projected alpha
relerr = abs(1.0_wp-(0.5_wp*(alpha-alphaprev)*(fh+fhprev)+ecurrent)/engprev)
alpha0 = alpha - (alpha-alphaprev)*fh/delfh
if((relerr <= quadratic_tol).and.(alpha0 > 0.0).and.(alpha0<alphamax)) then
!Step forward along h
do ia = 1, atom_num_l
r_atom(:,ia) = rzeroatom(:,ia) + alpha0*hatom(:,ia)
end do
do ip = 1, node_num_l
do ibasis = 1, basis_num(node_cg(ip))
r(:,ibasis,ip) = rzeronode(:,ibasis, ip) + alpha0*hnode(:,ibasis, ip)
end do
end do
neval = neval + 1
call update_neighbor(iter, .true.)
call update_force
ecurrent=compute_pe(1)
if((ecurrent-eoriginal) < emach) then
code = 0
return
end if
end if
!Quit with success if energy change is better than ideal
de_ideal = -backtrack_slope*alpha*fdothall
de = ecurrent-eoriginal
if(de <= de_ideal) then
code = 0
return
end if
!Save previous state
fhprev = fh
engprev = ecurrent
alphaprev = alpha
!Reduce alpha
alpha = alpha * alpha_reduce
!Backtracked too much, reset to starting point
! if de is positive exit with error'
! if de is negative than we have reached energy tol
if((alpha <= 0.0) .or. (de_ideal >= EMACH)) then
if(atom_num_l > 0) r_atom(:, 1:atom_num_l) = rzeroatom
if(ele_num_l > 0) r(:,:,1:node_num_l) = rzeronode
call update_neighbor(iter, .true.)
call update_force
if(de < 0.0) then
code = 1
return
else
code = 6
return
end if
end if
end do
end subroutine linesearch_quadratic
pure function get_cg_neval()
integer :: get_cg_neval
get_cg_neval = neval
return
end function get_cg_neval
end module cg

3392
src/comms.f90 Normal file

File diff suppressed because it is too large Load diff

255
src/compute.f90 Normal file
View file

@ -0,0 +1,255 @@
module compute
!This module contains all of the compute subroutines
use mpi
use parameters
use comms
use atom_types
use forces
use elements
use group
use box
implicit none
public
contains
function compute_pe(group, arg1)
!This function returns the summed energy for all atoms and elements, for elements it adds the contribution for all
!interpolated atoms
integer, intent(in) :: group
logical, intent(in), optional :: arg1
real(kind=wp) :: compute_pe
integer :: ia, ie, ip, ibasis, inod
real(kind=wp) :: pe, peall
logical :: weigh_ele
if(present(arg1)) then
weigh_ele=arg1
else
weigh_ele=.true.
end if
pe = 0
do ia = 1, atom_num_l
if (btest(a_mask(ia), group)) then
pe = pe + energy_atom(ia)
end if
end do
if(weigh_ele) then
do ie = 1, ele_num_l
if(btest(e_mask(ie), group).and.who_has_ele(ie)) then
do inod =1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis=1, basis_num(ie)
pe = pe + energy_eq(ibasis, ip)*mass_mat_coeff(size_ele(ie), etype(ie))
end do
end do
end if
end do
else
do ie = 1, ele_num_l
if(btest(e_mask(ie), group).and.who_has_ele(ie)) then
do inod =1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis=1, basis_num(ie)
pe = pe + energy_eq(ibasis, ip)
end do
end do
end if
end do
end if
call mpi_allreduce(pe, peall, 1, mpi_wp, mpi_sum, world, ierr)
compute_pe = peall
return
end function compute_pe
function compute_fnorm(group)
!This function returns the global norm of the force vector for all atoms and elements,
!for elements it adds the contribution for all interpolated atoms
integer, intent(in) :: group
real(kind=wp) :: compute_fnorm
integer :: ia, ie, ip, ibasis, inod
real(kind=wp) :: fme, f(3), fall
fme = 0.0_wp
do ia = 1, atom_num_l
if (btest(a_mask(ia), group)) then
fme = fme + force_atom(1,ia)*force_atom(1,ia) + force_atom(2, ia)*force_atom(2,ia) &
+force_atom(3,ia)*force_atom(3,ia)
end if
end do
do ie = 1, ele_num_l
if(btest(e_mask(ie), group).and.who_has_ele(ie)) then
do inod =1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis=1, basis_num(ie)
f = force_eq(:,ibasis, ip)
fme = fme + (f(1)*f(1) + f(2)*f(2) + f(3)*f(3))
end do
end do
end if
end do
call mpi_allreduce(fme, fall, 1, mpi_wp, mpi_sum, world, ierr)
compute_fnorm = sqrt(fall)
return
end function compute_fnorm
function compute_avgvel(group)
!This function returns the average velocity of the system
integer, intent(in) :: group
real(kind=wp) :: compute_avgvel(3)
integer :: ia, ie, ip, ibasis, inod
real(kind=wp) :: vme(3), vall(3)
vme=0.0_wp
do ia =1, atom_num_l
if (btest(a_mask(ia), group)) then
vme = vme + vel_atom(:,ia)
end if
end do
do ie = 1, ele_num_l
if(btest(e_mask(ie), group).and.who_has_ele(ie)) then
do inod =1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis=1, basis_num(ie)
vme = vme + vel(:, ibasis,ip)
end do
end do
end if
end do
call mpi_allreduce(vme(:), vall(:), 3, mpi_wp, mpi_sum, world, ierr)
compute_avgvel = vall/(group_counts(1,group)+group_counts(3,group))
return
end function compute_avgvel
function compute_ke(group, in_avg_vel)
!This subroutine calculates the kinetic energy of the group
integer, intent(in) :: group
real(kind=wp), intent(in), optional :: in_avg_vel(3)
real(kind=wp) :: compute_ke(2)
integer :: ia, j, ie, ip, ibasis, inod
real(kind=wp) :: kme(2), avgvel(3), kall(2)
if(present(in_avg_vel)) then
avgvel = in_avg_vel
else
avgvel = compute_avgvel(group)
end if
kme = 0.0_wp
do ia = 1, atom_num_l
if (btest(a_mask(ia), group)) then
do j = 1,3
kme(1) = kme(1) + 0.5_wp*masses(type_atom(ia))*const_motion*(vel_atom(j,ia) - avgvel(j))**2.0_wp
end do
end if
end do
do ie = 1, ele_num_l
if(btest(e_mask(ie), group).and.who_has_ele(ie)) then
do inod =1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis=1, basis_num(ie)
do j = 1,3
kme(2) = kme(2) + mass_mat_coeff(size_ele(ie), etype(ie))*0.5_wp*masses(basis_type(ibasis,ie)) &
*const_motion*(vel(j,ibasis,ip) - avgvel(j))**2.0_wp
end do
end do
end do
end if
end do
call mpi_allreduce(kme, kall, 2, mpi_wp, mpi_sum, world, ierr)
compute_ke = kall
return
end function compute_ke
function compute_temp(group, in_ke)
!This function calculates the temperature for the current model
integer, intent(in) :: group
real(kind=wp), intent(in), optional :: in_ke(2)
real(kind=wp) :: compute_temp(3)
real(kind=wp) :: ke(2)
if(present(in_ke)) then
ke = in_ke
else
ke = compute_ke(group)
end if
compute_temp = 0
compute_temp(1) = (2.0_wp*(ke(1)+ke(2)))/(3*(group_counts(1,group)+group_counts(4,group))*boltzmann)
if (atom_num > 0) compute_temp(2) = (2.0_wp*(ke(1)))/(3*(group_counts(1,group))*boltzmann)
if (ele_num > 0) compute_temp(3) = (2.0_wp*(ke(2)))/(3*group_counts(4,group)*boltzmann)
return
end function compute_temp
function compute_box_press()
!This calculates the box pressure. Can only be done on the full box which is why it takes no inputs
real(kind=wp) :: compute_box_press(3,3)
integer :: ia, ibasis, ip, inod, ie, i, j, k
real(kind=wp) :: vir_buffme(9), vir_buffall(9), box_volume
vir_buffme(:) = 0.0_wp
!Sum virial stress in the atom region
if(atom_num_l > 0) then
do ia = 1, atom_num_l
k = 1
do j = 1,3
do i = 1,3
vir_buffme(k) = vir_buffme(k) - virial_atom(i,j,ia)
k = k+1
end do
end do
end do
end if
if(ele_num_l > 0) then
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod,ie)
do ibasis = 1, basis_num(ie)
k = 1
do j = 1,3
do i = 1,3
vir_buffme(k) = vir_buffme(k) - virial_eq(i,j,ibasis,ip)*mass_mat_coeff(size_ele(ie), itype(ie))
k = k + 1
end do
end do
end do
end do
end if
end do
end if
!Now sum the virial over all processors
call mpi_allreduce(vir_buffme, vir_buffall, 9, mpi_wp, mpi_sum, world, ierr)
box_volume = box_length(1)*box_length(2)*box_length(3)
k = 1
do j = 1,3
do i = 1,3
compute_box_press(i,j) = nktv2p*vir_buffall(k)/box_volume
end do
end do
return
end function compute_box_press
end module compute

119
src/debug.f90 Normal file
View file

@ -0,0 +1,119 @@
module debug
!This module is used to run various debugging commands which can be used during simulations for additional correctness checks
use parameters
use forces
use elements
use str
implicit none
logical, private :: check_ele_flag
logical :: dflag
public
contains
subroutine parse_debug(line)
!This subroutine parses the debug command
character(len=*), intent(in) :: line
integer :: i, j, iospara
character(len=read_len) :: tmptxt, commands(10), msg
j = tok_count(line)-1
dflag=.true.
!Initialize flags
check_ele_flag=.false.
read(line, *, iostat=iospara, iomsg = msg) tmptxt, (commands(i), i =1, j)
!Now set the debug flags
do i = 1, j
select case(commands(i))
case('check_ele')
check_ele_flag= .true.
end select
end do
end subroutine parse_debug
subroutine run_debug
!This is the main running function which calls all of the sub debug subroutines
if(check_ele_flag) call check_ele
end subroutine run_debug
subroutine check_ele
!This code goes over every single element and compares the positions among all elements.
!This code is very slow
integer :: ie, ke, inod, i, j, k
real(kind=wp) :: rbuff(3*ng_max_node), rbuffall(3*ng_max_node*pro_num)
real(kind=wp) :: velbuff(3*ng_max_node), velbuffall(3*ng_max_node*pro_num)
logical :: have_ele, have_ele_all(pro_num)
character(len=read_len) :: msg
rbuff=0.0_wp
velbuff=0.0_wp
!Loop over all global tags
do ke = 1, ele_num
!Loop over all local tags to find the matching element
do ie = 1, ele_num_l
if(ele_glob_id(ie) == ke) exit
end do
if (ie > ele_num_l) then
have_ele=.false.
rbuff = 0.0_wp
else
have_ele=.true.
do inod =1, ng_node(etype(ie))
do i = 1, 3
rbuff(3*(inod-1)+i) = r(i, 1, cg_node(inod,ie))
if(need_vel) velbuff(3*(inod-1)+i) = vel(i, 1, cg_node(inod,ie))
end do
end do
end if
rbuffall=0.0_wp
velbuffall=0.0_wp
!Now communicate this element
call mpi_gather(have_ele, 1, mpi_logical, have_ele_all, 1, mpi_logical, root, world, ierr)
call mpi_gather(rbuff, 3*ng_max_node, mpi_wp, rbuffall, 3*ng_max_node, mpi_wp, root, world, ierr)
if(need_vel) call mpi_gather(velbuff, 3*ng_max_node, mpi_wp, velbuffall, 3*ng_max_node, mpi_wp, root, world, ierr)
!Now check the elements
if(rank==root) then
if(count(have_ele_all) > 1) then
do i = 1, pro_num
if(have_ele_all(i)) then
do j=1, pro_num
if((j>i) .and. (have_ele_all(j))) then
do k = 1, 3*ng_max_node
if(.not.is_equal(rbuffall((i-1)*ng_max_node*3 + k), &
rbuffall((j-1)*ng_max_node*3 + k))) then
write(msg, *) "Element with global tag ", ke, " does not have matching nodal ", &
"positions for processors ", i-1, " and ", j-1, " with values ", &
rbuffall((i-1)*ng_max_node*3 + k), " and ", &
rbuffall((j-1)*ng_max_node*3 + k)
call misc_error(msg)
end if
if(need_vel) then
if(.not.is_equal(velbuffall((i-1)*ng_max_node*3 + k), &
velbuffall((j-1)*ng_max_node*3 + k))) then
write(msg, *) "Element with global tag ", ke, " does not have matching nodal ", &
"velocities for processors ", i-1, " and ", j-1, " with values ", &
velbuffall((i-1)*ng_max_node*3 + k), " and ", &
velbuffall((j-1)*ng_max_node*3 + k)
call misc_error(msg)
end if
end if
end do
end if
end do
end if
end do
end if
end if
end do
end subroutine check_ele
end module debug

176
src/deform.f90 Normal file
View file

@ -0,0 +1,176 @@
module deform
!This module applies deformations to the box which is then applied to all of the atoms
use parameters
use box
use elements
use neighbors
use time
use comms
implicit none
integer :: deform_num
integer, private, parameter :: max_deform_num = 10
real(kind=wp),private :: rate(max_deform_num)
integer, private :: ddim(max_deform_num), nnevery(max_deform_num), exclude_gnum(max_deform_num)
public
contains
subroutine parse_deform(line)
!Parse the deform command
character(len=*), intent(in) :: line
character(len=read_len) :: tmptxt, deform_dim, msg, excludeg, args(20)
integer :: iospara, i, d, j, now_excludeg
real(kind = dp) :: strain, def
real(kind=dp), allocatable :: r_zero_atom(:, :), r_zero_node(:,:,:)
read(line, *, iostat = iospara, iomsg = msg) tmptxt, tmptxt
if(iospara > 0) call read_error(msg, iospara)
select case(tmptxt)
case('now')
now_excludeg=0
read(line, *, iostat= iospara, iomsg = msg) tmptxt, tmptxt, deform_dim, strain
if(iospara > 0) call read_error(msg, iospara)
j=tok_count(line)
if (j>4) then
read(line, *) args(1:j)
i=5
do while(i<=j)
select case(args(i))
case('exclude')
i=i+1
read(args(i), *) excludeg
i=i+1
now_excludeg = get_group_index(excludeg)
allocate(r_zero_atom(3,atom_num_l), r_zero_node(3, max_basisnum, node_num_l))
if(atom_num_l > 0) r_zero_atom = r_atom
if(ele_num_l > 0) r_zero_node = r
write(msg, *) "Excluding group ", trim(adjustl(excludeg)), ' from deform command'
call log_msg(msg)
case default
write(msg, *) "Keyword ", trim(adjustl(args(i))), " is not accepted in neighbor command"
call command_error(msg)
end select
end do
end if
!Now convert the dimension to an integer for use later
select case(deform_dim)
case('x','X')
d = 1
case('y','Y')
d = 2
case('z','Z')
d = 3
end select
!First we convert atoms and nodes to fractional coordinates if necessary
call x2frac
def = strain*orig_box_length(d)
box_bd(2*d-1) = box_bd(2*d-1) - 0.5_wp*def
box_bd(2*d) = box_bd(2*d) + 0.5_wp*def
box_length(d) = box_bd(2*d) - box_bd(2*d-1)
!Now update proc bds
do i = 1, 3
pro_length(i) = box_length(i) / num_pro(i)
pro_bd(2*i-1) = box_bd(2*i-1) + grid_coords(i) * pro_length(i)
pro_bd(2*i) = box_bd(2*i-1) + (grid_coords(i) + 1) * pro_length(i)
end do
call processor_bds
!Convert back to real coordinates if we applied a deformation
call frac2x
if(now_excludeg > 0) then
do i = 1, atom_num_l
if(btest(a_mask(i),now_excludeg)) then
r_atom(:, i)=r_zero_atom(:,i)
end if
end do
do i = 1, ele_num_l
if(btest(e_mask(i), now_excludeg)) then
r(:,:,i) = r_zero_node(:,:,i)
end if
end do
end if
write(msg, *) "Increasing box bounds in ", trim(adjustl(deform_dim)), " by ", def
call log_msg(msg)
call update_neighbor(iter, .false.,.true.)
case default
deform_num=deform_num + 1
read(line, *, iostat= iospara, iomsg = msg)tmptxt, nnevery(deform_num), deform_dim, rate(deform_num)
if(iospara > 0) call read_error(msg, iospara)
!Now convert the dimension to an integer for use later
select case(deform_dim)
case('x','X')
ddim(deform_num) = 1
case('y','Y')
ddim(deform_num) = 2
case('z','Z')
ddim(deform_num) = 3
end select
end select
return
end subroutine parse_deform
subroutine deform_box
!This subroutine updates the box boundaries and then rescales the atomic positions
integer :: i
real(kind=wp) :: def
logical :: convert
character(len=1) :: def_dim(3)
character(len=read_len) :: msg
def_dim = ['x','y','z']
!First update the box length
!Now update the box boundaries
convert = .false.
do i = 1, deform_num
if(mod(iter, nnevery(i)) == 0) then
!First c
if(.not.convert) then
!First we convert atoms and nodes to factional coordinates if necessary
call x2frac
convert = .true.
end if
def = time_step*rate(i)*orig_box_length(ddim(i))
box_bd(2*ddim(i)-1) = box_bd(2*ddim(i)-1) - 0.5_wp*def
box_bd(2*(ddim(i))) = box_bd(2*(ddim(i))) + 0.5_wp*def
box_length(ddim(i)) = box_bd(2*ddim(i)) - box_bd(2*ddim(i)-1)
write(msg, *) "Increasing box bounds in ", trim(adjustl(def_dim(ddim(1)))), " by ", def, " for deform ", i
call log_msg(msg)
end if
end do
!Now update proc bds
if(convert) then
do i = 1, 3
pro_length(i) = box_length(i) / num_pro(i)
pro_bd(2*i-1) = box_bd(2*i-1) + grid_coords(i) * pro_length(i)
pro_bd(2*i) = box_bd(2*i-1) + (grid_coords(i) + 1) * pro_length(i)
end do
call processor_bds
!Convert back to real coordinates if we applied a deformation
call frac2x
call update_neighbor(iter, .false.,.true.)
end if
return
end subroutine deform_box
end module deform

140
src/displace.f90 Normal file
View file

@ -0,0 +1,140 @@
module displace
!This module contains subroutines which apply displacements to atoms and elements
use forces
use parameters
use elements
use group
use str
use time
use neighbors
implicit none
public
contains
subroutine displace_points(line)
!This subroutine parses the displace command
character(len = *), intent(in) :: line
character(len=read_len) :: tmptxt, g, txtvec(3)
integer :: iospara, i, ia, ip, ibasis, ie, gnum
real(kind=wp) :: disp(3)
if((ele_num == 0).and.(atom_num == 0)) call misc_error("Model must be read in before calling displace command")
read(line, *, iostat=iospara) tmptxt, g, (txtvec(i), i = 1, 3)
if(iospara > 0) call read_error("Failure to read displace command", iospara)
!Get group number
gnum = get_group_index(g)
if(gnum == 0) then
call misc_error("Group "//trim(adjustl(g))// " in displace command has not been defined. "// &
"Please define group before use")
end if
!Read displacement vector
do i =1, 3
read(txtvec(i), *, iostat = iospara) disp(i)
if(iospara > 0) call read_error("Failure to read disp vector component "//trim(adjustl(txtvec(i))), iospara)
end do
!Now displace points
do ia = 1, atom_num_l
if(btest(a_mask(ia), gnum)) then
r_atom(:,ia) = r_atom(:, ia) + disp
end if
end do
do ip = 1, node_num_l
ie = node_cg(ip)
if(btest(e_mask(ie), gnum)) then
do ibasis = 1, basis_num(ie)
r(:, ibasis, ip) = r(:, ibasis, ip) + disp
end do
end if
end do
call update_neighbor(iter, .false., .true.)
end subroutine displace_points
subroutine ramp_displace(line)
!This subroutine parses the displace command
character(len = *), intent(in) :: line
character(len=read_len) :: tmptxt, g, txtvec(4), rdim, ddim
integer :: iospara, ia, ip, ibasis, ie, gnum, j, k
real(kind=wp) :: rhi, rlo, disp, hi, lo, frac
if((ele_num == 0).and.(atom_num == 0)) call misc_error("Model must be read in before calling ramp command")
read(line, *, iostat=iospara) tmptxt, g, ddim, txtvec(1), txtvec(2), rdim, rlo, rhi
if(iospara > 0) call read_error("Failure to read ramp command", iospara)
!Get the ramp dimensions
select case(ddim)
case('x','X')
j = 1
case('y','Y')
j = 2
case('z','Z')
j = 3
end select
select case(rdim)
case('x','X')
k = 1
case('y','Y')
k = 2
case('z','Z')
k = 3
end select
!Get the displacement magnitude
disp = rhi-rlo
!Parse the displacement hi and lo positions
call parse_pos(j, txtvec(1), lo)
call parse_pos(j, txtvec(2), hi)
!Get group number
gnum = get_group_index(g)
if(gnum == 0) then
call misc_error("Group "//trim(adjustl(g))// " in ramp command has not been defined. "// &
"Please define group before use")
end if
!Now displace points
do ia = 1, atom_num_l
if(btest(a_mask(ia), gnum)) then
if(r_atom(j,ia) > hi) then
frac = 1.0_wp
else if(r_atom(j,ia) < lo) then
frac = 0.0_wp
else
frac = (r_atom(j,ia) - lo)/(hi-lo)
end if
r_atom(k,ia) = r_atom(k, ia) + frac*disp + rlo
end if
end do
do ip = 1, node_num_l
ie = node_cg(ip)
if(btest(e_mask(ie), gnum)) then
do ibasis = 1, basis_num(ie)
if(r(j,ibasis,ip) > hi) then
frac = 1.0_wp
else if(r(j,ibasis,ip) < lo) then
frac = 0.0_wp
else
frac = (r(j, ibasis, ip) - lo)/(hi-lo)
end if
r(k, ibasis, ip) = r(k, ibasis, ip) + frac*disp + rlo
end do
end if
end do
if(nei_init) then
call update_neighbor(iter, .false., .true.)
end if
return
end subroutine ramp_displace
end module displace

831
src/dump.f90 Normal file
View file

@ -0,0 +1,831 @@
module dump
!
use atom_types
use parameters
use comms
use elements
use box
use errors
use forces
use math
use group
use time
implicit none
!Dump command parameters
integer, parameter :: max_dumps = 20
integer, private :: dump_num
integer, dimension(max_dumps), private :: dump_every, dump_type, dump_group
character(len = read_len), dimension(max_dumps), private :: base_filename, dump_name
!Gather arrays for atoms
integer, parameter :: max_send_count = 1024
integer, allocatable, save :: tag_atom_gather(:), type_atom_gather(:), a_mask_gather(:)
real(kind=wp), allocatable, save :: r_atom_gather(:,:), energy_atom_gather(:), force_atom_gather(:,:), &
virial_atom_gather(:,:,:), vel_atom_gather(:,:)
!Gather arrays for elements
integer, allocatable, save :: tag_ele_gather(:), size_ele_gather(:), etype_gather(:), &
basis_num_gather(:), basis_type_gather(:,:), e_mask_gather(:)
real(kind=wp), allocatable, save :: r_gather(:,:,:), energy_gather(:,:), force_gather(:,:,:), virial_gather(:,:,:,:), &
vel_gather(:,:,:)
!Gather arrays for interpolated atoms
integer, allocatable :: atomap_mask_gather(:), type_atomap_gather(:)
real(kind = wp), allocatable, save :: r_atomap_gather(:,:)
public
contains
subroutine dump_defaults
dump_every = huge(1)
dump_num = 0
end subroutine dump_defaults
subroutine parse_dump(line)
!Parse the dump command
character(len = *), intent(in) :: line
character(len = read_len) :: label, tmptxt, msg, dtype, dgroup
integer :: iospara, ppos
dump_num = dump_num + 1
read(line,*, iostat=iospara) label, dump_name(dump_num), dgroup, dtype, dump_every(dump_num), base_filename(dump_num)
if(iospara > 0) call read_error("Failure to read dump command", iospara)
!Check to make sure dump file format is correct
select case(dtype)
case('out')
dump_type(dump_num) = 1
case('lmp')
dump_type(dump_num) = 2
case default
write(tmptxt, *) "dump file format ", trim(adjustl(dtype)), " is not accepted"
call command_error(tmptxt)
end select
write(msg, *) "Writing output to ", trim(adjustl(base_filename(dump_num))), " using ", trim(adjustl(dtype)), " file format"
call log_msg(msg)
!Now get rid of the extension on the filename
ppos = scan(base_filename(dump_num), ".")
if(ppos > 0) then
base_filename(dump_num)(ppos:ppos+3) = ''
end if
!Parse the dump group
dump_group(dump_num) = get_group_index(dgroup)
if(dump_group(dump_num) > group_num) then
write(msg, *) "Group ", dgroup, " has not been created, please select an existing group to dump"
call command_error(msg)
end if
!Now if dump every = 0 set it to a very large number
if (dump_every(dump_num) == 0) then
dump_every(dump_num) = (huge(1))
end if
return
end subroutine parse_dump
subroutine parse_undump(line)
!This deletes a dump command from memory
character(len=*) :: line
integer :: i, iospara
character(len=read_len) :: undump_name, msg, tmp
!Parse the command to get the undump name
read(line, *, iostat = iospara, iomsg=msg) tmp, undump_name
if(iospara > 0) call read_error(msg, iospara)
!Now loop over all dump commands and delete the one that matches the name
do i = 1, dump_num
if(dump_name(i) == undump_name) then
dump_name(i:dump_num-1) = dump_name(i+1:dump_num)
dump_every(i:dump_num-1) = dump_every(i+1:dump_num)
dump_type(i:dump_num-1) = dump_type(i+1:dump_num)
dump_group(i:dump_num-1) = dump_group(i+1:dump_num)
base_filename(i:dump_num-1) = base_filename(i+1:dump_num)
dump_num = dump_num -1
exit
end if
end do
return
end subroutine parse_undump
subroutine parse_write_out(line)
!Parse the dump command
integer :: type_for_dump, dg
character(len = *), intent(in) :: line
character(len = read_len) :: label, tmptxt, msg, dtype, dgroup, fname
integer :: iospara, ppos
fname=''
read(line,*, iostat=iospara) label, dgroup, dtype, fname
if(iospara > 0) call read_error("Failure to read write_out command", iospara)
if(fname=='') call misc_error('Missing filename for write_out command' )
!Check to make sure dump file format is correct
select case(dtype)
case('out')
type_for_dump=1
case('lmp')
type_for_dump=2
case default
write(tmptxt, *) "dump file format ", trim(adjustl(dtype)), " is not accepted"
call command_error(tmptxt)
end select
write(msg, *) "Writing output to ", trim(adjustl(fname)), " using ", trim(adjustl(dtype)), " file format"
call log_msg(msg)
!Parse the dump group
dg = get_group_index(dgroup)
if(dg > group_num) then
write(msg, *) "Group ", dgroup, " has not been created, please select an existing group to dump"
call command_error(msg)
end if
!Now write it out
if(atom_num > 0) call gather_at
if((ele_num > 0).and.(type_for_dump == 1)) call gather_cg
if((ele_num > 0).and.(type_for_dump == 2)) call gather_atomap
!Loop over all dumps and check to see if any need to be written
if(rank == root) then
select case (type_for_dump)
case(1)
call write_pycac(iter, trim(adjustl(fname)), dg)
case(2)
call write_lmp(trim(adjustl(fname)), dg)
end select
end if
call dealloc_gather
return
end subroutine parse_write_out
subroutine write_dump(timestep, force_dump)
!This subroutine converts the timestep to the filename and writes the correctly formatted dump file
integer, intent(in) :: timestep
logical, intent(in), optional :: force_dump
integer :: i
character(len = read_len) :: fname
logical :: gathered, want_dump
if(present(force_dump)) then
want_dump = force_dump
else
want_dump = .false.
end if
gathered = .false.
do i = 1, dump_num
if((mod(timestep, dump_every(i)) == 0).or.want_dump) then
!Gather arrays if needed
if(.not.gathered) then
if(atom_num > 0) call gather_at
if((ele_num > 0).and.any(dump_type == 1)) call gather_cg
if((ele_num > 0).and.any(dump_type == 2)) call gather_atomap
gathered = .true.
end if
!Loop over all dumps and check to see if any need to be written
write(fname, *) iter
if(rank == root) then
select case (dump_type(i))
case(1)
call write_pycac(iter, trim(adjustl(base_filename(i)))//trim(adjustl(fname))//".out", dump_group(i))
case(2)
call write_lmp(trim(adjustl(base_filename(i)))//trim(adjustl(fname))//".lmp", dump_group(i))
end select
end if
end if
end do
if(gathered) call dealloc_gather
end subroutine write_dump
subroutine dealloc_gather
!Deallocate gather arrays when no longer needed
if(allocated(tag_ele_gather)) then
deallocate(tag_ele_gather, size_ele_gather, etype_gather, basis_num_gather, basis_type_gather, e_mask_gather, &
r_gather, energy_gather, force_gather, virial_gather, stat=allostat)
if(allostat > 0) call alloc_error("Failure to deallocate cg gather arrays", allostat)
if(need_vel) deallocate(vel_gather)
end if
if(allocated(tag_atom_gather)) then
deallocate(tag_atom_gather, type_atom_gather, a_mask_gather, r_atom_gather, energy_atom_gather, &
force_atom_gather, virial_atom_gather, stat = allostat)
if(allostat > 0) call alloc_error("Failure to deallocate at gather arrays", allostat)
if(need_vel) deallocate(vel_atom_gather)
if(allocated(type_atomap_gather)) then
deallocate(r_atomap_gather, type_atomap_gather, atomap_mask_gather, stat = allostat)
if(allostat > 0) call alloc_error("Failure to deallocate atomap gather arrays", allostat)
end if
end if
end subroutine dealloc_gather
subroutine gather_cg
integer :: n_ints, n_reals, ntot_ints, ntot_reals, packet_num, ie, ip, ireal, iint, &
n, je, jp, pro, inod, i, pb_in(3,max_basisnum, ng_max_node)
real(kind=wp) :: r_nodes(3, max_basisnum, ng_max_node), force_nodes(3, max_basisnum, ng_max_node), &
eng_nodes(max_basisnum, ng_max_node), virial_nodes(3,3,max_basisnum,ng_max_node),&
vel_nodes(3, max_basisnum, ng_max_node)
integer, allocatable :: gather_ints(:), send_ints(:)
real(kind=wp), allocatable :: gather_reals(:), send_reals(:)
!Allocate gather arrays if necessary
if(rank == root) then
allocate(tag_ele_gather(ele_num), size_ele_gather(ele_num), etype_gather(ele_num), e_mask_gather(ele_num), &
basis_num_gather(ele_num), basis_type_gather(max_basisnum, ele_num), &
r_gather(3, max_basisnum, node_num), energy_gather(max_basisnum, node_num), &
force_gather(3, max_basisnum, node_num), virial_gather(3,3,max_basisnum,node_num), stat=allostat)
if (allostat > 0) call alloc_error("Failure to allocate gather arrays", allostat)
if(need_vel) allocate(vel_gather(3, max_basisnum, node_num), stat=allostat)
if (allostat > 0) call alloc_error("Failure to allocate vel gather arrays", allostat)
end if
!Now allocate the send_arrays
n_ints = 5+max_basisnum
ntot_ints = max_send_count*n_ints
if(need_vel) then
n_reals = 22*max_basisnum*ng_max_node
else
n_reals = 19*max_basisnum*ng_max_node
end if
ntot_reals = max_send_count*n_reals
allocate(send_ints(ntot_ints), send_reals(ntot_reals), stat = allostat)
if (allostat > 0 ) call alloc_error("Failure to allocate send arrays for cg comm in gather_in_root", allostat)
!Only allocate gather arrays on root
if(rank == root) then
allocate(gather_ints(pro_num*ntot_ints), gather_reals(pro_num*ntot_reals))
if (allostat > 0 ) call alloc_error("Failure to allocate gather arrays for cg comm in gather_cg", allostat)
else
allocate(gather_ints(0), gather_reals(0))
if (allostat > 0 ) call alloc_error("Failure to allocate gather arrays for cg comm in gather_cg", allostat)
end if
!Now build the local send buff array
vel_nodes=0.0_wp
packet_num = 0
ip = 0
ie = 0
je = 0
jp = 0
iint = 0
ireal = 0
send_ints(:) = 0
send_reals(:) = 0.0_wp
pb_in = 0
if (rank == root) then
gather_ints(:) = 0
gather_reals(:) = 0.0_wp
end if
do while (ie < ele_num)
if( (je == ele_num_l).or.(packet_num == max_send_count) ) then
!If we have sent/packed all of our elements or if this packet is equal to send_counts then we communicate
!Gather integer data
call mpi_gather(send_ints, ntot_ints, mpi_integer, gather_ints, ntot_ints, mpi_integer, root, world, ierr)
!Gather real data
call mpi_gather(send_reals, ntot_reals, mpi_wp, gather_reals, ntot_reals, mpi_wp, root, world, ierr)
!If root then loop over all received values and unpack
if (rank == root) then
outerloop:do pro= 1, pro_num
innerloop:do i = 1, max_send_count
!Index the start point for the current element in the array
ireal=n_reals*((pro-1)*max_send_count + (i-1))
iint = n_ints*((pro-1)*max_send_count + (i-1))
!If this buffer in the array is 0 then we move it ahead to where the next processor beings
if (ie == ele_num) then
exit outerloop
!Exit if ie == ele_num
else if(gather_ints(iint+1) == 0) then
exit innerloop
end if
ie = ie + 1
!Unpack the received buffer for the current element
call unpack_ele_dump(gather_ints(iint+1:iint+n_ints), gather_reals(ireal+1:ireal+n_reals), &
etype_gather(ie), tag_ele_gather(ie), size_ele_gather(ie), e_mask_gather(ie), &
basis_num_gather(ie), basis_type_gather(:,ie), r_nodes, eng_nodes, &
force_nodes, virial_nodes, vel_nodes)
!Set nodal quantities
n = ng_node(etype_gather(ie))
r_gather(:,:,ip+1:ip+n) = r_nodes(:,:,1:n)
energy_gather(:,ip+1:ip+n) = eng_nodes(:,1:n)
force_gather(:,:,ip+1:ip+n) = force_nodes(:,:,1:n)
virial_gather(:,:,:,ip+1:ip+n) = virial_nodes(:,:, :,1:n)
if(need_vel) vel_gather(:,:,ip+1:ip+n) = vel_nodes(:,:,1:n)
ip = ip + n
end do innerloop
end do outerloop
end if
!broadcast gathered num to see if we need to continue in the loop
call mpi_bcast(ie, 1, mpi_int, root, world, ierr)
!gathered_num can't be more than ele_num
if(ie > ele_num) then
call misc_error("Gathered_num can't be larger than ele_num ")
end if
!Zero out arrays to reset for next round of communications
packet_num = 0
send_ints(:) = 0
send_reals(:) = 0.0_wp
ireal = 0
iint = 0
else
!We aren't ready to communicate so keep packing
je = je+1
if(je > ele_num_l) call misc_error("je greater than ele_num_l in gather_cg")
if(who_has_ele(je)) then
!get the node positions
do inod = 1, ng_node(etype(je))
jp = cg_node(inod, je)
r_nodes(:,:,inod) = r(:,:,jp)
eng_nodes(:,inod) = energy_eq(:,jp)
force_nodes(:,:,inod) = force_eq(:, :, jp)
virial_nodes(:,:,:,inod) = virial_eq(:,:,:,jp)
if(periodic) then
pb_in(:,:,inod) = pb_node(:,:,jp)
end if
if(need_vel) vel_nodes(:,:,inod) = vel(:,:,jp)
end do
!Get the start position for the data in the send arrays
ireal=n_reals*(packet_num)
iint = n_ints*(packet_num)
call pack_ele_dump(etype(je), tag_ele(je), size_ele(je), e_mask(je), basis_num(je), basis_type(:, je), &
pb_in, r_nodes, eng_nodes, force_nodes, virial_nodes, vel_nodes,&
send_ints(iint+1:iint+n_ints), send_reals(ireal+1:ireal+n_reals))
!Increment the packet num
packet_num = packet_num + 1
end if
end if
end do
return
end subroutine gather_cg
subroutine gather_at
!Gather all atoms to root processor
integer :: ia, ja, n_ints, n_reals, ntot_ints, ntot_reals, packet_num, ireal, iint, pro, i
integer, allocatable :: gather_ints(:), send_ints(:)
real(kind=wp) :: v(3)
real(kind=wp), allocatable :: gather_reals(:), send_reals(:)
!Allocate gather arrays if necessary
if(rank == root) then
allocate(tag_atom_gather(atom_num), type_atom_gather(atom_num), a_mask_gather(atom_num), r_atom_gather(3, atom_num), &
energy_atom_gather(atom_num), force_atom_gather(3,atom_num), virial_atom_gather(3,3,atom_num), &
stat = allostat)
if (allostat > 0) call alloc_error("Failure to allocate gather at arrays", allostat)
if(need_vel) allocate(vel_atom_gather(3,atom_num), stat = allostat)
if (allostat > 0) call alloc_error("Failure to allocate vel at arrays", allostat)
end if
!Now allocate the send_arrays
n_ints = 3
ntot_ints = max_send_count*n_ints
if(need_vel) then
n_reals=22
else
n_reals = 19
end if
ntot_reals = max_send_count*n_reals
allocate(send_ints(ntot_ints), send_reals(ntot_reals), stat = allostat)
if (allostat > 0 ) call alloc_error("Failure to allocate send arrays for cg comm in gather_in_root", allostat)
!Only allocate gather arrays on root
if(rank == root) then
allocate(gather_ints(pro_num*ntot_ints), gather_reals(pro_num*ntot_reals))
if (allostat > 0 ) call alloc_error("Failure to allocate gather arrays for cg comm in gather_cg", allostat)
else
allocate(gather_ints(0), gather_reals(0))
if (allostat > 0 ) call alloc_error("Failure to allocate gather arrays for cg comm in gather_cg", allostat)
end if
!Now build the local send buff array
packet_num = 0
iint = 0
ireal = 0
ia = 0
ja = 0
v(:) = 0.0_wp
send_ints(:) = 0
send_reals(:) = 0.0_wp
if (rank == root) then
gather_ints(:) = 0
gather_reals(:) = 0.0_wp
end if
do while (ia < atom_num)
if( (ja == atom_num_l).or.(packet_num == max_send_count) ) then
!If we have sent/packed all of our atoms or if this packet is equal to send_counts then we communicate
!Gather integer data
call mpi_gather(send_ints, ntot_ints, mpi_integer, gather_ints, ntot_ints, mpi_integer, root, world, ierr)
!Gather real data
call mpi_gather(send_reals, ntot_reals, mpi_wp, gather_reals, ntot_reals, mpi_wp, root, world, ierr)
!If root then loop over all received values and unpack
if (rank == root) then
outerloop:do pro= 1, pro_num
innerloop:do i = 1, max_send_count
!Index the start point for the current element in the array
ireal=n_reals*((pro-1)*max_send_count + (i-1))
iint = n_ints*((pro-1)*max_send_count + (i-1))
!If this buffer in the array is 0 then we move it ahead to where the next processor beings
!Exit if ia == atom_num
if (ia == atom_num) then
exit outerloop
else if(gather_ints(iint+1) == 0) then
exit innerloop
end if
ia = ia + 1
!Unpack the received buffer for the current element
call unpack_atom_dump(gather_ints(iint+1:iint+n_ints), gather_reals(ireal+1:ireal+n_reals), &
tag_atom_gather(ia), type_atom_gather(ia), a_mask_gather(ia), &
r_atom_gather(:,ia), energy_atom_gather(ia), force_atom_gather(:,ia),&
virial_atom_gather(:,:,ia), v(1:3))
if(need_vel) vel_atom_gather(:,ia) = v(1:3)
end do innerloop
end do outerloop
end if
!broadcast gathered num to see if we need to continue in the loop
call mpi_bcast(ia, 1, mpi_int, root, world, ierr)
!ia can't be more than atom_num
if(ia > atom_num) then
call misc_error("Gathered_num can't be larger than atom_num ")
end if
!Zero out arrays to reset for next round of communications
packet_num = 0
send_ints(:) = 0
send_reals(:) = 0.0_wp
ireal = 0
iint = 0
else
!We aren't ready to communicate so keep packing
ja = ja+1
if(ja > atom_num_l) call misc_error("ja greater than atom_num_l in gather_cg")
!Get the start position for the data in the send arrays
ireal=n_reals*(packet_num)
iint = n_ints*(packet_num)
if(need_vel) v(:) = vel_atom(:,ja)
call pack_atom_dump(tag_atom(ja), type_atom(ja), a_mask(ja), r_atom(:,ja), energy_atom(ja), force_atom(:,ja), &
virial_atom(:,:, ja), v, send_ints(iint+1:iint+n_ints), send_reals(ireal+1:ireal+n_reals))
!Increment the packet num
packet_num = packet_num + 1
end if
end do
return
end subroutine gather_at
subroutine gather_atomap
!This subroutine gathers atomap information. This is required to get the correct atomistic positions for
!NVT simulations
integer :: ia, ja, ntot_ints, ntot_reals, packet_num, ireal, iint, pro, i, &
atomap_mask(atomap_num_l), ie, iatom, iatomap, virt_count, ibasis
integer, allocatable :: gather_ints(:), send_ints(:)
real(kind=wp), allocatable :: gather_reals(:), send_reals(:)
!Allocate gather arrays if necessary
ntot_ints = 2*max_send_count
ntot_reals = 3*max_send_count
if(rank == root) then
allocate(r_atomap_gather(3, atomap_num), type_atomap_gather(atomap_num), atomap_mask_gather(atomap_num), &
stat = allostat)
if (allostat > 0) call alloc_error("Failure to allocate gather atomap arrays", allostat)
allocate(gather_ints(pro_num*ntot_ints), gather_reals(pro_num*ntot_reals), stat=allostat)
if(allostat > 0) call alloc_error("Failure to allocate gather account", allostat)
else
allocate(gather_ints(0), gather_reals(0), stat=allostat)
if(allostat > 0) call alloc_error("Failure to allocate gather account", allostat)
end if
!Now allocate the send_arrays
allocate(send_ints(ntot_ints), send_reals(ntot_reals), stat = allostat)
if (allostat > 0 ) call alloc_error("Failure to allocate send arrays for cg comm in gather_in_root", allostat)
!First get the group positions for all atomaps
do ie = 1, ele_num_l
select case(etype(ie))
case(1,2,3)
virt_count=(size_ele(ie)+1)**3
end select
do iatom= 1, virt_count
do ibasis = 1, basis_num(ie)
iatomap = cg_atomap(basis_num(ie)*(iatom-1) + ibasis, ie)
if(iatomap /= 0) then
atomap_mask(iatomap) = e_mask(ie)
end if
end do
end do
end do
packet_num = 0
iint = 0
ireal = 0
ia = 0
ja = 0
send_ints(:) = 0
send_reals(:) = 0.0_wp
if(rank == root) then
gather_ints(:) = 0
gather_reals(:) = 0.0_wp
end if
do while (ia < atomap_num)
if( (ja == atomap_num_l).or.(packet_num == max_send_count) ) then
!If we have sent/packed all of our atoms or if this packet is equal to send_counts then we communicate
!Gather integer data
call mpi_gather(send_ints, ntot_ints, mpi_integer, gather_ints, ntot_ints, mpi_integer, root, world, ierr)
!Gather real data
call mpi_gather(send_reals, ntot_reals, mpi_wp, gather_reals, ntot_reals, mpi_wp, root, world, ierr)
!If root then loop over all received values and unpack
if (rank == root) then
outerloop:do pro= 1, pro_num
innerloop:do i = 1, max_send_count
!Index the start point for the current element in the array
ireal=3*((pro-1)*max_send_count + (i-1))
iint =2*((pro-1)*max_send_count + (i-1))
!If this buffer in the array is 0 then we move it ahead to where the next processor beings
!Exit if ia == atom_num
if (ia == atomap_num) then
exit outerloop
else if(gather_ints(iint+1) == 0) then
exit innerloop
end if
ia = ia + 1
!Unpack the received buffer for the current element
type_atomap_gather(ia) = gather_ints(iint+1)
atomap_mask_gather(ia) = gather_ints(iint+2)
r_atomap_gather(:, ia) = gather_reals(ireal+1:ireal+3)
end do innerloop
end do outerloop
end if
!broadcast gathered num to see if we need to continue in the loop
call mpi_bcast(ia, 1, mpi_int, root, world, ierr)
!ia can't be more than atom_num
if(ia > atomap_num) then
call misc_error("Gathered_num can't be larger than atom_num ")
end if
!Zero out arrays to reset for next round of communications
packet_num = 0
send_ints(:) = 0
send_reals(:) = 0.0_wp
ireal = 0
iint = 0
else
!We aren't ready to communicate so keep packing
ja = ja+1
if(ja > atomap_num_l) call misc_error("ja greater than atom_num_l in gather_cg")
!Get the start position for the data in the send arrays
ireal=3*(packet_num)
iint = 2*(packet_num)
send_ints(iint+1) = type_atomap(ja)
send_ints(iint+2) = atomap_mask(ja)
send_reals(ireal+1:ireal+3) = r_atomap(:,ja)
!Increment the packet num
packet_num = packet_num + 1
end if
end do
return
end subroutine gather_atomap
subroutine write_pycac(timestep, filename, g)
integer, intent(in) :: timestep, g
character(len=*), intent(in) :: filename
integer :: i, ia, ie, ip, ibasis, inod, write_atoms, write_eles
real(kind=wp) :: v_vec(6)
!Write pycac dump file
1 format("#This is a pycac dump file created using the quasi-static CAC code"/ &
"Can be converted using cacmb code from https://gitlab.com/aselimov/cacmb")
2 format("Atoms: ", i16, " Elements:", i16)
3 format("Atom format: id atom_type x y z pe fx fy fz v11 v22 v33 v32 v31 v21")
13 format("Atom format: id atom_type x y z pe fx fy fz v11 v22 v33 v32 v31 v21 velx vely velz")
4 format('Element format: id num_node num_basis esize')
5 format('Node format: ip ib basis_type x y z pe fx fy fz v11 v22 v33 v32 v31 v21')
15 format('Node format: ip ib basis_type x y z pe fx fy fz v11 v22 v33 v32 v31 v21 velx vely velz')
6 format("Timestep: ", i16)
7 format("Box_bd ", 3a, 6f23.15)
!First get counts of atoms/elements in write group
!If group is all then it's just atom_num and ele_num
if (g == 1) then
write_atoms = atom_num
write_eles = ele_num
else
write_atoms = 0
write_eles = 0
do i = 1, atom_num
if(btest(a_mask_gather(i),g)) write_atoms = write_atoms + 1
end do
do i =1, ele_num
if (btest(e_mask_gather(i), g)) write_eles = write_eles + 1
end do
end if
!Open file if root
open(unit=11, file=trim(adjustl(filename)), action='write', status='replace', position='rewind')
!Write header information
write(11, 1)
write(11, 6) timestep
write(11, 2) write_atoms, write_eles
write(11, 7) (boundary(i:i)//" ", i = 1, 3), (box_bd(i), i = 1, 6)
!get the atom type number into the form string
write(11, '(a, 999(f23.15,1x))') "Masses: ", (masses(i), i = 1, natom_types)
!First write the atom section if needed
if (atom_num > 0) then
!Write atom header
if(need_vel) then
write(11,13)
else
write(11,3)
end if
do ia = 1, atom_num
if(btest(a_mask_gather(ia), g)) then
!Assign virial vector
v_vec(1) = virial_atom_gather(1,1,ia)
v_vec(2) = virial_atom_gather(2,2,ia)
v_vec(3) = virial_atom_gather(3,3,ia)
v_vec(4) = virial_atom_gather(3,2,ia)
v_vec(5) = virial_atom_gather(3,1,ia)
v_vec(6) = virial_atom_gather(2,1,ia)
if (need_vel) then
write(11, *) tag_atom_gather(ia), type_atom_gather(ia), r_atom_gather(:,ia), &
energy_atom_gather(ia), force_atom_gather(:,ia), v_vec(:), &
vel_atom_gather(:,ia)
else
write(11, *) tag_atom_gather(ia), type_atom_gather(ia), r_atom_gather(:,ia), &
energy_atom_gather(ia), force_atom_gather(:,ia), v_vec(:)
end if
end if
end do
end if
if(ele_num > 0) then
!Write cg header
write(11,4)
if(need_vel) then
write(11,15)
else
write(11,5)
end if
ip=0
do ie = 1, ele_num
if(btest(e_mask_gather(ie), g)) then
!Write element information
write(11,'(4i16)' ) tag_ele_gather(ie), ng_node(etype_gather(ie)), basis_num_gather(ie), size_ele_gather(ie)
!write node information
do inod = 1, ng_node(etype_gather(ie))
ip = ip + 1
do ibasis = 1, basis_num_gather(ie)
v_vec(1) = virial_gather(1, 1, ibasis, ip)
v_vec(2) = virial_gather(2, 2, ibasis, ip)
v_vec(3) = virial_gather(3, 3, ibasis, ip)
v_vec(4) = virial_gather(3, 2, ibasis, ip)
v_vec(5) = virial_gather(3, 1, ibasis, ip)
v_vec(6) = virial_gather(2, 1, ibasis, ip)
if(need_vel) then
write(11, *) inod, ibasis, basis_type_gather(ibasis, ie), r_gather(:,ibasis,ip), &
energy_gather(ibasis,ip), force_gather(:, ibasis, ip), v_vec(:), &
vel_gather(:, ibasis,ip)
else
write(11, *) inod, ibasis, basis_type_gather(ibasis, ie), r_gather(:,ibasis,ip), &
energy_gather(ibasis,ip), force_gather(:, ibasis, ip), v_vec(:)
end if
end do
end do
end if
end do
end if
close(11)
return
end subroutine write_pycac
subroutine write_lmp(filename, g)
integer, intent(in) :: g
character(len = *) :: filename
integer :: i, write_num =0, max_atom_tag
open(unit=11, file=trim(adjustl(filename)), action='write', status='replace', position='rewind')
!Write comment line
write(11, '(a)') '# lmp file made using CAC code'
write(11, '(a)')
!Calculate total atom number to write
write_num = 0
do i = 1, atom_num
if (btest(a_mask_gather(i), g)) write_num = write_num + 1
end do
do i = 1, atomap_num
if (btest(atomap_mask_gather(i), g)) write_num = write_num + 1
end do
!Write total number of atoms + interpolated atoms
write(11, '(i16, a)') write_num, ' atoms'
!Write number of atom types
write(11, '(i16, a)') natom_types, ' atom types'
write(11, '(a)')
!Write box boundaries
write(11, '(2f23.15, a)') box_bd(1:2), ' xlo xhi'
write(11, '(2f23.15, a)') box_bd(3:4), ' ylo yhi'
write(11, '(2f23.15, a)') box_bd(5:6), ' zlo zhi'
!Masses
write(11, '(a)') 'Masses'
write(11, '(a)')
do i = 1, natom_types
write(11, '(i16, f23.15)') i, masses(i)
end do
write(11, '(a)')
!Now first write atom positions
write(11, '(a)') 'Atoms'
write(11, '(a)')
do i = 1, atom_num
if (btest(a_mask_gather(i), g)) then
write(11, '(2i16, 3f23.15)') tag_atom_gather(i), type_atom_gather(i), r_atom_gather(:,i)
end if
end do
max_atom_tag = maxval(tag_atom_gather)
do i = 1, atomap_num
if (btest(atomap_mask_gather(i), g)) then
write(11, '(2i16, 3f23.15)') max_atom_tag+i, type_atomap_gather(i), r_atomap_gather(:,i)
end if
end do
close(11)
return
end subroutine write_lmp
pure function need_dump(time)
integer, intent(in) :: time
logical :: need_dump
integer :: i
need_dump = .false.
do i = 1, dump_num
if(mod(time, dump_every(i)) == 0) then
need_dump = .true.
return
end if
end do
return
end function need_dump
end module dump

195
src/dynamics.f90 Normal file
View file

@ -0,0 +1,195 @@
module dynamics
!This code contains the code needed to run the various types of dynamics
use parameters
use vel_verlet
use neighbors
use quenched_dynamics
use forces
use comms
use potential
use dump
use thermo
use time
use temp
use berendsen
use deform
implicit none
real(kind=wp), save :: itime
character(len = 100) :: ensemble
integer :: ensemble_option
private :: pre_step, post_step, step
public
contains
subroutine dynamics_defaults
ensemble_option = 0
ensemble = "none"
end subroutine dynamics_defaults
subroutine parse_dynamics(line)
character(len = *), intent(in) :: line
character(len=read_len) tmptxt
integer :: iospara
read(line, *, iostat = iospara) tmptxt, ensemble
if (iospara > 0) call read_error("Invalid read of dynamics command", iospara)
select case(ensemble)
case("none")
ensemble_option = 0
if(rank==root) call log_msg("Warning: no dynamics specified. Atom positions will not evolve")
case('NVE', 'nve')
ensemble_option = 1
case('qd')
ensemble_option = 2
case('eu')
ensemble_option = 3
case('ld')
ensemble_option = 4
case default
call command_error("Ensemble "//trim(adjustl(ensemble))//" is not currently accepted as an option for dynamics")
end select
return
end subroutine parse_dynamics
subroutine parse_run(line)
!parse the run command
character(len = *), intent(in) :: line
character(len=read_len) :: tmptxt
integer :: iospara
begin_step = iter
read(line, *, iostat = iospara) tmptxt, run_steps
if (iospara > 0) call read_error("Invalid read of run command", iospara)
call run_dynamics(run_steps)
end subroutine parse_run
subroutine run_dynamics(num_steps)
!This subroutine actually runs the dynamics steps
integer, intent(in) :: num_steps
integer ::i
!Check to make sure timestep is greater than 0
if(time_step <= 0.0_wp) then
call misc_error("Time_step was never set, please add command time_step val before run command")
end if
!Initialize the force calculation and the first dump
if(nei_init) then
call update_neighbor(iter, .false., .true.)
else
if(ele_num > 0) call ghost_cg
if(atom_num> 0) call ghost_at
call neighbor_lists
end if
call update_force
if(first_run) call write_dump(iter, .true.)
call write_thermo_style
call write_thermo_out(iter)
do i = 1, num_steps
iter = iter + 1
t = t+time_step
call pre_step(iter)
call step
call post_step(iter)
end do
need_virial = .true.
call update_force
call write_dump(iter, .true.)
call write_thermo_out(iter)
call log_neighbor_info
first_run = .false.
end subroutine run_dynamics
subroutine pre_step(i)
!This subroutine is run before each dynamics timestep is calculated
!i is the current step number
integer, intent(in) ::i
!If we are dumping this timestep then we need to calculate the virial stress
if(need_dump(i).or.pflag) need_virial = .true.
!Check to see if we need virial for the thermo command
if((mod(i,thermo_every)==0).and.need_p) need_virial = .true.
call deform_box
return
end subroutine pre_step
subroutine post_step(i)
!This subroutine is run after the dynamics timestep is calculated
!i is the current step number
integer, intent(in) ::i
!Check to see if we need to rescale velocity
if(tflag) call rescale_v
!Check to see if we need to dilate box
if(pflag) call rescale_box
!Dump check
call write_dump(i)
!Thermo if we need it
if(mod(i,thermo_every)==0) call write_thermo_out(i)
need_virial = .false.
return
end subroutine post_step
subroutine step
!This subroutine calls the correct dynamics to run
!i is the current step number
select case(ensemble_option)
case(0)
continue
case(1)
call verlet(iter)
case(2)
call qd(iter)
case(3)
call euler(iter)
end select
end subroutine step
subroutine euler(iter)
integer, intent(in) :: iter
integer :: ip, ibasis, ia
do ip = 1, node_num_l
do ibasis=1, basis_num(node_cg(ip))
vel(:, ibasis, ip) = vel(:, ibasis, ip) + time_step*ftm2v/masses(basis_type(ibasis,node_cg(ip))) &
*force_eq(:,ibasis,ip)
r(:, ibasis, ip) = r(:, ibasis, ip) + time_step*vel(:, ibasis,ip)
end do
end do
do ia = 1, atom_num_l
vel_atom(:,ia) = vel_atom(:,ia) + time_step*ftm2v/masses(type_atom(ia))*force_atom(:,ia)
r_atom(:,ia) = r_atom(:,ia) + time_step*vel_atom(:,ia)
end do
call update_neighbor(iter)
call update_force
end subroutine euler
end module dynamics

1607
src/eam.f90 Normal file

File diff suppressed because it is too large Load diff

662
src/elements.f90 Normal file
View file

@ -0,0 +1,662 @@
module elements
use parameters
use errors
use box
use math
implicit none
!This module contains all arrays and subroutines for processing atom and element data
!Variables to track the various global values
integer, save :: ele_num, node_num, atom_num, ng_max_node, max_basisnum, atomap_num, atomap_max
logical :: need_vel !this variable denotes if we need velocity arrays
!Variables to track various local values
integer, save :: ele_num_l, node_num_l, atom_num_l, atomap_num_l, ele_num_lr, node_num_lr, atom_num_lr, atomap_num_lr, &
atomap_num_lg, atom_num_lg
!Current accepted element type definitions
integer, parameter :: defined_element_types=3
character(len=5), parameter, dimension(defined_element_types) :: element_types = (/ '1NN', '2NN', '20C' /)
logical, dimension(defined_element_types) :: etype_present
integer, parameter, dimension(defined_element_types) :: ng_node = (/ 8, 8, 20 /) !Number of nodes per element
integer :: max_size !Max of size_ele
!Coarse-grained elements
integer, allocatable, save :: tag_ele(:), size_ele(:), etype(:), basis_num(:), basis_type(:,:), &
cg_node(:,:), node_cg(:), pb_node(:,:,:), cg_atomap(:,:), ele_glob_id(:), e_mask(:)
real(kind=wp), allocatable, save :: r(:,:,:), vel(:,:,:) !allocate to 3, max_basis_num, node_num_l, so it's nodal not
! by element
!Interpolated atoms
integer, allocatable, save :: tag_atomap(:), type_atomap(:), atomap_to_intpo(:,:)
real(kind=wp), allocatable, save :: r_atomap(:,:)
!Interpolation variable
real(kind=wp), allocatable, save :: a_interpo(:,:,:,:)
integer :: unique_sizes
integer, allocatable :: size_to_shape(:), shape_sizes(:)
logical, allocatable :: needed_atomap(:)
!Real atoms
integer, allocatable :: tag_atom(:), type_atom(:), a_mask(:)
real(kind=wp), allocatable :: r_atom(:,:), vel_atom(:,:)
public
contains
subroutine alloc_at_arrays
!Allocate atom arrays
integer :: allostat
if(allocated(tag_atom)) deallocate(tag_atom, type_atom, a_mask, r_atom)
allocate( tag_atom(atom_num_l), &
type_atom(atom_num_l), &
a_mask(atom_num_l), &
r_atom(3,atom_num_l), &
stat=allostat)
if(allostat > 0) call alloc_error("Failed to allocate at_arrays", allostat)
tag_atom(:) = 0
type_atom(:) = 0
a_mask(:) = 0
r_atom = 0.0_wp
if(need_vel) then
if(allocated(vel_atom)) deallocate(vel_atom)
allocate(vel_atom(3,atom_num_l), stat=allostat)
if(allostat > 0) call alloc_error("Failed to allocate vel_atom", allostat)
vel_atom = 0.0_wp
end if
return
end subroutine alloc_at_arrays
subroutine dealloc_at_arrays
!deallocate atom arrays
integer :: allostat
deallocate( tag_atom, type_atom, a_mask, r_atom, stat=allostat)
if(allostat > 0) call alloc_error("Failed to deallocate at_arrays", allostat)
if(need_vel) then
deallocate(vel_atom, stat=allostat)
if(allostat > 0) call alloc_error("Failed to deallocate vel_atom", allostat)
end if
return
end subroutine dealloc_at_arrays
subroutine alloc_velocity_arrays
if((ele_num > 0) .and..not.allocated(vel)) then
allocate(vel(3, max_basisnum, node_num_lr), stat = allostat)
if (allostat > 0) call alloc_error("Failure to allocate vel in alloc_velocity array", allostat)
vel(:,:,:) = 0.0_wp
end if
if((atom_num > 0).and..not.allocated(vel_atom)) then
allocate(vel_atom(3, atom_num_lr), stat = allostat)
if (allostat > 0) call alloc_error("Failure to allocate vel_atom in alloc_velocity array", allostat)
vel_atom(:,:) = 0.0_wp
end if
end subroutine alloc_velocity_arrays
subroutine dealloc_velocity_arrays
if((ele_num > 0) .and..not.allocated(vel)) then
deallocate(vel, stat = allostat)
if (allostat > 0) call alloc_error("Failure to deallocate vel in alloc_velocity array", allostat)
end if
if((atom_num > 0).and..not.allocated(vel_atom)) then
deallocate(vel_atom, stat = allostat)
if (allostat > 0) call alloc_error("Failure to deallocate vel_atom in alloc_velocity array", allostat)
end if
end subroutine dealloc_velocity_arrays
subroutine grow_at_arrays(seg_real)
!Grow atom arrays
integer, intent(in), optional :: seg_real
integer :: grow_by
integer, allocatable :: tag_array(:), type_array(:), mask_array(:)
real(kind=wp), allocatable :: r_array(:,:), vel_array(:,:)
if (present(seg_real)) then
grow_by = seg_real
else
grow_by = seg_num
end if
allocate( tag_array(atom_num_lr+grow_by), &
type_array(atom_num_lr+grow_by),&
mask_array(atom_num_lr+grow_by),&
r_array(3,atom_num_lr+grow_by), &
stat=allostat)
if(allostat > 0) call alloc_error("Failure allocating atom arrays in grow_at_arrays", allostat)
tag_array(1:atom_num_lr) = tag_atom
tag_array(atom_num_lr+1:) = 0
call move_alloc(tag_array, tag_atom)
type_array(1:atom_num_lr) = type_atom
type_array(atom_num_lr+1:) = 0
call move_alloc(type_array, type_atom)
mask_array(1:atom_num_lr) = a_mask
mask_array(atom_num_lr+1:) = 0
call move_alloc(mask_array, a_mask)
r_array(:,1:atom_num_lr) = r_atom
r_array(:,atom_num_lr+1:) = 0.0_wp
call move_alloc(r_array, r_atom)
if(need_vel) then
allocate(vel_array(3,atom_num_lr+grow_by), stat=allostat)
if(allostat > 0) call alloc_error("Failed allocating vel_array in grow_at_arrays", allostat)
vel_array(:,1:atom_num_lr) = vel_atom
vel_array(:,atom_num_lr+1:) = 0.0_wp
call move_alloc(vel_array, vel_atom)
end if
atom_num_lr = atom_num_lr +grow_by
return
end subroutine grow_at_arrays
subroutine alloc_cg_arrays
!Allocate arrays needed for coarse-grained elements
integer :: allostat
if(allocated(tag_ele)) deallocate(tag_ele, size_ele, ele_glob_id, etype, cg_node, cg_atomap, basis_num, basis_type, &
node_cg, r)
allocate( &
tag_ele(ele_num_l), &
size_ele(ele_num_l), &
ele_glob_id(ele_num_l), &
etype(ele_num_l), &
e_mask(ele_num_l), &
cg_node(ng_max_node, ele_num_l), &
cg_atomap(atomap_max, ele_num_l), &
basis_num(ele_num_l), &
basis_type(max_basisnum, ele_num_l), &
node_cg(node_num_l), &
r(3,max_basisnum, node_num_l), &
stat=allostat )
if (allostat > 0) call alloc_error("Failed allocating cg_arrays", allostat)
tag_ele(:) = 0
size_ele(:) = 0
etype(:) = 0
e_mask(:) = 0
ele_glob_id(:) = 0
cg_node(:,:) =0
cg_atomap(:,:) = 0
basis_num(:) = 0
basis_type(:,:) = 0
node_cg(:) = 0
r(:,:,:) = 0.0_wp
if(periodic) then
if (allocated(pb_node)) deallocate(pb_node)
allocate(pb_node(3, max_basisnum, node_num_l), stat=allostat)
if (allostat > 0) call alloc_error("Failed allcoating pb_node in alloc_cg_arrays", allostat)
pb_node(:,:,:) = 0
end if
if(need_vel) then
if (allocated(vel)) deallocate(vel)
allocate(vel(3, max_basisnum, node_num_l))
if (allostat > 0) call alloc_error("Failed allocating cg vel array", allostat)
vel(:,:,:) = 0.0_wp
end if
!Interpolation arrays
if(allocated(tag_atomap)) deallocate(tag_atomap, type_atomap, atomap_to_intpo, r_atomap)
allocate (&
tag_atomap(atomap_num_l), &
type_atomap(atomap_num_l), &
atomap_to_intpo(2, atomap_num_l), &
r_atomap(3, atomap_num_l), &
stat=allostat &
)
if (allostat > 0) call alloc_error("Failed allocating interp_arrays", allostat)
tag_atomap(:) = 0
type_atomap = 0
atomap_to_intpo = 0
r_atomap(:,:) = 0.0_wp
end subroutine alloc_cg_arrays
subroutine dealloc_cg_arrays
!Deallocate arrays needed for coarse-grained elements
integer :: allostat
deallocate( tag_ele, size_ele, ele_glob_id, etype, e_mask, cg_node, cg_atomap, basis_num, basis_type, &
node_cg, r, stat=allostat )
if (allostat > 0) call alloc_error("Failed deallocating cg_arrays", allostat)
if(periodic) then
deallocate(pb_node, stat=allostat)
if (allostat > 0) call alloc_error("Failed deallcoating pb_node in alloc_cg_arrays", allostat)
end if
if(need_vel) then
deallocate(vel)
if (allostat > 0) call alloc_error("Failed deallocating cg vel array", allostat)
end if
!Interpolation arrays
deallocate(tag_atomap, type_atomap, r_atomap, atomap_to_intpo, stat=allostat)
if (allostat > 0) call alloc_error("Failed allocating interp_arrays", allostat)
return
end subroutine dealloc_cg_arrays
subroutine grow_cg_arrays(opt, seg_real)
!This subroutine just grows the element arrays if needed
!opt is 1 for growing element arrays, 2 for growing node arrays, 3 for growing atomap arrays
!seg_real is the growth size, usually compared to
integer, intent(in) :: opt
integer, intent(in), optional :: seg_real
!temp element arrays
integer, allocatable ::tag_ele_array(:), size_ele_array(:), etype_array(:), cg_node_array(:,:), basis_num_array(:), &
basis_type_array(:,:), cg_atomap_array(:,:), mask_array(:)
!Temp node arrays
integer, allocatable :: node_cg_array(:), pb_node_array(:,:,:), ele_glob_id_array(:)
real(kind=wp), allocatable :: r_array(:,:,:), vel_array(:,:,:)
!temp atomap arrays
integer, allocatable :: tag_atomap_array(:), type_atomap_array(:), atomap_to_intpo_array(:,:)
real(kind=wp), allocatable :: r_atomap_array(:,:)
integer :: grow_by
if (present(seg_real)) then
grow_by = seg_real
else
grow_by = seg_num
end if
if (opt == 1) then
allocate( &
tag_ele_array(ele_num_lr+grow_by), &
ele_glob_id_array(ele_num_lr+grow_by), &
size_ele_array(ele_num_lr+grow_by), &
etype_array(ele_num_lr+grow_by), &
mask_array(ele_num_lr+grow_by), &
cg_node_array(ng_max_node, ele_num_lr+grow_by), &
cg_atomap_array(atomap_max, ele_num_lr+grow_by), &
basis_num_array(ele_num_lr+grow_by), &
basis_type_array(max_basisnum, ele_num_lr+grow_by), &
stat=allostat &
)
if(allostat > 0) call alloc_error("Failed allocation of temp ele arrays in grow_cg_arrays", allostat)
tag_ele_array(1:ele_num_lr) = tag_ele(:)
tag_ele_array(ele_num_lr+1:) = 0
call move_alloc(tag_ele_array, tag_ele)
ele_glob_id_array(1:ele_num_lr) = ele_glob_id(:)
ele_glob_id_array(ele_num_lr+1:) = 0
call move_alloc(ele_glob_id_array, ele_glob_id)
size_ele_array(1:ele_num_lr) = size_ele(:)
size_ele_array(ele_num_lr+1:) = 0
call move_alloc(size_ele_array, size_ele)
etype_array(1:ele_num_lr) = etype(:)
etype_array(ele_num_lr+1:) = 0
call move_alloc(etype_array, etype)
mask_array(1:ele_num_lr) = e_mask
mask_array(ele_num_lr+1:)= 0
call move_alloc(mask_array, e_mask)
cg_node_array(:,1:ele_num_lr) = cg_node(:,:)
cg_node_array(:,ele_num_lr+1:) = 0
call move_alloc(cg_node_array, cg_node)
cg_atomap_array(:,1:ele_num_lr) = cg_atomap(:,:)
cg_atomap_array(:,ele_num_lr+1:) = 0
call move_alloc(cg_atomap_array, cg_atomap)
basis_num_array(1:ele_num_lr) = basis_num(:)
basis_num_array(ele_num_lr+1:) = 0
call move_alloc(basis_num_array, basis_num)
basis_type_array(:,1:ele_num_lr) = basis_type(:,:)
basis_type_array(:,ele_num_lr+1:) = 0
call move_alloc(basis_type_array, basis_type)
ele_num_lr = ele_num_lr + grow_by
else if (opt==2) then
allocate( node_cg_array(node_num_lr + grow_by), &
r_array(3, max_basisnum, node_num_lr + grow_by), &
stat=allostat)
node_cg_array(1:node_num_lr) = node_cg
node_cg_array(node_num_lr+1:) = 0
call move_alloc(node_cg_array, node_cg)
r_array(:,:,1:node_num_lr) = r(:,:,1:node_num_lr)
r_array(:,:,node_num_lr+1:) = 0.0_wp
call move_alloc(r_array, r)
if(need_vel) then
allocate( vel_array(3, max_basisnum, node_num_lr + grow_by), &
stat=allostat)
if(allostat > 0) call alloc_error("Failure allocating vel in grow_cg_arrays",allostat)
vel_array(:,:,1:node_num_lr) = vel(:,:,1:node_num_lr)
vel_array(:,:,node_num_lr+1:) = 0.0_wp
call move_alloc(vel_array, vel)
end if
if(periodic) then
allocate( pb_node_array(3, max_basisnum, node_num_lr + grow_by), &
stat=allostat)
if(allostat > 0) call alloc_error("Failure allocating pb_node in grow_cg_arrays",allostat)
pb_node_array(:,:,1:node_num_lr) = pb_node
pb_node_array(:,:,node_num_lr+1:) = 0.0_wp
call move_alloc(pb_node_array, pb_node)
end if
node_num_lr = node_num_lr + grow_by
else if(opt==3) then
!Resize atomap arrays
allocate(tag_atomap_array(atomap_num_lr + grow_by), &
type_atomap_array(atomap_num_lr + grow_by), &
atomap_to_intpo_array(2, size(atomap_to_intpo,2)+grow_by), &
r_atomap_array(3,atomap_num_lr + grow_by), &
stat = allostat)
if(allostat > 0) call alloc_error("Failure allocating temp atomap arrays in grow_cg_arrays", allostat)
tag_atomap_array(1:atomap_num_lr) = tag_atomap(:)
tag_atomap_array(atomap_num_lr+1:) = 0
call move_alloc(tag_atomap_array, tag_atomap)
type_atomap_array(1:atomap_num_lr) = type_atomap(:)
type_atomap_array(atomap_num_lr+1:) = 0
call move_alloc(type_atomap_array, type_atomap)
atomap_to_intpo_array = 0
atomap_to_intpo_array(:, 1:size(atomap_to_intpo,2)) = atomap_to_intpo
call move_alloc(atomap_to_intpo_array, atomap_to_intpo)
r_atomap_array(:,1:atomap_num_lr) = r_atomap(:,:)
r_atomap_array(:,atomap_num_lr+1:) = 0.0_wp
call move_alloc(r_atomap_array, r_atomap)
atomap_num_lr = atomap_num_lr + grow_by
end if
return
end subroutine grow_cg_arrays
subroutine setup_interpolation(max_esize, unique_enum, unique_esize)
!This subroutine sets up the interpolation arrays
integer, intent(in) :: max_esize, unique_enum
integer, dimension(unique_enum), intent(in) :: unique_esize
integer :: i, ix, iy, iz, esize, iatom
real(kind = wp) :: dr, ds, dt
!First allocate important arrays
allocate( a_interpo(maxval(ng_node), atomap_max, unique_enum, defined_element_types), &
size_to_shape(max_esize), &
shape_sizes(max_esize), &
stat = allostat)
if (allostat > 0) call alloc_error("Failed to allocate shape arrays in setup_interpolation", allostat)
!Save the maximum esize as we need it later
max_size = max_esize
!Now loop over all unique elements. We duplicate the interpolation shape function for all element types, this leads to some
!extra memory usage, but it shoudl be small enough to where it doesn't matter
size_to_shape(:) = 0
unique_sizes = unique_enum
a_interpo(:,:,:,:) = 0.0_wp
do i = 1, unique_enum
esize = unique_esize(i)
shape_sizes(i) =unique_esize(i)
!size_to_shape maps the element size to the shape function index
size_to_shape(esize) = i
iatom=0
!Cube element shape functions
do iz = 1, esize+1
dt=-1.0_dp +(iz-1)*(2.0_dp/(esize))
do iy = 1, esize+1
ds=-1.0_dp +(iy-1)*(2.0_dp/(esize))
do ix = 1, esize+1
dr=-1.0_dp +(ix-1)*(2.0_dp/(esize))
iatom = iatom + 1
a_interpo(1,iatom,i,1:2) = (1.0_wp-dr)*(1.0_wp-ds)*(1.0_wp-dt)/8.0_wp
a_interpo(2,iatom,i,1:2) = (1.0_wp+dr)*(1.0_wp-ds)*(1.0_wp-dt)/8.0_wp
a_interpo(3,iatom,i,1:2) = (1.0_wp+dr)*(1.0_wp+ds)*(1.0_wp-dt)/8.0_wp
a_interpo(4,iatom,i,1:2) = (1.0_wp-dr)*(1.0_wp+ds)*(1.0_wp-dt)/8.0_wp
a_interpo(5,iatom,i,1:2) = (1.0_wp-dr)*(1.0_wp-ds)*(1.0_wp+dt)/8.0_wp
a_interpo(6,iatom,i,1:2) = (1.0_wp+dr)*(1.0_wp-ds)*(1.0_wp+dt)/8.0_wp
a_interpo(7,iatom,i,1:2) = (1.0_wp+dr)*(1.0_wp+ds)*(1.0_wp+dt)/8.0_wp
a_interpo(8,iatom,i,1:2) = (1.0_wp-dr)*(1.0_wp+ds)*(1.0_wp+dt)/8.0_wp
end do
end do
end do
!serendipity 20 node element shape function
iatom = 0
do iz = 1, esize+1
dt = (iz - ((real(esize,wp)/2.0_wp) + 1.0_wp)) / (real(esize,wp) / 2.0_wp)
do iy = 1, esize+1
ds = (iy - ((real(esize,wp)/2.0_wp) + 1.0_wp)) / (real(esize,wp) / 2.0_wp)
do ix = 1, esize+1
dr = (ix - ((real(esize,wp) / 2.0_wp) + 1.0_wp)) / (real(esize,wp) / 2.0_wp)
iatom = iatom + 1
!Corner nodes
a_interpo(1,iatom,i,3) = (1.0_dp-dr)*(1.0_dp-ds)*(1.0_dp-dt)*(-dr-ds-dt-2)/8.0_dp
a_interpo(2, iatom,i,3) = (1.0_dp+dr)*(1.0_dp-ds)*(1.0_dp-dt)*(dr-ds-dt-2)/8.0_dp
a_interpo(3, iatom,i,3) = (1.0_dp+dr)*(1.0_dp+ds)*(1.0_dp-dt)*(dr+ds-dt-2)/8.0_dp
a_interpo(4, iatom,i,3) = (1.0_dp-dr)*(1.0_dp+ds)*(1.0_dp-dt)*(-dr+ds-dt-2)/8.0_dp
a_interpo(5, iatom,i,3) = (1.0_dp-dr)*(1.0_dp-ds)*(1.0_dp+dt)*(-dr-ds+dt-2)/8.0_dp
a_interpo(6, iatom,i,3) = (1.0_dp+dr)*(1.0_dp-ds)*(1.0_dp+dt)*(dr-ds+dt-2)/8.0_dp
a_interpo(7, iatom,i,3) = (1.0_dp+dr)*(1.0_dp+ds)*(1.0_dp+dt)*(dr+ds+dt-2)/8.0_dp
a_interpo(8, iatom,i,3) = (1.0_dp-dr)*(1.0_dp+ds)*(1.0_dp+dt)*(-dr+ds+dt-2)/8.0_dp
!Side nodes, first node r is zero
a_interpo(9, iatom,i,3) = (1-dr*dr)*(1-ds)*(1-dt)/4.0_dp
a_interpo(11, iatom,i,3) = (1-dr*dr)*(1+ds)*(1-dt)/4.0_dp
a_interpo(17, iatom,i,3) = (1-dr*dr)*(1-ds)*(1+dt)/4.0_dp
a_interpo(19, iatom,i,3) = (1-dr*dr)*(1+ds)*(1+dt)/4.0_dp
!node s is zero
a_interpo(10, iatom,i,3) = (1+dr)*(1-ds*ds)*(1-dt)/4.0_dp
a_interpo(12, iatom,i,3) = (1-dr)*(1-ds*ds)*(1-dt)/4.0_dp
a_interpo(18, iatom,i,3) = (1+dr)*(1-ds*ds)*(1+dt)/4.0_dp
a_interpo(20, iatom,i,3) = (1-dr)*(1-ds*ds)*(1+dt)/4.0_dp
!node t is zero
a_interpo(13, iatom,i,3) = (1-dr)*(1-ds)*(1-dt*dt)/4.0_dp
a_interpo(14, iatom,i,3) = (1+dr)*(1-ds)*(1-dt*dt)/4.0_dp
a_interpo(15, iatom,i,3) = (1+dr)*(1+ds)*(1-dt*dt)/4.0_dp
a_interpo(16, iatom,i,3) = (1-dr)*(1+ds)*(1-dt*dt)/4.0_dp
end do
end do
end do
end do
return
end subroutine setup_interpolation
subroutine dealloc_shape_arrays
!this subroutine deallocates shape arrays. Will be needed if element sizes change for some reason.
if(allocated(a_interpo)) then
deallocate( a_interpo, size_to_shape, shape_sizes, stat = allostat)
if (allostat > 0) call alloc_error("Failed deallocating shape arrays in dealloc_shape_arrays", allostat)
end if
end subroutine
subroutine interp_atom(iatom, esize, ele_type, pb_in, basisnum, r_nodes, r_iatom)
!This subroutine interpolates atom positions based on the iatom number
!ARGUMENTS
integer, intent(in) :: iatom !Atom to interpolate
integer, intent(in) :: esize !Size of element, needed for getting the correct interpolation array
integer, intent(in) :: ele_type
integer, dimension(3, max_basisnum, ng_max_node), intent(in) :: pb_in !Periodic boundary info
integer, intent(in) :: basisnum !Number of basis atoms at nodes
real(kind=wp), dimension(3, max_basisnum, ng_max_node), intent(in) :: r_nodes !Contiguous array of nodal positions
real(kind=wp), dimension(3,max_basisnum), intent(out) :: r_iatom !Position of all interpolated atoms at lat point
!INTERNAL VARIABLES
integer :: inod, ibasis, info(3), node_num
real(kind=wp) :: rout(3), shape_fun(ng_node(ele_type))
logical :: ipb, ip_change(max_basisnum,ng_node(ele_type))
node_num = ng_node(ele_type)
!Initialization
r_iatom(:,:) = 0.0_wp
ipb =.false.
ip_change(:,:) = .false.
shape_fun = a_interpo(1:node_num, iatom, size_to_shape(esize), ele_type)
do inod = 1, node_num
do ibasis = 1, basisnum
!Figure out periodic boundaries for the nodes
rout=r_nodes(:,ibasis, inod)
if(periodic) then
!Restore periodic boundaries if needed
call restore_pb(rout, pb_in(:,ibasis,inod), ipb)
ip_change(ibasis,inod) = ipb
end if
r_iatom(:,ibasis) = r_iatom(:,ibasis) + rout*shape_fun(inod)
end do
end do
!Now apply periodic boundaries if needed
if(any(ip_change)) then
do ibasis = 1, basisnum
call cross_pb(r_iatom(:,ibasis), info)
if(.not. in_box_bd(r_iatom(:, ibasis))) then
call bounds_error("Atom incorrectly interpolated", r_iatom, box_bd)
end if
end do
end if
return
end subroutine interp_atom
subroutine update_virtual_atoms(update_all)
!This subroutine updates the virtual atoms from nodal positions
logical, intent(in) :: update_all
integer :: ie, virt_count, iatom, ibasis, jatomap, iatomap, inod, ip, pb_in(3, max_basisnum, ng_max_node)
real(kind = wp) :: r_interp(3, max_basisnum), r_nodes(3, max_basisnum, ng_max_node)
!Loop over all atoms
jatomap = 0
do ie = 1, ele_num_l
select case(etype(ie))
case(1,2,3)
virt_count = (size_ele(ie)+1)**3
end select
!Get the nodal values into a connected array
r_nodes(:,:,:) = 0.0_wp
pb_in(:,:,:) = 0
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
r_nodes(:,:,inod) = r(:,:,ip)
if(periodic) pb_in(:,:,inod) = pb_node(:,:,ip)
end do
do iatom = 1, virt_count
!interpolate the atoms
call interp_atom(iatom, size_ele(ie), etype(ie), pb_in, basis_num(ie), r_nodes, r_interp)
do ibasis = 1, basis_num(ie)
!Now get the real atom position
iatomap=cg_atomap(basis_num(ie)*(iatom-1) + ibasis, ie)
if(iatomap /= 0) then
jatomap = jatomap+1
!This just checks to make sure that the cg_atomap is sorted, ie the first virtual atom we have
! is first in r_atomap and the second is second etc.
if(iatomap /= jatomap) then
print *, "Error: Iatomap", iatomap, " should equal", jatomap
call mpi_abort(mpi_comm_world, 1, ierr)
end if
r_atomap(:,iatomap) = r_interp(:,ibasis)
end if
end do
end do
end do
return
end subroutine update_virtual_atoms
pure function get_virtual_count(et, esize)
!Return the number of virtual lattice points
integer, intent(in) :: et, esize
integer :: get_virtual_count
select case(et)
case(1,2,3)
get_virtual_count = (esize+1)**3
end select
return
end function get_virtual_count
subroutine x2frac
!This subroutine changes r arrays to be fractional coordinates
integer :: i, j, ibasis
do i = 1, atom_num_l
do j = 1,3
r_atom(j, i) = (r_atom(j,i)-box_bd(2*j-1))/(box_length(j))
end do
end do
do i = 1, node_num_l
do ibasis= 1, basis_num(node_cg(i))
do j = 1, 3
r(j, ibasis, i) = (r(j,ibasis,i)-box_bd(2*j-1))/(box_length(j))
end do
end do
end do
return
end subroutine x2frac
subroutine frac2x
!This subroutine changes the fractional coordinates back to real coordinates
integer :: i, j, ibasis
do i = 1, atom_num_l
do j = 1,3
r_atom(j,i) = r_atom(j,i)*box_length(j) + box_bd(2*j-1)
end do
end do
do i = 1, node_num_l
do ibasis= 1, basis_num(node_cg(i))
do j = 1, 3
r(j,ibasis,i) = r(j, ibasis, i)*box_length(j) + box_bd(2*j-1)
end do
end do
end do
return
end subroutine frac2x
end module elements

73
src/errors.f90 Normal file
View file

@ -0,0 +1,73 @@
module errors
!This subroutines is in charge of handling error strings and exiting the application
use mpi
use parameters
use logger
implicit none
public
contains
subroutine alloc_error(error_string, allostat)
integer, intent(in) :: allostat
character(len=*), intent(in) :: error_string
character(len=read_len) :: msg
write(msg, *) "Allocate Error: ", trim(adjustl(error_string)), " with allostat ", allostat
call exit_error(msg)
return
end subroutine alloc_error
subroutine bounds_error(error_string, r, bounds)
!Atom unexpectedly outside boundary, usually box boundary
real(kind=wp), intent(in) :: r(3), bounds(6)
character(len=*), intent(in) :: error_string
character(len=read_len) :: msg
write(msg,*) "Bounds Error: ", trim(adjustl(error_string)), " for position ", r, " and bounds", bounds
call exit_error(msg)
return
end subroutine bounds_error
subroutine read_error(error_string, stat)
!Error when parsing string
character(len = *), intent(in) :: error_string
integer, intent(in) :: stat
character(len=read_len) :: msg
write(msg,*) "Read Error: ", trim(adjustl(error_string)), " with stat ", stat
call exit_error(msg)
return
end subroutine read_error
subroutine misc_error(error_string)
!Misc_error
character(len=*), intent(in) :: error_string
character(len=read_len) :: msg
write(msg,*) "Error: ", trim(adjustl(error_string))
call exit_error(msg)
return
end subroutine misc_error
subroutine command_error(error_string)
!Error with incorrect command
character(len = *) :: error_string
character(len=read_len) :: msg
write(msg,*) "Command Error: ", trim(adjustl(error_string))
call exit_error(msg)
return
end subroutine command_error
subroutine exit_error(msg)
!Exit code with error
character(len=*), intent(in) :: msg
call log_msg(msg, 0, .true.)
call close_log
call mpi_abort(mpi_comm_world, 1, ierr)
return
end subroutine exit_error
end module errors

284
src/fire.f90 Normal file
View file

@ -0,0 +1,284 @@
module fire
use parameters
use comms
use elements
use forces
use time
use atom_types
use neighbors
use potential
use debug
implicit none
real(kind=wp), private, save :: tmax, tmin, dtgrow, dtshrink, alpha0, alphashrink, alpha, last_negative, dmax, &
dtmax, dtmin
integer, private, save :: delaystep, vdfmax, vdotf_negatif, neval
logical, private, save :: initialdelay, flagv0
real(kind = wp), parameter :: &
f_inc_fire = 1.1_wp, &
f_dec_fire = 0.5_wp, &
f_alpha_fire = 0.99_wp, &
alpha_start_fire = 0.1_wp
real(kind=wp), save :: alpha_fire
public
contains
subroutine fire_defaults
!This sets the default parameters for fire. These are taken from the default parameters used in lammps
!Presented in https://doi.org/10.1016%2Fj.commatsci.2020.109584
!Max timstep is tmax x delta t_start
tmax = 10.0_wp
!Minimum timestep is tmin x delta t_start
tmin = 0.02_wp
!Number of steps to wait after P<0 before increasing deltat
delaystep = 20
!Factor by which del t increases/decrases
dtgrow = 1.1_wp
dtshrink = 0.5_wp
!Coefficient for mixing velocity and force vectors and the factor by which it decreases
alpha0 = 0.25_wp
alpha = alpha0
alphashrink = 0.99_wp
!Exit after vdfmax consecutive iterations with P(t) < 0
vdfmax = 2000 !Inertia correction halfstepback = .true. activates initial delay in modifying delta t and alpha
initialdelay = .true.
dmax = 0.1
end subroutine fire_defaults
subroutine set_fire_param(param, val)
character(len=*), intent(in) :: param
real(kind=wp), intent(in) :: val
select case(param)
case('alpha0')
alpha0=val
case('tmax')
tmax=val
case('tmin')
tmin=val
end select
end subroutine set_fire_param
subroutine fire_init
!Initialize fire variables
last_negative = 0
vdotf_negatif = 0
flagv0=.true.
neval = 0
alpha = alpha0
dtmax = tmax*time_step
dtmin = tmin*time_step
end subroutine fire_init
subroutine fire_iterate(i, code)
integer, intent(in) :: i
integer, intent(out) :: code
integer :: ie, ibasis, ip, ia
real(kind= wp) :: vdotfme, vdotfall, vdotvme, vdotvall, fdotfme, fdotfall, scale1, scale2, dtf, dtfm, &
dtvone, vmax, dtv
logical :: delayflag
!First calculate vdotfall
vdotfme = 0.0_wp
code=0
do ip = 1, node_num_l
ie = node_cg(ip)
if(who_has_ele(ie)) then
do ibasis = 1, basis_num(ie)
!We multiply by the energy of all virtual atoms for the energy, which is force_eq times the nodal mass
vdotfme = vdotfme + vel(1,ibasis,ip)*force_eq(1,ibasis,ip)+vel(2,ibasis,ip)*force_eq(2,ibasis,ip) &
+ vel(3,ibasis,ip)*force_eq(3,ibasis,ip)
end do
end if
end do
do ia = 1, atom_num_l
vdotfme = vdotfme + force_atom(1,ia)*vel_atom(1,ia) + force_atom(2,ia)*vel_atom(2,ia) &
+ force_atom(3,ia)*vel_atom(3,ia)
end do
call mpi_allreduce(vdotfme, vdotfall, 1, mpi_wp, mpi_sum, world, ierr)
!if (v dot f) > 0:
!v = (1-alpha) v + alpha |v| Fhat
!|v| = length of v, Fhat = unit f
!Only: (1-alpha) and alpha |v| Fhat is calculated here
!the modificatin of v is made within the integration, after v update
!if more than delaystep since v dot f was negative:
!increase timestep, update global timestep and decrease alpha
if (vdotfall > 0) then
vdotvme = 0.0_wp
fdotfme = 0.0_wp
vdotf_negatif = 0
do ip = 1, node_num_l
ie = node_cg(ip)
if (who_has_ele(ie)) then
do ibasis = 1, basis_num(ie)
!We multiply by the force/velocity of all virtual atoms for the energy, which is force_eq times the nodal mass
vdotvme = vdotvme + vel(1,ibasis,ip)*vel(1,ibasis,ip)+vel(2,ibasis,ip)*vel(2,ibasis,ip) &
+ vel(3,ibasis,ip)*vel(3,ibasis,ip)
fdotfme = fdotfme + force_eq(1,ibasis,ip)*force_eq(1,ibasis,ip) &
+ force_eq(2,ibasis,ip)*force_eq(2,ibasis,ip) &
+ force_eq(3,ibasis,ip)*force_eq(3,ibasis,ip)
end do
end if
end do
do ia = 1, atom_num_l
vdotvme = vdotvme + vel_atom(1,ia)*vel_atom(1,ia) + vel_atom(2,ia)*vel_atom(2,ia) &
+ vel_atom(3,ia)*vel_atom(3,ia)
fdotfme = fdotfme + force_atom(1,ia)*force_atom(1,ia) + force_atom(2,ia)*force_atom(2,ia) &
+ force_atom(3,ia)*force_atom(3,ia)
end do
call mpi_allreduce(vdotvme, vdotvall, 1, mpi_wp, mpi_sum, world, ierr)
call mpi_allreduce(fdotfme, fdotfall, 1, mpi_wp, mpi_sum, world, ierr)
!Calculate scaling factors
scale1 = 1.0 - alpha
if(fdotfall <= 1d-20) then
scale2 = 0.0_wp
else
scale2 = alpha * sqrt(vdotvall/fdotfall)
end if
if(i - last_negative > delaystep) then
time_step = min(time_step*dtgrow, dtmax)
alpha = alpha * alphashrink
end if
else
last_negative = i
delayflag = .true.
if ((i - 1 < delaystep).and.initialdelay) delayflag = .false.
if(delayflag) then
alpha = alpha0
if(time_step*dtshrink >= dtmin) time_step = time_step * dtshrink
end if
!Check stopping criterion
vdotf_negatif = vdotf_negatif + 1
if ((vdfmax > 0).and.(vdotf_negatif > vdfmax)) then
code = 3
return
end if
!Apply intertia correcection
do ip = 1, node_num_l
do ibasis = 1, basis_num(ie)
r(:,ibasis, ip) = r(:, ibasis, ip) - 0.5_wp*time_step*vel(:,ibasis,ip)
end do
end do
do ia = 1, atom_num_l
r_atom(:,ia) = r_atom(:, ia) - 0.5_wp*time_step*vel_atom(:,ia)
end do
!Zero velocities
if(node_num_l > 0) vel(:,:,:) = 0.0_wp
if(atom_num_l > 0 ) vel_atom(:,:) = 0.0_wp
flagv0 = .true.
end if
!Evaluate velocity to determine whether dtv has to be limited. Required when v is reset
if(flagv0) then
dtf = time_step*ftm2v
call update_neighbor(iter)
call update_force
neval = neval + 1
do ip = 1, node_num_l
ie = node_cg(ip)
do ibasis = 1, basis_num(ie)
dtfm = dtf/masses(basis_type(ibasis,ie))
vel(:,ibasis, ip) = dtfm * force_eq(:, ibasis, ip)
end do
end do
do ia = 1, atom_num_l
dtfm = dtf/masses(type_atom(ia))
vel_atom(:,ia) = dtfm*force_atom(:,ia)
end do
end if
!Limit timestep so no particle moves further than dmax
dtvone = time_step
do ip = 1, node_num_l
if(who_has_ele(node_cg(ip))) then
vmax = maxval(vel(:,:,ip))
if(dtvone*vmax > dmax) dtvone = dmax/vmax
end if
end do
do ia = 1, atom_num_l
vmax = maxval(vel_atom(:,ia))
if(dtvone*vmax > dmax) dtvone = dmax/vmax
end do
call mpi_allreduce(dtvone, dtv, 1, mpi_wp, mpi_min, world, ierr)
!Reset velocities if necessary
if(flagv0) then
if(node_num_l > 0) vel(:,:,:) = 0.0_wp
if(atom_num_l > 0) vel_atom(:,:) = 0.0_wp
end if
!Now do the integration step, this is the lammps default semi-implicit Euler scheme
dtf = dtv * ftm2v
do ip = 1, node_num_l
do ibasis = 1, basis_num(node_cg(ip))
dtfm = dtf/masses(basis_type(ibasis,node_cg(ip)))
vel(:,ibasis, ip) = vel(:, ibasis, ip) + dtfm * force_eq(:, ibasis, ip)
if (vdotfall > 0.0_wp) vel(:,ibasis,ip) = scale1*vel(:,ibasis,ip) + scale2*force_eq(:,ibasis,ip)
r(:,ibasis,ip) = r(:, ibasis, ip) + dtv*vel(:, ibasis,ip)
end do
end do
do ia = 1, atom_num_l
dtfm = dtf/masses(type_atom(ia))
vel_atom(:,ia) = vel_atom(:,ia) + dtfm*force_atom(:,ia)
if(vdotfall > 0.0_wp) vel_atom(:,ia) = scale1*vel_atom(:,ia) + scale2*force_atom(:,ia)
r_atom(:,ia) = r_atom(:,ia) + dtv*vel_atom(:,ia)
end do
call update_neighbor(iter)
call update_force
neval = neval + 1
! do ip = 1, node_num_l
! do ibasis = 1, basis_num(ie)
! dtfm = dtf/masses(basis_type(ibasis,ie))
! vel(:,ibasis, ip) = vel(:, ibasis, ip) + dtfm * force_eq(:, ibasis, ip)
! end do
! end do
! do ia = 1, atom_num_l
! dtfm = dtf/masses(type_atom(ia))
! vel_atom(:,ia) = vel_atom(:,ia) + dtfm*force_atom(:,ia)
! end do
!Set the velocity evaluation flog
flagv0 = .false.
end subroutine fire_iterate
pure function get_fire_delaystep()
integer :: get_fire_delaystep
get_fire_delaystep = delaystep
return
end function
pure function get_fire_neval()
integer :: get_fire_neval
get_fire_neval = neval
return
end function
subroutine fire_clean
time_step = orig_time_step
end subroutine fire_clean
end module

196
src/force_mod.f90 Normal file
View file

@ -0,0 +1,196 @@
module force_mod
use forces
use parameters
use elements
use group
use str
use time
implicit none
integer, parameter, private :: max_force = 20
integer :: set_force_num, add_force_num
integer, private :: force_group(max_force), add_force_group(max_force), add_force_every(max_force)
logical, private :: force_mask(3, max_force), add_force_mask(3, max_force), add_force_norm(max_force), &
first_add(max_force)
real(kind=wp), private :: fvec(3, max_force), add_f(3, max_force)
public
contains
subroutine init_set_force
set_force_num = 0
force_group = 0
force_mask = .false.
first_add = .false.
fvec = 0.0_wp
end subroutine
subroutine init_add_force
add_force_num = 0
add_force_group = 0
add_force_mask = .false.
add_force_every(:) = 0
add_f = 0.0_wp
end subroutine init_add_force
subroutine parse_set_force(line)
!This subroutine parses the set_force command
character(len = *), intent(in) :: line
character(len=read_len) :: tmptxt, g, txtvec(3)
integer :: iospara, i
set_force_num = set_force_num + 1
first_add(set_force_num) = .true.
read(line, *, iostat=iospara) tmptxt, g, (txtvec(i), i = 1, 3)
if(iospara > 0) call read_error("Failure to read set_force command", iospara)
!Get group number
force_group(set_force_num) = get_group_index(g)
if(force_group(set_force_num) == 0) then
call misc_error("Group "//trim(adjustl(g))// " in set_force command has not been defined. "// &
"Please define group before use")
end if
!Read force_vec and force_mask
do i =1, 3
call to_lower(txtvec(i))
if(.not.(txtvec(i) == 'null')) then
force_mask(i, set_force_num) = .true.
read(txtvec(i), *, iostat = iospara) fvec(i, set_force_num)
if(iospara > 0) call read_error("Failure to read force vector component "//trim(adjustl(txtvec(i))), iospara)
end if
end do
end subroutine parse_set_force
subroutine parse_add_force(line)
character(len=*), intent(in) :: line
character(len=read_len) :: tmptxt, g, txtvec(3), extra_commands(20), msg
integer :: iospara, i, j
add_force_num = add_force_num + 1
read(line, *, iostat=iospara) tmptxt, g, (txtvec(i), i = 1, 3)
if(iospara > 0) call read_error("Failure to read add_force command", iospara)
!Get group number
add_force_group(add_force_num) = get_group_index(g)
if(add_force_group(add_force_num) == 0) then
call misc_error("Group "//trim(adjustl(g))// " in add_force command has not been defined. "// &
"Please define group before use")
end if
!Read force_vec and force_mask
do i =1, 3
call to_lower(txtvec(i))
if(.not.(txtvec(i) == 'null')) then
add_force_mask(i, add_force_num) = .true.
read(txtvec(i), *, iostat = iospara) add_f(i, add_force_num)
if(iospara > 0) call read_error("Failure to read force vector component "//trim(adjustl(txtvec(i))), iospara)
end if
end do
!Now get parse any extra optional commands
j = tok_count(line)-5
if( j > 0) then
read(line, *, iostat=iospara, iomsg=msg) (tmptxt, i = 1, 5), (extra_commands(i), i = 1, j)
i = 1
do while (i<=j)
select case(extra_commands(i))
case('every')
i = i+1
read(extra_commands(i), *, iostat=iospara, iomsg=msg) add_force_every(add_force_num)
if(iospara>0) call read_error(msg, iospara)
case('norm')
add_force_norm(add_force_num) = .true.
end select
i=i+1
end do
end if
end subroutine parse_add_force
subroutine add_force
integer :: i, ip, ia, ie, j
real(kind=wp) :: f(3)
logical :: do_now
do i = 1, add_force_num
!Check to see if we need to add force now
do_now = .false.
if(first_add(i)) then
do_now = .true.
first_add(i) = .false.
else if(add_force_every(i) == 0) then
do_now = .false.
else if (mod(iter, add_force_every(i)) == 0) then
do_now = .true.
end if
if (do_now) then
f = add_f(:,i)
!Check to see if the force is normalized, if so make sure the group contains only atoms
if(add_force_norm(i)) then
if(group_counts(2,add_force_group(i)) > 0) then
call command_error("Currently cannot normalize force when addforce is used on a group containing elements")
end if
if(group_counts(1, add_force_group(i)) >0) then
f(:) = f(:)/group_counts(1,add_force_group(i))
else
f=0.0_wp
end if
end if
do ia = 1, atom_num_l
if(btest(a_mask(ia), add_force_group(i))) then
do j = 1, 3
if (add_force_mask(j, i)) then
force_atom(j,ia) = force_atom(j,ia) + f(j)
end if
end do
end if
end do
do ip = 1, node_num_l
ie = node_cg(ip)
if(btest(e_mask(ie),add_force_group(i))) then
do j = 1, 3
if (add_force_mask(j,i)) then
force_eq(j,:, ip) = force_eq(j,:,ip) + f(j)
end if
end do
end if
end do
end if
end do
end subroutine
subroutine run_set_force
integer :: i, j, ia, ip, ie
do i = 1, set_force_num
do ia = 1, atom_num_l
if(btest(a_mask(ia), force_group(i))) then
do j = 1, 3
if (force_mask(j, i)) then
force_atom(j,ia) = fvec(j,i)
end if
end do
end if
end do
do ip = 1, node_num_l
ie = node_cg(ip)
if(btest(e_mask(ie),force_group(i))) then
do j = 1, 3
if (force_mask(j,i)) then
force_eq(j,:, ip) = fvec(j,i)
end if
end do
end if
end do
end do
end subroutine run_set_force
end module force_mod

330
src/forces.f90 Normal file
View file

@ -0,0 +1,330 @@
module forces
!This module is in charge of calculating energy, force, and virial
use parameters
use elements
use integration
implicit none
real(kind = wp) :: energy_sum, energy_atom_sum, energy_tally, f_norm_tally, energy_equiv_tally
!Atom arrays
real(kind = wp), allocatable :: energy_atom(:), force_atom(:,:), force_atom_pre(:,:), virial_atom(:,:,:)
!Element arrays
real(kind=wp), allocatable :: energy(:,:), energy_eq(:,:), &
force(:,:,:), force_eq(:,:,:), force_eq_pre(:,:,:), &
virial(:,:,:,:), virial_eq(:,:,:,:)
logical :: need_force_pre
public
contains
subroutine alloc_force_arrays
if(atom_num > 0) then
if( allocated(force_atom)) then
deallocate(energy_atom, force_atom, virial_atom, stat=allostat)
if (allostat > 0) call alloc_error("Failure to deallocate force_atom arrays", allostat)
end if
allocate(energy_atom(atom_num_l), force_atom(3, atom_num_l), &
virial_atom(3,3,atom_num_l), stat=allostat)
if (allostat > 0) call alloc_error("Failure to allocate force_atom arrays", allostat)
end if
if(ele_num> 0) then
if(allocated(force_eq)) then
deallocate(force_eq, energy_eq, virial_eq, stat=allostat)
if (allostat > 0) call alloc_error("Failure deallocating eq arrays", allostat)
end if
allocate(force_eq(3, max_basisnum, node_num_l), energy_eq(max_basisnum, node_num_l), &
virial_eq(3, 3, max_basisnum, node_num_l), stat = allostat)
if (allostat > 0) call alloc_error("Failure allocating init_force_arrays", allostat)
end if
end subroutine alloc_force_arrays
subroutine alloc_pre_array
if(atom_num > 0) then
allocate(force_atom_pre(3, atom_num_lr))
end if
if(ele_num>0) then
allocate(force_eq_pre(3, max_basisnum, node_num_lr))
end if
end subroutine alloc_pre_array
subroutine resize_force_arrays
!Resize the force arrays to match the node number
real(kind=wp), allocatable :: force_pre_array(:,:,:), force_atom_array(:,:)
call alloc_force_arrays
!Force_eq_pre is the only array that we have to preserve data for because the other ones update prior to use
if(need_force_pre) then
if(ele_num > 0) then
allocate(force_pre_array(3,max_basisnum, node_num_l), stat = allostat)
if(allostat > 0) call alloc_error("Failure to allocate force_pre_array", allostat)
force_pre_array = force_eq_pre(:, :, 1:node_num_l)
call move_alloc(force_pre_array, force_eq_pre)
end if
if(atom_num > 0) then
allocate(force_atom_array(3,atom_num_l), stat = allostat)
if(allostat > 0) call alloc_error("Failure to allocate force_atom_array", allostat)
force_atom_array = force_atom_pre(:,1:atom_num_l)
call move_alloc(force_atom_array, force_atom_pre)
end if
end if
end subroutine resize_force_arrays
subroutine update_equiv
!This subroutine calculates the normalized nodal quantities (referred to here as the equivalent)
integer :: ie, ip, i, j, je, inod, jnod, ibasis, ind
real(kind = wp), allocatable :: force_array(:), force_buff(:), virial_array(:), virial_buff(:), &
energy_array(:), energy_buff(:)
!If we have more than one processor than we need to share all this information for all elements that share data
if(pro_num > 1) then
allocate(force_array(3*ng_max_node*ele_shared_num*max_basisnum), &
energy_array(ng_max_node*ele_shared_num*max_basisnum), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate force/energy_array in update_equiv", allostat)
force_array(:) = 0.0_wp
energy_array(:) = 0.0_wp
if(need_virial) then
allocate(virial_array(9*ng_max_node*ele_shared_num*max_basisnum), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate virial_array in update_equiv", allostat)
virial_array(:) = 0.0_wp
end if
!Assign the buffers for reducing to calculate the equivalent forces.
!The buffer will have empty space so the communications aren't completely
!efficient memory wise but it shouldn't cause very bad slowdowns.
do ie = 1, ele_num_l
je = ele_id_shared(ie)
if(je /= 0) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
!Calculate the current index and put the force, virial, and energy into the send arrays
ind = 3*ng_max_node*max_basisnum*(je-1) + 3*max_basisnum*(inod-1) + 3*(ibasis-1)
do i = 1, 3
if(ind + i > 3*ng_max_node*ele_shared_num*max_basisnum) then
print *, 'Error: Index of force_array', &
ind+i, ' is larger than', &
' array size', 3*ng_max_node*ele_shared_num*max_basisnum
call mpi_abort(mpi_comm_world, 1, ierr)
end if
force_array(ind+i) = force(i, ibasis, ip)
if(need_virial) then
do j = 1, 3
if(3*ind + 3*(i-1)+j > 9*ng_max_node*ele_shared_num*max_basisnum) then
print *, 'Error: Index of virial_array', &
3*ind + 3*(i-1)+j, ' is larger than', &
' array size', 9*ng_max_node*ele_shared_num
call mpi_abort(mpi_comm_world, 1, ierr)
end if
virial_array(3*ind+3*(i-1)+j) = virial(j, i, ibasis, ip)
end do
end if
end do
if(ng_max_node*max_basisnum*(je-1)+max_basisnum*(inod-1)+ibasis &
> ng_max_node*ele_shared_num*max_basisnum) then
print *, 'Error: Index of energy_array', &
ng_max_node*(je-1)+max_basisnum*(inod-1)+ibasis, ' is larger than', &
' array size', ng_max_node*ele_shared_num*max_basisnum
call mpi_abort(mpi_comm_world, 1, ierr)
end if
energy_array(ng_max_node*max_basisnum*(je-1)+max_basisnum*(inod-1)+ibasis) = energy(ibasis, ip)
end do
end do
end if
end do
!allocate force_buff, virial_buff, and energy_buff
allocate(force_buff(3*ng_max_node*ele_shared_num*max_basisnum), &
energy_buff(ng_max_node*ele_shared_num*max_basisnum), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate force/energy_buff in update_equiv", allostat)
!Reduce all force_buff, energy_buff, and virial_buff
force_buff(:) = 0.0_wp
call mpi_allreduce(force_array, force_buff, 3*ng_max_node*ele_shared_num*max_basisnum, &
mpi_wp, mpi_sum, world, ierr)
energy_buff(:) = 0.0_wp
call mpi_allreduce(energy_array, energy_buff, ng_max_node*ele_shared_num*max_basisnum, &
mpi_wp, mpi_sum, world, ierr)
if(need_virial) then
allocate(virial_buff(9*ng_max_node*ele_shared_num*max_basisnum), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate virial_buff in update_equiv", allostat)
virial_buff(:) = 0.0_wp
call mpi_allreduce(virial_array, virial_buff, 9*ng_max_node*ele_shared_num*max_basisnum, &
mpi_wp, mpi_sum, world, ierr)
end if
!update local energy_eq, force_eq, virial_eq from the buff received from all processors
do ie = 1, ele_num_l
je = ele_id_shared(ie)
if(je /= 0) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
ind = 3*ng_max_node * max_basisnum * (je-1) + 3*(inod-1)*max_basisnum + 3*(ibasis-1)
do i = 1, 3
force(i, ibasis, ip) = force_buff(ind+i)
if(need_virial) then
do j = 1, 3
virial(j, i, ibasis, ip) = virial_buff(3*ind + 3*(i-1)+j)
end do
end if
end do
energy(ibasis, ip) = energy_buff(ng_max_node*max_basisnum*(je-1)+max_basisnum*(inod-1) + ibasis)
end do
end do
end if
end do
end if
!Reset values for eq arrays
force_eq(:, :, :) = 0.0_wp
if(need_virial) then
virial_eq(:, :, :, :) = 0.0_wp
end if
energy_eq(:, :) = 0.0_wp
do ie = 1, ele_num_l
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
select case(mass_mat_ty)
case('lumped')
!Calculate equivalent values by dividing by the lumped mass matrix
do ibasis = 1, basis_num(ie)
force_eq(:,ibasis, ip) = force(:,ibasis, ip)/mass_mat_coeff(size_ele(ie), etype(ie))
if(need_virial) then
virial_eq(:, :, ibasis, ip) = virial(:, :, ibasis, ip) / mass_mat_coeff(size_ele(ie), etype(ie))
end if
energy_eq(ibasis, ip) = energy(ibasis, ip)/mass_mat_coeff(size_ele(ie), etype(ie))
end do
case('consistent')
!Use a consistent mass matrix
do jnod = 1, ng_node(etype(ie))
do ibasis = 1, basis_num(ie)
force_eq(:, ibasis, ip) = force(:, ibasis, ip) * mass_mat_inv(inod, jnod) &
/ mass_mat_coeff(size_ele(ie),etype(ie))
if(need_virial) then
virial_eq(:, :, ibasis, ip) = virial(:, :, ibasis, ip) * mass_mat_inv(inod, jnod) &
/ mass_mat_coeff(size_ele(ie),etype(ie))
end if
energy_eq(ibasis, ip) = energy(ibasis, ip) * mass_mat_inv(inod, jnod) &
/ mass_mat_coeff(size_ele(ie), etype(ie))
end do
end do
case default
print *, 'Error: Mass matrix type ', mass_mat_ty, ' is not accepted'
call mpi_abort(mpi_comm_world, 1, ierr)
end select
end do
end do
!Deallocate arrays we no longer need
deallocate(force, energy, stat = deallostat)
if(need_virial) then
deallocate(virial, stat = deallostat)
end if
if(deallostat /= 0) call alloc_error("Failure to deallocate force/energy/virial", deallostat)
return
end subroutine update_equiv
subroutine communicate_force_eq
!This subroutine communicates forces for all shared elements when needed (such as when using langevin dynamics
integer :: ie, ip, i, je, inod, ibasis, ind
real(kind = wp), allocatable :: force_array(:), force_buff(:)
!If we have more than one processor than we need to share all this information for all elements that share data
if(pro_num > 1) then
allocate(force_array(3*ng_max_node*ele_shared_num*max_basisnum), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate force in communicate_force_eq", allostat)
force_array(:) = 0.0_wp
!Assign the buffers for reducing to calculate the equivalent forces.
!The buffer will have empty space so the communications aren't completely
!efficient memory wise but it shouldn't cause very bad slowdowns.
do ie = 1, ele_num_l
je = ele_id_shared(ie)
if(je /= 0) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
!Calculate the current index and put the force, virial, and energy into the send arrays
ind = 3*ng_max_node*max_basisnum*(je-1) + 3*max_basisnum*(inod-1) + 3*(ibasis-1)
do i = 1, 3
if(ind + i > 3*ng_max_node*ele_shared_num*max_basisnum) then
print *, 'Error: Index of force_array', &
ind+i, ' is larger than', &
' array size', 3*ng_max_node*ele_shared_num*max_basisnum
call mpi_abort(mpi_comm_world, 1, ierr)
end if
force_array(ind+i) = force_eq(i, ibasis, ip)
end do
end do
end do
end if
end do
!allocate force_buff, virial_buff, and energy_buff
allocate(force_buff(3*ng_max_node*ele_shared_num*max_basisnum), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate force_buff in update_equiv", allostat)
!Reduce all force_buff, energy_buff, and virial_buff
force_buff(:) = 0.0_wp
call mpi_allreduce(force_array, force_buff, 3*ng_max_node*ele_shared_num*max_basisnum, &
mpi_wp, mpi_sum, world, ierr)
!update local energy_eq, force_eq, virial_eq from the buff received from all processors
do ie = 1, ele_num_l
je = ele_id_shared(ie)
if(je /= 0) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
ind = 3*ng_max_node * max_basisnum * (je-1) + 3*(inod-1)*max_basisnum + 3*(ibasis-1)
do i = 1, 3
force_eq(i, ibasis, ip) = force_buff(ind+i)
end do
end do
end do
end if
end do
end if
return
end subroutine communicate_force_eq
subroutine reset_tallies
!Reset energy and force tallies
energy_tally = 0
energy_equiv_tally = 0
f_norm_tally = 0
end subroutine
end module forces

402
src/group.f90 Normal file
View file

@ -0,0 +1,402 @@
module group
use parameters
use elements
use box
use str
use comms
implicit none
integer, parameter :: max_group_num = 20
integer, save :: group_num, group_counts(4,max_group_num)
integer, dimension(10, max_group_num), save :: group_parameters_int
real(kind=wp), dimension(10, max_group_num) :: group_parameters_real
character(len=50), dimension(10, max_group_num) :: group_parameters_string
character(len=read_len), dimension(max_group_num) :: group_name, group_string, group_shape, which_in_group
public
contains
subroutine group_defaults
group_parameters_int = 0
group_parameters_real=0.0_wp
group_counts = 0
group_name(1) = 'all'
group_num = 1
end subroutine group_defaults
subroutine init_group_all
!This initializes the all group
integer :: i, group_countme(4)
group_countme = 0
if(ele_num > 0) e_mask=0
if(atom_num > 0) a_mask=0
group_countme(1) = atom_num_l
group_countme(2) = ele_num_l
do i = 1, atom_num_l
a_mask(i)= ibset(a_mask(i), 1)
end do
do i = 1, ele_num_l
e_mask(i)= ibset(e_mask(i), 1)
group_countme(3) = group_countme(3) + ng_node(etype(i))*basis_num(i)
group_countme(4) = group_countme(4) + basis_num(i)*(size_ele(i)+1)**3
end do
call mpi_allreduce(group_countme, group_counts(:,1), 4, mpi_integer, mpi_sum, world, ierr)
end subroutine init_group_all
function get_group_index(gname)
!This subroutine returns the index of a group
character(len=*), intent(in) :: gname
integer :: get_group_index
integer :: i
get_group_index = 0
do i = 1, group_num
if (trim(group_name(i)) == trim(gname)) then
get_group_index=i
exit
end if
end do
return
end function get_group_index
subroutine parse_group(line)
!This subroutine parses the group command
character(len=*), intent(in) :: line
integer :: iospara, i, j, shape_args
character(len=read_len) :: err, tmptxt
!First figure out what group_shape
group_num = group_num + 1
j = tok_count(line)
read(line, *, iomsg=err, iostat=iospara) tmptxt, group_name(group_num), which_in_group(group_num), group_shape(group_num),&
(group_parameters_string(i,group_num), i = 1, j)
if(iospara > 0) call read_error(err, iospara)
!Check to make sure which_in_group is correct
select case(which_in_group(group_num))
case('elements', 'atoms', 'all')
continue
case default
call command_error("Group command doesn't accept selection based on "//trim(adjustl(which_in_group(group_num))) &
//" should be elements, atoms, or all")
end select
!Now make sure that the group_shape is one of the acceptable options and check the inputted parameters
select case(group_shape(group_num))
case('block', 'sphere', 'type', 'file')
continue
case default
call command_error("Group command doesn't accept shape "//trim(adjustl(group_shape(group_num))) &
//" should be block, sphere, or type")
end select
!Now parse the group parameters
select case(group_shape(group_num))
case('block')
do i =1, 6
call parse_pos(int((i+1)/2), group_parameters_string(i, group_num), group_parameters_real(i, group_num))
end do
shape_args = 6
case('sphere')
do i =1, 3
call parse_pos(i, group_parameters_string(i, group_num), group_parameters_real(i, group_num))
end do
!Now get the radius
read(group_parameters_string(4, group_num), *, iostat = iospara, iomsg = err) group_parameters_real(i, group_num)
if(iospara > 0) call read_error(err, iospara)
group_parameters_real(4, group_num) = group_parameters_real(4, group_num) * group_parameters_real(4, group_num)
shape_args=4
case('type')
read(group_parameters_string(1,group_num), *, iostat = iospara, iomsg = err) group_parameters_int(1, group_num)
if(iospara > 0) call read_error(err, iospara)
shape_args = 1
case('file')
continue
end select
call assign_group(group_num)
end subroutine parse_group
subroutine assign_group(i)
!This subroutine finds all elements and/or atoms within the group boundaries
!specified by the user.
integer, intent(in) :: i
integer :: inod, ibasis, ia, ie, ip, group_countme(4), j, reada, reade, intag
integer, allocatable :: amap(:), emap(:)
real(kind=dp) :: rc(3)
character(len = read_len) :: msg
group_countme = 0
select case(trim(adjustl(group_shape(i))))
case('block')
write(msg, *) "Group ", trim(group_name(i)), " has block shape with boundaries: ", group_parameters_real(1:6, i)
case('sphere')
write(msg, *) "Group ", trim(group_name(i)), " has sphere shape with centroid ", group_parameters_real(1:3, i), &
" and radius ", group_parameters_real(4, i)
case('type')
write(msg, *) "Group ", trim(group_name(i)), " selects all atoms of type ", group_parameters_int(1,i)
case('file')
write(msg, *) "Group ", trim(group_name(i)), " is read in from file ", trim(adjustl(group_parameters_string(1,i)))
end select
call log_msg(msg)
!Assign atom groups
if(trim(adjustl(group_shape(i))) == "file") then
!First make a map for the tags to position in the array
open(unit=11, file=trim(adjustl(group_parameters_string(1,i))), status='old', action='read', position='rewind')
!First read in number of atoms/elements to read
read(11,*) reada, reade
!Now first read the atoms
if(reada > 0) then
!If we are actually interested
if( (trim(adjustl(which_in_group(i))) == 'atoms').or. (trim(adjustl(which_in_group(i))) == 'all'))then
allocate(amap(maxval(tag_atom)))
amap=0
!First create the tag to index map
do j = 1, atom_num_l
amap(tag_atom(j)) = j
end do
!Now read in the data and set the group mask
do j = 1, reada
read(11,*) intag
if(intag <= size(amap)) then
if(amap(intag) > 0) then
a_mask(amap(intag)) = ibset(a_mask(amap(intag)),i)
group_countme(1) = group_countme(1) + 1
end if
end if
end do
else
!Otherwise just read until you are done with the atom reading
do j = 1, reada
read(11,*) intag
end do
end if
end if
if(reade > 0) then
if( (trim(adjustl(which_in_group(i))) == 'elements').or. (trim(adjustl(which_in_group(i))) == 'all'))then
!First create the tag to index map
allocate(emap(maxval(tag_ele)))
emap =0
do j = 1, ele_num_l
emap(tag_ele(j)) = j
end do
!Now read in the data and set the group mask
do j=1, reade
read(11,*) intag
if(intag <= size(emap)) then
if(emap(intag) > 0) then
e_mask(emap(intag)) = ibset(e_mask(emap(intag)), i)
ie=emap(intag)
if(who_has_ele(ie)) then
group_countme(2) = group_countme(2) + 1
group_countme(3) = group_countme(3) + ng_node(etype(ie))*basis_num(ie)
group_countme(4) = group_countme(4) + basis_num(ie) * (size_ele(ie)+1)**3
end if
end if
end if
end do
end if
end if
close(11)
else
if((trim(adjustl(which_in_group(i))) == 'atoms').or. (trim(adjustl(which_in_group(i))) == 'all')) then
do ia = 1, atom_num_l
if( trim(adjustl(group_shape(i))) == 'type') then
if(type_atom(ia) == group_parameters_int(1,i)) then
a_mask(ia) = ibset(a_mask(ia), i)
group_countme(1) = group_countme(1) + 1
end if
else if (in_group(i, r_atom(:,ia))) then
a_mask(ia) = ibset(a_mask(ia), i)
group_countme(1) = group_countme(1) + 1
end if
end do
end if
!Assign element groups
if((trim(adjustl(which_in_group(i))) == 'elements').or. (trim(adjustl(which_in_group(i))) == 'all')) then
j=0
do ie = 1, ele_num_l
if( trim(adjustl(group_shape(i))) == 'type') then
if(any(basis_type(:,ie) == group_parameters_int(1,i))) then
e_mask(ie) = ibset(e_mask(ie), i)
if(who_has_ele(ie)) then
group_countme(2) = group_countme(2) + 1
group_countme(3) = group_countme(3) + ng_node(etype(ie))*basis_num(ie)
group_countme(4) = group_countme(4) + basis_num(ie)*(size_ele(ie)+1)**3
end if
end if
else
!Otherwise get the element centroid
rc = 0.0_wp
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
rc = rc + r(:,ibasis, ip)
end do
end do
rc = rc/(ng_node(etype(ie))*basis_num(ie))
!If centroid is in group then we set the mask
if (in_group(i, rc)) then
e_mask(ie) = ibset(e_mask(ie), i)
if(who_has_ele(ie)) then
group_countme(2) = group_countme(2) + 1
group_countme(3) = group_countme(3) + ng_node(etype(ie))*basis_num(ie)
group_countme(4) = group_countme(4) + basis_num(ie)*(size_ele(ie)+1)**3
end if
end if
end if
end do
end if
end if
call mpi_allreduce(group_countme, group_counts(:,i), 4, mpi_integer, mpi_sum, world, ierr)
write(msg,*) "Group ", trim(group_name(i)), " with num ", i, " has ", group_counts(1,i), " atoms and ", &
group_counts(2,i), " elements"
call log_msg(msg)
end subroutine assign_group
pure function in_group(g, r)
!Return true if in group
integer, intent(in) :: g
real(kind=wp), intent(in) :: r(3)
real(kind=wp) :: rsq, rdiff(3)
logical :: in_group
in_group = .false.
select case(trim(adjustl(group_shape(g))))
case('block')
in_group = in_block_bd(r(:), group_parameters_real(1:6, g))
case('sphere')
rdiff = r-group_parameters_real(1:3, g)
rsq = rdiff(1)*rdiff(1) + rdiff(2)*rdiff(2) + rdiff(3)*rdiff(3)
if (rsq < group_parameters_real(4, g)) in_group = .true.
end select
end function in_group
subroutine write_group(line)
!This subroutine writes out the group information to a file named after the group
character(len=*),intent(in) :: line
character(len=read_len) :: group_name, txtholder
integer :: g, at, el, a, e, i, counts_atom(pro_num), displs_atom(pro_num), counts_ele(pro_num), displs_ele(pro_num)
integer, allocatable :: gatoms(:), gelements(:), gatoms_gather(:), gelements_gather(:)
read(line, *) txtholder, group_name
g=get_group_index(group_name)
!Now allocate the variables which will contain the tags
at=group_counts(1,g)
el=group_counts(2,g)
allocate(gatoms(at), gelements(el))
!Allocate gather variables on root
if(rank==root) then
allocate(gatoms_gather(at), gelements_gather(el))
else
allocate(gatoms_gather(1), gelements_gather(1))
end if
!Now add the atoms and element tags within the group to the list
if(atom_num > 0) then
a=0
do i = 1, atom_num_l
if (btest(a_mask(i), g)) then
a=a+1
gatoms(a) = tag_atom(i)
end if
end do
end if
if(ele_num > 0) then
e=0
do i = 1, ele_num_l
if(who_has_ele(i)) then
if(btest(e_mask(i), g)) then
e=e+1
gelements(e) = tag_ele(i)
end if
end if
end do
end if
!First gather counts and displacements for atoms
if(atom_num > 0 ) then
displs_atom=0
counts_atom=0
call mpi_gather(a, 1, mpi_integer, counts_atom, 1, mpi_integer, root, world, ierr)
if(rank==root) then
displs_atom=0
do i =2, pro_num
displs_atom(i)=displs_atom(i-1)+counts_atom(i-1)
end do
if(at /= sum(counts_atom)) call misc_error("Sum of counts doesn't match atoms in group in write_group")
end if
end if
!Now on root gather counts and displacements for elements
if(ele_num > 0) then
counts_ele=0
displs_ele=0
call mpi_gather(e, 1, mpi_integer, counts_ele, 1, mpi_integer, root, world, ierr)
if(rank==root) then
displs_ele=0
do i =2, pro_num
displs_ele(i)=displs_ele(i-1)+counts_ele(i-1)
end do
if(el /= sum(counts_ele)) call misc_error("Sum of counts doesn't match eles in group in write_group")
end if
end if
!Now gatherv the tags to root
call mpi_gatherv(gatoms(1:a), a, mpi_integer, gatoms_gather, counts_atom, displs_atom, mpi_integer, root, world, ierr)
call mpi_gatherv(gelements(1:e), e, mpi_integer, gelements_gather, &
counts_ele, displs_ele, mpi_integer, root, world, ierr)
!Now write out the data if you are root
if(rank==root) then
open(unit=11, file=trim(adjustl(group_name))//"_tags.dat", action="write", status='replace', position='rewind')
write(11,*) at, el
if(atom_num > 0) then
do i=1, at
write(11, *) gatoms_gather(i)
end do
end if
if(ele_num > 0) then
do i=1, el
write(11, *) gelements_gather(i)
end do
end if
close(11)
end if
end subroutine write_group
end module group

280
src/input_parser.f90 Normal file
View file

@ -0,0 +1,280 @@
module input_parser
!This code is in charge of reading the code and running functions that mattter
use mpi
use comms
use parameters
use potential
use eam
use read_data
use neighbors
use logger
use dump
use group
use minimize
use force_mod
use displace
use set
use deform
use modify
use langevin
implicit none
logical, private :: initialized
public
contains
subroutine read_input
!Actually read the input, every processor should have access to the input arguments
!so we should be able to loop over these arguments
character(len=read_len) :: line, label, req_commands(2), msg, loop_commands(100)
integer :: iosline, iospara, i, loop_times, nloopcom, nloop, j
logical :: req_flags(2), first_flag(2), read_loop_flag, run_loop_flag
initialized = .false.
req_flags(:) = .false.
read_loop_flag = .false.
run_loop_flag = .false.
nloopcom = 0
first_flag = .true.
req_commands(1) = 'read'
req_commands(2) = 'potential'
iosline = 0
do while (iosline == 0)
if(run_loop_flag) then
if(j > nloopcom) then
j = 1
nloop = nloop + 1
if(nloop > loop_times) then
run_loop_flag = .false.
nloopcom = 0
cycle
end if
end if
line = loop_commands(j)
j = j+1
else
if(rank == root) then
line=''
read(*, '(a)', iostat = iosline) line
if(iosline > 0) then
print *, 'Error: Wrong reading input file line ', line, &
' because of', iosline
call mpi_abort(mpi_comm_world, 1, ierr)
end if
!scan(line, '#' /= 1) is used to skip all lines starting with #
if((scan(line, '#') /= 1).and.(line /= '')) then
!This communication needs to be done over mpi_comm_world to make sure every processor
!is running the commands
call mpi_bcast(line, read_len, mpi_character, root, mpi_comm_world, ierr)
else
cycle
end if
else
call mpi_bcast(line, read_len, mpi_character, root, mpi_comm_world, ierr)
!Check for the exit input loop code
if (line == "exit input loop") exit
end if
end if
!Get the command
read(line, *, iostat = iospara) label
if(iospara > 0) then
print *, 'Error: Wrong reading input file label ', label, &
' because of', iospara
call mpi_abort(mpi_comm_world, 1, ierr)
end if
!Log the command
call log_msg(line,0)
!run the command
if(read_loop_flag) then
if(label == 'endloop') then
read_loop_flag = .false.
run_loop_flag= .true.
nloop = 1
j = 1
else
nloopcom = nloopcom+1
loop_commands(nloopcom) = trim(adjustl(line))
end if
else
select case(label)
case('loop')
!Get the number of times to loop
read(line, *) label, loop_times
read_loop_flag = .true.
nloopcom = 0
case('read_data')
call parse_read(line)
initialized=.false.
need_vel=.false.
need_force_pre=.false.
req_flags(1) = .true.
case('potential')
call parse_potential(line)
req_flags(2) = .true.
case('neighbor')
call parse_neighbors(line)
case('write_out')
call parse_write_out(line)
case('dump')
call parse_dump(line)
case('undump')
call parse_undump(line)
case('timestep')
call parse_timestep(line)
case('modify')
call parse_modify(line)
case('group')
call parse_group(line)
case('mass')
call parse_mass(line)
case('debug')
call parse_debug(line)
case('langevin')
call parse_langevin(line)
case('setforce')
if(first_flag(1)) then
call init_set_force
first_flag(1) = .false.
end if
call parse_set_force(line)
case('addforce')
if(first_flag(2)) then
call init_add_force
first_flag(2) = .false.
end if
call parse_add_force(line)
case('displace')
call displace_points(line)
case('ramp')
call ramp_displace(line)
case('boundary')
call parse_boundary(line)
!Now regenerate the grid comm
call mpi_cart_create(world, 3, num_pro, period, .false., grid_comm, ierr)
call mpi_comm_rank(grid_comm, grank, ierr)
call mpi_cart_coords(grid_comm, grank, 3, grid_coords, ierr)
case('thermo')
call parse_thermo(line)
case('types')
call parse_types(line)
case('min_style')
call parse_min_style(line)
case('minimize')
select case(min_style)
case(1)
!If we are using fire than we need all of the info
call pre_calc(2)
case(2)
!If we are using cg than we only need the virial
call pre_calc(3)
end select
call parse_minimize(line)
case('run')
do i=1, 2
if(.not.req_flags(i)) then
print *, "Error: must call ", req_commands(i), " before run command"
call mpi_abort(mpi_comm_world, 1, ierr)
end if
end do
!Prepare for calculation
call pre_calc(1)
call parse_run(line)
case('dynamics')
call parse_dynamics(line)
case('set')
call parse_set(line)
case('temp')
call parse_temp(line)
case('press')
call parse_berendsen(line)
case('unpress')
pflag=.false.
case('deform')
call parse_deform(line)
case('thermo_style')
call parse_thermo_style(line)
case('write_group')
call write_group(line)
case default
write(msg, *) 'Input parameter label ', trim(adjustl(label)), &
' in line ', trim(adjustl(line)), ' is not accepted'
call misc_error(msg)
end select
end if
end do
!Because root will be the only processor that has access to stdin we have to broadcast a code to all the
!other processors for them to exit the input loop
if(rank == root) then
line = "exit input loop"
call mpi_bcast(line, read_len, mpi_character, root, mpi_comm_world, ierr)
end if
return
end subroutine read_input
subroutine pre_calc(runtype)
!This is the code which distributes the model and builds neighbor lists before calculation
!Runtype dictates what the command is being called for calculation. The option for runtype changes how flags are set
integer, intent(in) :: runtype
!Set flags and allocate variables
need_virial = .true.
select case(runtype)
case(1)
if(.not.need_vel) then
need_vel = .true.
call alloc_velocity_arrays
end if
if(.not.need_force_pre) then
need_force_pre = .true.
call alloc_pre_array
end if
case(2)
if(.not.need_vel) then
need_vel = .true.
call alloc_velocity_arrays
end if
if(need_force_pre) then
need_force_pre=.false.
if(atom_num > 0) deallocate(force_atom_pre)
if(ele_num>0) deallocate(force_eq_pre)
end if
case(3)
if(need_vel) then
need_vel = .false.
call dealloc_velocity_arrays
end if
end select
if (.not. initialized) then
!Set up the potential map
!Allocate necessary force and potential arrays
call alloc_force_arrays
call alloc_potential_arrays
initialized = .true.
end if
end subroutine pre_calc
end module input_parser

467
src/integration.f90 Normal file
View file

@ -0,0 +1,467 @@
module integration
!This module contains the code required for integration point setup and virtual atom data
use parameters
use comms
use elements
use errors
implicit none
integer :: max_intpo_num, intpo_num, intpo_num_l
integer, save :: atomap_max_ele, etype_count, etype_to_itype(defined_element_types), &
intpo_nums(4, defined_element_types), intpo_count(defined_element_types)
integer, allocatable, save :: atom_intpo(:,:,:), who_rep_atomap(:,:,:)
real(kind = wp), allocatable, save :: weight_intpo(:,:,:)
real(kind = wp), allocatable, save :: mass_mat_inv(:,:)
character(len = 100) :: mass_mat_ty
logical, allocatable, save :: who_has_intpo(:,:)
!Itype is the integration array type for the elements
integer, allocatable, save :: itype(:)
public
contains
subroutine integration_defaults
mass_mat_ty = 'lumped'
end subroutine integration_defaults
subroutine init_integration
!Initialize the integration
integer :: i, j, ie, iep, local_iep(2), global_iep(2)
real(kind=wp) :: weight_temp
real(kind=wp) :: v_inv_matrix(8**2)
!First set up the mass arrays
!Allocate inverse mass arays
allocate(mass_mat_inv(8,8))
!Now calculate the inverse mass matrix, this is just for FCC 8 node elements
v_inv_matrix = [ 8.0_wp, -4.0_wp, 2.0_wp, -4.0_wp, -4.0_wp, 2.0_wp, -1.0_wp, 2.0_wp, &
-4.0_wp, 8.0_wp, -4.0_wp, 2.0_wp, 2.0_wp, -4.0_wp, 2.0_wp, -1.0_wp, &
2.0_wp, -4.0_wp, 8.0_wp, -4.0_wp, -1.0_wp, 2.0_wp, -4.0_wp, 2.0_wp, &
-4.0_wp, 2.0_wp, -4.0_wp, 8.0_wp, 2.0_wp, -1.0_wp, 2.0_wp, -4.0_wp, &
-4.0_wp, 2.0_wp, -1.0_wp, 2.0_wp, 8.0_wp, -4.0_wp, 2.0_wp, -4.0_wp, &
2.0_wp, -4.0_wp, 2.0_wp, -1.0_wp, -4.0_wp, 8.0_wp, -4.0_wp, 2.0_wp, &
-1.0_wp, 2.0_wp, -4.0_wp, 2.0_wp, 2.0_wp, -4.0_wp, 8.0_wp, -4.0_wp, &
2.0_wp, -1.0_wp, 2.0_wp, -4.0_wp, -4.0_wp, 2.0_wp, -4.0_wp, 8.0_wp ]
mass_mat_inv(:,:) = reshape(v_inv_matrix, [8,8])
!First communicate the etypes present, this array is only on the root processor as it's defined in read_restart
call mpi_bcast(etype_present, defined_element_types, mpi_logical, root, world, ierr)
!Assign the etype_to_itype array, this just maps the etype to the correct index of the itype variables
!This is because generally all the element types won't be used in one simulation to save on memory
j = 0
intpo_nums(:,:) = 0
intpo_count(:) = 0
etype_to_itype(:) = 0
do i = 1, defined_element_types
if (etype_present(i)) then
j=j+1
etype_to_itype(i) = j
select case(i)
!Now set up the integration point definitions for FCC elements (Will be expanded to other element types
!Intpo_nums has the integration point count for the integration schemes
!The first index si the corner integration points, the second is the number of edge integration points,
!the third is the surface integration points, and the 4th is the interior integration points
case(1)
!1NN element integration point count
intpo_nums(:,j) = (/ 8, 12, 6, 1 /)
intpo_count(j) = 27
case(2,3)
!2NN element integration point count
intpo_nums(:,j) = (/ 64, 48, 12, 1 /)
intpo_count(j) = 125
end select
end if
end do
!Calculate max number of intpo nums for elements needed for model
max_intpo_num = maxval(intpo_count)
!Allocate arrays
etype_count = count(etype_present)
allocate(who_rep_atomap((max_size+1)**3, unique_sizes, etype_count), &
weight_intpo(max_intpo_num, unique_sizes, etype_count), &
atom_intpo(max_intpo_num, unique_sizes, etype_count), &
stat = allostat)
if(allostat > 0) call alloc_error("Failure allocating weight arrays in init_integration", allostat)
!Now initialize the integration points for all of the elements that we need
!At the moment this code creates the integration points for all possible combinations of esize and etype
if(etype_present(1)) call init_rhomb(1)
if (etype_present(2))call init_rhomb(2)
if (etype_present(3)) call init_rhomb(2, 3)
!Now update the itypes for all the elements
call update_itype
!Now double check to make sure all of the weights for all elements add up correctly and count the number of integration
!points
local_iep = 0
global_iep = 0
do ie = 1, ele_num_l
weight_temp = 0.0_wp
do iep = 1, intpo_count(itype(ie))
weight_temp = weight_temp + weight_intpo(iep, size_to_shape(size_ele(ie)), itype(ie))
end do
if(.not. is_equal(weight_temp, real((size_ele(ie)+1)**3,wp))) then
print *, "Error: weight_temp for element ", ie, " should equal ", (size_ele(ie)+1)**3
call mpi_abort(mpi_comm_world, 1, ierr)
end if
if(who_has_ele(ie)) then
local_iep(1) = local_iep(1) + intpo_count(itype(ie))
local_iep(2) = local_iep(2) + intpo_count(itype(ie))*basis_num(ie)
end if
end do
!Sum to get total intpo_num
call mpi_allreduce(local_iep, global_iep, 2, mpi_integer, mpi_sum, world, ierr)
!Now multiply by the maximum basis_num to get the right dimensions for the arrays
intpo_num = global_iep(2)
!Now update integration point arrays
call update_intpo
end subroutine init_integration
subroutine init_rhomb(intpo_depth, etyp)
!Initialize one NN rhombohedral elements
!intpo_depth is the integration point depth. The only difference between 1NN and 2NN elements is this integration point
!depth
integer, intent(in) :: intpo_depth
integer, intent(in), optional :: etyp
integer :: i, j, k, ide, ix, iy, iz, ie, iep, esize, et, interp_count, intpo_set_num, iatom, &
i_edge, i_node, i_surf, i_inner
real(kind=wp) :: node_weight, edge_weight, surf_rect_weight, inner_weight, x, y, z
!Variables needed for primitive unit cell definitions
real(kind=wp) :: weight_temp, cube_temp(24), cubic_mat(3,8), prim_cell(3,max_basisnum, ng_max_node)
!We need a pb_in variable for the interp function even though the pb doesn't matter
integer ::pb_in(3, max_basisnum, ng_max_node)
!arrays needed for assigning integration points to virtual atoms
integer :: pos_to_iatom(max_size+1, max_size+1, max_size+1), atom_rep_iatom((max_size+1)**3)
real(kind=wp) :: all_atom_set((max_size+1)), r_in_nat(3, (max_size+1)**3), rtemp(3,max_basisnum)
character :: msg
!etype denotes the type of the element in the element type definition array. For the rhombohedral elements
!it's the same as the intpo_depth
if (present(etyp)) then
et=etype_to_itype(etyp)
else
et=etype_to_itype(intpo_depth)
end if
!Initialize the matrix of node positions in natural coordinates
cube_temp(:) = [ -1.0_wp, -1.0_wp, -1.0_wp, &
1.0_wp, -1.0_wp, -1.0_wp, &
1.0_wp, 1.0_wp, -1.0_wp, &
-1.0_wp, 1.0_wp, -1.0_wp, &
-1.0_wp, -1.0_wp, 1.0_wp, &
1.0_wp, -1.0_wp, 1.0_wp, &
1.0_wp, 1.0_wp, 1.0_wp, &
-1.0_wp, 1.0_wp, 1.0_wp ]
prim_cell(:,1, 1:8) = reshape(cube_temp, [ 3, 8])
pb_in(:,:,:) = 0
do ie = 1, unique_sizes
esize = shape_sizes(ie)
node_weight = 1.0_wp
edge_weight = esize + 1 - 2*intpo_depth
surf_rect_weight = (esize + 1 - 2*intpo_depth)**2.0_wp
inner_weight = (esize + 1 - 2 * intpo_depth) **3.0_wp
if(rank == root) then
weight_temp = node_weight*intpo_nums(1, et) &
+ edge_weight*intpo_nums(2, et) &
+ surf_rect_weight*intpo_nums(3, et) &
+ inner_weight*intpo_nums(4, et)
if(.not. is_equal(weight_temp, real((esize+1)**3, wp))) then
print *, "Error: Wrong total weight of integration points: ", weight_temp, ' for element type ', et, &
" with esize ", esize, " should be ", (esize+1)**3
call mpi_abort(mpi_comm_world, 1, ierr)
end if
end if
!Now figure out which iatoms are at integration point sites
!Elements are defined from -esize/2 to esize/2 centered around 0. Here i has the number of interpolated atoms that are either
! greater than or less than 0. So for esize=4, the atoms are at -2 -1 0 1 2 and i will be 2
all_atom_set(:) = 0
if(mod(esize,2) == 0) then
i = esize/2
do ide =1, i
all_atom_set(2*ide) = -1.0_wp + 2.0_wp*(ide-1)/real(esize,wp)
all_atom_set(2*ide+1) = 1.0_wp - 2.0_wp*(ide-1)/real(esize,wp)
end do
else
i = (esize)/2
all_atom_set(1)= 1/real(esize,wp)
all_atom_set(esize+1)= -1/real(esize,wp)
do ide =1, i
all_atom_set(2*ide) = -1.0_wp + 2.0_wp*(ide-1)/real(esize,wp)
all_atom_set(2*ide+1) = 1.0_wp - 2.0_wp*(ide-1)/real(esize,wp)
end do
end if
!all_atom set contains only the positions which will have integration points.
!As an example, for esize =4 and intpo_depth=1 all_atom_set is (0, -2, 2). All integration points
!positions can be created from those 3 values in natural coordinates
!First get the position of the interpolated atoms in the natural coordinates and find the relevant position in
!all_atom_set
interp_count = (esize+1)**3
do iatom = 1, interp_count
call interp_atom(iatom, esize, 1, pb_in, 1, prim_cell, rtemp)
r_in_nat(:,iatom) = rtemp(:,1)
end do
!Now map the all_atom_set positions to iatoms
pos_to_iatom(:,:,:) = 0
do k = 1, esize+1
do j = 1, esize+1
do i = 1, esize +1
x = all_atom_set(i)
y = all_atom_set(j)
z = all_atom_set(k)
do iatom = 1, interp_count
if( is_equal(r_in_nat(1,iatom), real(x,wp)).and. &
is_equal(r_in_nat(2,iatom), real(y,wp)).and. &
is_equal(r_in_nat(3,iatom), real(z,wp))) then
pos_to_iatom(i,j,k) = iatom
exit
end if
end do
end do
end do
end do
intpo_set_num = 2*intpo_depth + 1
!Now loop over all integration point
iep=0
i_node = 0
i_edge = 0
i_surf = 0
i_inner= 0
atom_rep_iatom=0
do iz = 1, esize+1
do iy = 1, esize+1
do ix = 1, esize + 1
!Get the position for pos_to_iatom array
iatom = pos_to_iatom(ix,iy,iz)
!These are node integration points
if(((ix > 1).and.(ix <= intpo_set_num)).and. &
((iy >1).and.(iy <= intpo_set_num)).and. &
((iz > 1).and.(iz <= intpo_set_num))) then
iep = iep + 1
atom_intpo(iep, ie, et) = iatom
weight_intpo(iep, ie, et) = node_weight
atom_rep_iatom(iatom) = iatom
i_node = i_node + 1
!These are edge integration points
else if (((ix == 1).and.((iy > 1).and.(iy<=intpo_set_num)).and.((iz > 1).and.(iz<=intpo_set_num))).or. &
((iy == 1).and.((ix > 1).and.(ix<=intpo_set_num)).and.((iz > 1).and.(iz<=intpo_set_num))).or. &
((iz == 1).and.((iy > 1).and.(iy<=intpo_set_num)).and.((ix > 1).and.(ix<=intpo_set_num)))) then
iep = iep+1
atom_intpo(iep, ie, et) = iatom
weight_intpo(iep, ie, et) = edge_weight
atom_rep_iatom(iatom) = iatom
i_edge = i_edge+1
!These are surface integration points
else if(((ix == 1).and.(iy == 1).and.((iz > 1).and.(iz<=intpo_set_num))).or. &
(((ix > 1).and.(ix<=intpo_set_num)).and.(iy == 1).and.(iz == 1)).or. &
((ix == 1).and.((iy > 1).and.(iy<=intpo_set_num)).and.(iz == 1))) then
iep = iep+1
atom_intpo(iep, ie, et) = iatom
weight_intpo(iep, ie, et) = surf_rect_weight
atom_rep_iatom(iatom) = iatom
i_surf = i_surf+1
else if((ix == 1).and.(iy == 1).and.(iz == 1)) then
!Interior integration point
iep = iep+1
atom_intpo(iep, ie, et) = iatom
weight_intpo(iep, ie, et) = inner_weight
atom_rep_iatom(iatom) = iatom
i_inner = i_inner + 1
!Now below are points which are represented by edge integration points
else if(((ix == 1).or.(ix > intpo_set_num)).and. &
(iy > 1).and.(iy <= intpo_set_num).and. &
(iz > 1).and. (iz <= intpo_set_num)) then
atom_rep_iatom(iatom) = pos_to_iatom(1, iy, iz)
i_edge = i_edge + 1
else if(((iy == 1).or.(iy > intpo_set_num)).and. &
(ix > 1).and. (ix <= intpo_set_num).and. &
(iz > 1).and. (iz <= intpo_set_num)) then
atom_rep_iatom(iatom) = pos_to_iatom(ix, 1, iz)
i_edge = i_edge + 1
else if(((iz == 1).or.(iz > intpo_set_num)).and. &
(ix > 1).and. (ix <= intpo_set_num).and. &
(iy > 1).and. (iy <= intpo_set_num)) then
atom_rep_iatom(iatom) = pos_to_iatom(ix, iy, 1)
i_edge = i_edge + 1
!Now below are points which are represented by surface integration points
else if(((ix == 1).or.(ix > intpo_set_num)).and. &
((iy == 1).or.(iy > intpo_set_num)).and. &
(iz > 1).and.(iz <= intpo_set_num)) then
atom_rep_iatom(iatom) = pos_to_iatom(1,1,iz)
i_surf = i_surf + 1
else if(((iy == 1).or.(iy > intpo_set_num)).and. &
((iz == 1).or.(iz > intpo_set_num)).and. &
(ix > 1).and. (ix <= intpo_set_num)) then
atom_rep_iatom(iatom) = pos_to_iatom(ix, 1, 1)
i_surf = i_surf + 1
else if(((ix == 1).or.(ix > intpo_set_num)).and. &
((iz == 1).or.(iz > intpo_set_num)).and. &
(iy > 1).and. (iy <= intpo_set_num)) then
atom_rep_iatom(iatom) = pos_to_iatom(1, iy, 1)
i_surf = i_surf + 1
!Otherwise it is represented by the interior integration point
else
atom_rep_iatom(iatom) = pos_to_iatom(1,1,1)
i_inner = i_inner + 1
end if
end do
end do
end do
!Now verify that all the counts match up
if ( (i_node+i_edge+i_surf+i_inner) /= interp_count) then
print *, "Error: total considered interpolated points should equal ", interp_count, &
" not ", (i_node+i_edge+i_surf+i_Inner), " in init_rhomb"
call mpi_abort(1, mpi_comm_world, ierr)
end if
!We now form the mapping array which dictates where in atom_intpo our representative integration point lies
who_rep_atomap = 0
do iatom = 1, interp_count
do iep = 1, intpo_count(et)
if (atom_rep_iatom(iatom) == atom_intpo(iep, ie, et)) then
who_rep_atomap(iatom,ie, et) = iep
end if
end do
if (atom_rep_iatom(iatom)==0) then
write(msg, *) "atom_rep_iatom should not be 0 for iatom", iatom, " and esize ", esize
call misc_error(msg)
end if
end do
end do
end subroutine init_rhomb
subroutine update_intpo
!This subroutine updates the ownership arrays for the integration points
integer :: ie, iep, j, iatom, iatomap, ibasis, intpo_num_sum
!First allocate who_has_intpo
if(allocated(who_has_intpo)) then
if (ele_num_l > size(who_has_intpo,2)) deallocate(who_has_intpo)
end if
if(.not.allocated(who_has_intpo)) allocate(who_has_intpo(max_intpo_num*max_basisnum, ele_num_l))
intpo_num_l = 0
if(atomap_num_l > size(atomap_to_intpo,2)) then
deallocate(atomap_to_intpo)
allocate(atomap_to_intpo(2,size(r_atomap,2)))
end if
atomap_to_intpo = 0
who_has_intpo(:, :) = .false.
do ie = 1, ele_num_l
j = size_to_shape(size_ele(ie))
do iep = 1, intpo_count(itype(ie))
iatom = atom_intpo(iep, j, itype(ie))
do ibasis = 1, basis_num(ie)
iatomap=cg_atomap(basis_num(ie)*(iatom-1) + ibasis, ie)
!If iatomap /=0 then it's our iatomap
if(iatomap /= 0) then
if(in_block_bd(r_atomap(:,iatomap), pro_bd)) then
intpo_num_l = intpo_num_l + 1
who_has_intpo(basis_num(ie)*(iep-1) + ibasis, ie) = .true.
atomap_to_intpo(1,iatomap) = basis_num(ie)*(iep-1) + ibasis
atomap_to_intpo(2,iatomap) = ie
else
print *, "Error: Atomap ", iatomap, " should belong to ", rank, " but doesn't"
print *, "Pos is ", r_atomap(:,iatomap), " and bd is ", pro_bd
call mpi_abort( mpi_comm_world, 1, ierr)
end if
end if
end do
end do
end do
!Now check to make sure we have the right number of intpo
call mpi_reduce(intpo_num_l, intpo_num_sum, 1, mpi_integer, mpi_sum, root, mpi_comm_world, ierr)
if(rank == root) then
if(intpo_num_sum /= intpo_num) then
print *, "Error: Total integration point number ", intpo_num_sum, " does not equal ", intpo_num
call mpi_abort(mpi_comm_world, 1, ierr)
end if
end if
end subroutine update_intpo
pure function mass_mat_coeff(esize, etype)
integer, intent(in) :: esize, etype
real(kind=wp) :: mass_mat_coeff
mass_mat_coeff = real(esize+1,wp)**3.0_wp/real(ng_node(etype),wp)
return
end function mass_mat_coeff
subroutine update_itype
!This just updates the itype array for the local element num
integer :: i
if (allocated(itype)) deallocate(itype)
allocate(itype(ele_num_l))
do i = 1, ele_num_l
itype(i) = etype_to_itype(etype(i))
end do
end subroutine
end module integration

119
src/langevin.f90 Normal file
View file

@ -0,0 +1,119 @@
module langevin
!This module contains all the code for running steps of the verlet algorithm
!This is equivalent to the NVE ensemble
use parameters
use comms
use elements
use forces
use time
use atom_types
use group
implicit none
!Arguments passed in by users
real(kind=wp), private, save :: Tb, damp, T0
real(kind=wp), private, save :: gfactor1(max_atom_types), gfactor2(max_atom_types)
integer, save :: langevin_group(20), lnum
public
contains
subroutine langevin_defaults
lnum = 0
langevin_group = 0
end subroutine langevin_defaults
subroutine parse_langevin(line)
!Parse the langevin thermostat command
character(len=*), intent(in) :: line
character(len=read_len) :: tmptxt, g
integer :: i
if(tok_count(line) < 5) call command_error("Missing arguments for dynamics langevin command")
read(line, *) tmptxt, g, T0, Tb, damp
!Get the group for this langevin call
lnum = lnum + 1
langevin_group(lnum) = get_group_index(g)
if(langevin_group(lnum) == 0) then
write(tmptxt,*) "Group name ", g, " not defined in langevin command"
call command_error(tmptxt)
end if
!Now get force prefactors for all atom types
do i = 1, natom_types
gfactor1(i) = -masses(i)/damp/ftm2v
gfactor2(i) = sqrt(masses(i)) * sqrt(24.0*boltzmann/damp/time_step/const_motion)/ftm2v
end do
return
end subroutine parse_langevin
subroutine langevin_post_force
integer :: i, ia, ie, ibasis, inod, ip
real(kind = wp) :: T_target, delta, tsqrt, fdrag(3), fran(3), rand, gam1, gam2
delta = (iter - begin_step)/(run_steps)
T_target = T0 + delta*(Tb-T0)
tsqrt=sqrt(T_target)
!update force with langevin for all atoms
!Loop over all langevin calls
do i = 1, lnum
do ia = 1, atom_num_l
if(btest(a_mask(ia), langevin_group(lnum))) then
gam1 = gfactor1(type_atom(i))
gam2 = gfactor2(type_atom(i))*tsqrt
call random_number(rand)
fran(1) = gam2*(rand-0.5)
call random_number(rand)
fran(2) = gam2*(rand-0.5)
call random_number(rand)
fran(3) = gam2*(rand-0.5)
fdrag(1) = gam1*vel_atom(1,ia)
fdrag(2) = gam1*vel_atom(2,ia)
fdrag(3) = gam1*vel_atom(3,ia)
force_atom(1,ia) = force_atom(1,ia) + fdrag(1) + fran(1)
force_atom(2,ia) = force_atom(2,ia) + fdrag(2) + fran(2)
force_atom(3,ia) = force_atom(3,ia) + fdrag(3) + fran(3)
end if
end do
do ie = 1, ele_num_l
if(btest(e_mask(ie), langevin_group(lnum)).and.(who_has_ele(ie))) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod,ie)
do ibasis = 1, basis_num(ie)
gam1 = gfactor1(basis_type(ibasis,ie))
gam2 = gfactor2(basis_type(ibasis,ie))*tsqrt
call random_number(rand)
fran(1) = gam2*(rand-0.5)
call random_number(rand)
fran(2) = gam2*(rand-0.5)
call random_number(rand)
fran(3) = gam2*(rand-0.5)
fdrag(1) = gam1*vel_atom(1,ia)
fdrag(2) = gam1*vel_atom(2,ia)
fdrag(3) = gam1*vel_atom(3,ia)
force_eq(1,ibasis,ip) = force_eq(1,ibasis,ip) + fdrag(1) + fran(1)
force_eq(2,ibasis,ip) = force_eq(2,ibasis,ip) + fdrag(2) + fran(2)
force_eq(3,ibasis,ip) = force_eq(3,ibasis,ip) + fdrag(3) + fran(3)
end do
end do
end if
end do
end do
if(ele_num > 0) call communicate_force_eq
end subroutine langevin_post_force
end module langevin

70
src/logger.f90 Normal file
View file

@ -0,0 +1,70 @@
module logger
!This is in charge of logging to stdout and to log files
use parameters
logical,save :: log_on
public
contains
subroutine log_defaults
log_on=.true.
end subroutine log_defaults
subroutine init_log
!Open log file
if (log_on) then
if (rank == root) then
open(unit=51, file="cac.log", action='write', status='replace', position='rewind')
end if
end if
end subroutine init_log
subroutine log_msg(msg, isline, all_procs)
!Log to stdout and log file
character(len=*), intent(in) :: msg
integer, intent(in), optional :: isline !if 0 then no tab in front
logical, intent(in), optional :: all_procs !if .true. then all processors calling this write
integer :: tab
logical :: lognow
if(log_on) then
lognow = .false.
if(present(all_procs)) then
lognow = all_procs
end if
if(rank == root) lognow = .true.
if(present(isline)) then
tab = isline
else
tab = 1
end if
!write non-line information, which should be indented
if(lognow) then
if(tab == 1) then
print *, " ", trim(adjustl(msg))
if(rank==root) write(51, "(a)") " ", trim(adjustl(msg))
!Write line information which isn't indented
else if(tab == 0) then
print *, trim(adjustl(msg))
if(rank==root) write(51, "(a)") trim(adjustl(msg))
end if
end if
end if
return
end subroutine log_msg
subroutine close_log
!Close the log file
if (log_on) then
if(rank == root) close(51)
end if
end subroutine close_log
end module logger

74
src/main.f90 Normal file
View file

@ -0,0 +1,74 @@
! Copyright (c) 2017-2018 Georgia Institute of Technology. All Rights Reserved
! Redistributing this source code is prohibited. This is a testing version of CAC.
! This source code is provided as is, with no warranties or representations of accuracy
! or suitability for any application, and with no expectation of user support.
! Please alert Alex Selimov (aselimov3@gatech.edu) with any bugs or changes made to source code.
! Written by Shuozhi Xu (shuozhixu@ucsb.edu)
program main
use mpi
use comms
use parameters
use input_parser
use integration
use neighbors
use logger
use minimize
use time
use group
use dump
use thermo
use temp
use berendsen
use potential
use debug
use deform
use langevin
implicit none
!initialize mpi
call comm_init
!Initialize time
call time_defaults
!Start timer
program_start = mpi_wtime()
!Initialize log
call log_defaults
call init_log
!Initialize some default values
deform_num = 0
!Initialize some flags
tflag = .false.
pflag = .false.
dflag = .false.
first_run = .true.
!Now call all necessary default setting subroutines
call potential_defaults
call integration_defaults
call neighbors_defaults
call min_defaults
call group_defaults
call thermo_defaults
call dump_defaults
call langevin_defaults
call dynamics_defaults
!Run input parsing code
call read_input
!close log
call log_time
call close_log
call mpi_finalize(ierr)
stop
end program main

515
src/math.f90 Normal file
View file

@ -0,0 +1,515 @@
module math
!This contains math helper functions
use mpi
use parameters
implicit none
public
contains
subroutine sort_array(array_in, n, array_out, array_order)
!sort 1d real array from minimum to maximum
integer, intent(in) :: n
integer, dimension(n), intent(out) :: array_order
real(kind = wp), dimension(n), intent(in) :: array_in
real(kind = wp), dimension(n), intent(out) :: array_out
integer :: i, j, temp_int
real(kind = wp) :: temp
array_out(:) = array_in(:)
do i = 1, n
array_order(i) = i
end do
do i = 1, n
do j = i+1, n
if(array_out(i) > array_out(j)) then
temp = array_out(j)
array_out(j) = array_out(i)
array_out(i) = temp
temp_int = array_order(j)
array_order(j) = array_order(i)
array_order(i) = temp_int
end if
end do
end do
return
end subroutine sort_array
pure function identity_mat(n)
!real identity matrix of n by n
implicit none
integer, intent(in) :: n
real(kind = wp), dimension(n, n) :: identity_mat
integer :: i
identity_mat(:, :) = 0.0_wp
do i = 1, n
identity_mat(i, i) = 1.0_wp
end do
return
end function identity_mat
pure function cross_product(a, b)
!cross product between two 3*1 vectors
implicit none
real(kind = wp), dimension(3), intent(in) :: a, b
real(kind = wp), dimension(3) :: cross_product
cross_product(1) = a(2) * b(3) - a(3) * b(2)
cross_product(2) = a(3) * b(1) - a(1) * b(3)
cross_product(3) = a(1) * b(2) - a(2) * b(1)
return
end function cross_product
pure function triple_product(a, b, c)
!triple product between three 3*1 vectors
implicit none
real(kind = wp), dimension(3), intent(in) :: a, b, c
real(kind = wp) :: triple_product
triple_product = dot_product(a, cross_product(b, c))
return
end function triple_product
subroutine matrix_normal(a, n, a_nor)
!normalize an n by n matrix
implicit none
integer, intent(in) :: n
real(kind = wp), dimension(n, n), intent(in) :: a
real(kind = wp), dimension(n, n), intent(out) :: a_nor
real(kind = wp), dimension(n) :: v
integer :: i
a_nor(:, :) = a(:, :)
do i = 1, n
v(:) = a(:, i)
a_nor(:, i) = v(:) / norm2(v)
end do
return
end subroutine matrix_normal
subroutine matrix_lattice(a, n, iorh)
!check if a matrix is orthogonal and obey right hand rule
implicit none
integer, intent(in) :: n
real(kind = wp), dimension(n, n), intent(in) :: a
logical, dimension(2), intent(out) :: iorh
real(kind = wp), dimension(n) :: v, v_k
integer :: i, j
iorh(:) = .true.
i_loop: do i = 1, n
do j = i + 1, n
if(abs(dot_product(a(:, i), a(:, j))) > lim_small) then
iorh(1) = .false.
end if
if(j == i + 1) then
v(:) = cross_product(a(:, i), a(:, j))
v_k(:) = v(:) - a(:, mod(j, n)+1)
else if((i == 1).and.(j == n)) then
v(:) = cross_product(a(:, j), a(:, i))
v_k(:) = v(:) - a(:, i+1)
end if
if(norm2(v_k(:)) > lim_small) then
iorh(2) = .false.
end if
if(all(iorh).eqv..false.) then
exit i_loop
end if
end do
end do i_loop
return
end subroutine matrix_lattice
subroutine matrix_inverse(a, n, a_inv)
!inverse an n by n matrix
implicit none
integer, intent(in) :: n
real(kind = wp), dimension(n, n), intent(in) :: a
real(kind = wp), dimension(n, n), intent(out) :: a_inv
integer :: i, j, k, piv_loc
real(kind = wp) :: coeff, sum_l, sum_u
real(kind = wp), dimension(n) :: b, x, y, b_piv
real(kind = wp), dimension(n, n) :: l, u, p
real(kind = wp), allocatable :: v(:), u_temp(:), l_temp(:), p_temp(:)
l(:, :) = identity_mat(n)
u(:, :) = a(:, :)
p(:, :) = identity_mat(n)
!LU decomposition with partial pivoting
do j = 1, n-1
allocate( v(n-j+1), stat = allostat )
if(allostat /=0 ) then
print *, 'Fail to allocate v in matrix_inverse'
call mpi_abort(1,mpi_comm_world,ierr)
end if
v(:) = u(j:n, j)
if(maxval(abs(v)) < lim_zero) then
print *, 'Fail to inverse matrix', a
call mpi_abort(1,mpi_comm_world,ierr)
end if
piv_loc = maxloc(abs(v), 1)
deallocate( v, stat = deallostat )
if(deallostat /=0 ) then
print *, 'Fail to deallocate v in matrix_inverse'
call mpi_abort(1,mpi_comm_world,ierr)
end if
!partial pivoting
if(piv_loc /= 1) then
allocate( u_temp(n-j+1), p_temp(n), stat = allostat)
if(allostat /=0 ) then
print *, 'Fail to allocate p_temp and/or u_temp in matrix_inverse'
call mpi_abort(1,mpi_comm_world,ierr)
end if
u_temp(:) = u(j, j:n)
u(j, j:n) = u(piv_loc+j-1, j:n)
u(piv_loc+j-1, j:n) = u_temp(:)
p_temp(:) = p(j, :)
p(j, :) = p(piv_loc+j-1, :)
p(piv_loc+j-1, :) = p_temp(:)
deallocate( u_temp, p_temp, stat = deallostat )
if(deallostat /=0 ) then
print *, 'Fail to deallocate p_temp and/or u_temp in matrix_inverse'
call mpi_abort(1,mpi_comm_world,ierr)
end if
if(j > 1) then
allocate( l_temp(j-1), stat = allostat )
if(allostat /= 0) then
print *, 'Fail to allocate l_temp in matrix_inverse'
call mpi_abort(1,mpi_comm_world,ierr)
end if
l_temp(:) = l(j, 1:j-1)
l(j, 1:j-1) = l(piv_loc+j-1, 1:j-1)
l(piv_loc+j-1, 1:j-1) = l_temp(:)
deallocate(l_temp, stat = deallostat)
if(deallostat /=0 ) then
print *, 'Fail to deallocate l_temp in matrix_inverse'
call mpi_abort(1,mpi_comm_world,ierr)
end if
end if
end if
!LU decomposition
do i = j+1, n
coeff = u(i, j)/u(j, j)
l(i, j) = coeff
u(i, j:n) = u(i, j:n)-coeff*u(j, j:n)
end do
end do
a_inv(:, :) = 0.0_wp
do j = 1, n
b(:) = 0.0_wp
b(j) = 1.0_wp
b_piv(:) = matmul(p, b)
!Now we have LUx = b_piv
!the first step is to solve y from Ly = b_piv
!forward substitution
do i = 1, n
if(i == 1) then
y(i) = b_piv(i)/l(i, i)
else
sum_l = 0
do k = 1, i-1
sum_l = sum_l+l(i, k)*y(k)
end do
y(i) = (b_piv(i)-sum_l)/l(i, i)
end if
end do
!then we solve x from ux = y
!backward subsitution
do i = n, 1, -1
if(i == n) then
x(i) = y(i)/u(i, i)
else
sum_u = 0
do k = i+1, n
sum_u = sum_u+u(i, k)*x(k)
end do
x(i) = (y(i)-sum_u)/u(i, i)
end if
end do
! put x into j column of a_inv
a_inv(:, j) = x(:)
end do
return
end subroutine matrix_inverse
! three point interpolation
function interp3(x, tab)
!this comes from dl poly
implicit none
real(kind = wp), intent(in) :: x
real(kind = wp), dimension(:), intent(in) :: tab
real(kind = wp) :: interp3
integer :: l
real(kind = wp) :: rdr, rrr, ppp, gk0, gk1, gk2, t1, t2
interp3 = 0.0_wp
if(x > tab(3)) then
interp3 = 0
else
rdr = 1.0_wp / tab(4)
rrr = x - tab(2)
l = min(nint(rrr*rdr), nint(tab(1))-1)
if(l < 5) then
interp3 = tab(5)
end if
ppp = rrr * rdr - real(l, wp)
gk0 = tab(l-1)
gk1 = tab(l)
gk2 = tab(l+1)
t1 = gk1 + (gk1 - gk0) * ppp
t2 = gk1 + (gk2 - gk1) * ppp
if(ppp < 0.0_wp) then
interp3 = t1 + 0.5_wp * (t2 - t1) * (ppp + 1.0_wp)
else if(l == 5) then
interp3 = t2
else
interp3 = t2 + 0.5_wp * (t2 - t1) * (ppp - 1.0_wp)
end if
end if
return
end function interp3
subroutine delete_duplicate(array_in, n, array_out, m)
!delete duplicates in an 1-d integer array, the others are 0
implicit none
integer, intent(in) :: n
integer, dimension(n), intent(in) :: array_in
integer, intent(out) :: m
integer, dimension(n), intent(out) :: array_out
integer :: i, j
integer, dimension(n) :: array_temp
m = 0
array_out(:) = 0
array_temp(:) = array_in(:)
do i = 1, n
if(array_temp(i) > 0) then
do j = i+1, n
if(array_temp(i) == array_temp(j)) then
array_temp(j) = 0
end if
end do
m = m+1
array_out(m) = array_temp(i)
end if
end do
return
end subroutine delete_duplicate
subroutine normal_distribution(mean, dev, rand)
!return a normally distributed random number
implicit none
real(kind = wp), intent(in) :: mean, dev
real(kind = wp), intent(out) :: rand
real(kind = wp) :: rand_uf, rand_us
rand_uf = -1.0_wp
do while(rand_uf < lim_zero)
call random_number(rand_uf)
end do
if(rand_uf <= 0.5_wp) then
call random_number(rand_us)
rand = sqrt(-2.0_wp * log(rand_uf)) * cos(2.0_wp * pi * rand_us)
else
call random_number(rand_us)
rand = sqrt(-2.0_wp * log(rand_uf)) * sin(2.0_wp * pi * rand_us)
end if
rand = mean + dev * rand
return
end subroutine normal_distribution
function is_equal(A,B)
real(kind=dp), intent(in) :: A, B
logical :: is_equal
if( (abs(A-B)) <= zerotol) then
is_equal = .true.
else
is_equal = .false.
end if
return
end function
function dumb_interp(test_x, xlo, ylo, xhi, yhi)
!Simple interpolation used just for validation in unit tests
real(kind=dp), intent(in) :: test_x, xlo, ylo, xhi, yhi
real(kind=dp) :: dumb_interp
dumb_interp = ((test_x - xlo)/(xhi-xlo))*(yhi-ylo) + ylo
return
end function
pure function in_block_bd(v, block_bd, check_bd)
!This function determines whether a point is within a block in 3d
!Input/output
real(kind=dp), dimension(3), intent(in) :: v
real(kind=dp), dimension(6), intent(in) :: block_bd
logical, dimension(3), optional, intent(in) :: check_bd
logical :: in_block_bd
!Other variables
integer :: i
logical :: bool_bd(3)
in_block_bd = .true.
if(present(check_bd)) then
bool_bd = check_bd
else
bool_bd(:)=.true.
end if
do i =1 ,3
if(bool_bd(i)) then
!Check upper bound
if(v(i) >= (block_bd(2*i)) ) then
in_block_bd =.false.
exit
!Check lower bound
else if (v(i) < (block_bd(2*i-1))) then
in_block_bd = .false.
exit
end if
end if
end do
end function in_block_bd
pure function in_cutoff(rl, rk, rcmin, rcoff)
!This function returns true when rl and rk are separated by a distance between rcmin and rcoff
real(kind=wp), dimension(3), intent(in) :: rl, rk
real(kind=wp), intent(in) :: rcmin, rcoff
logical :: in_cutoff
real(kind=wp) :: rlk
rlk=norm2(rk-rl)
if (( rlk > rcoff).or.(rlk<rcmin)) then
in_cutoff=.false.
else
in_cutoff=.true.
end if
return
end function
pure function neighbor_dis_free(rl, rk)
real(kind=wp), dimension(3), intent(in) :: rl, rk
real(kind=wp), dimension(4) :: neighbor_dis_free
neighbor_dis_free(1:3) = rk-rl
neighbor_dis_free(4) = norm2(neighbor_dis_free(1:3))
return
end function
subroutine interpolate(npoints, delta, tab, spline)
!Interpolate a tab array using cubic splines
integer, intent(in) :: npoints
real(kind=wp), intent(in) :: delta
real(kind = wp), dimension(npoints) :: tab
real(kind=wp), dimension(7,npoints), intent(out) :: spline
integer :: i
spline(7,:) = tab(:)
spline(6,1) = spline(7,2) - spline(7,1)
spline(6,2) = 0.5_wp*(spline(7,3) - spline(7,1))
spline(6, npoints-1) = 0.5_wp*(spline(7, npoints) - spline(7, npoints-2))
spline(6, npoints) = spline(7,npoints) - spline(7,npoints-1)
do i = 3, npoints-2
spline(6,i) = ((spline(7,i-2)-spline(7,i+2)) + 8.0_wp*(spline(7,i+1)-spline(7,i-1)))/12.0_wp
end do
do i = 1, npoints-1
spline(5,i) = 3.0_wp*(spline(7,i+1) - spline(7,i)) - 2.0_wp*spline(6,i) - spline(6,i+1)
spline(4,i) = spline(6,i) + spline(6, i+1) - 2.0_wp*(spline(7,i+1) - spline(7,i))
end do
spline(5,npoints) = 0.0_wp
spline(4,npoints) = 0.0_wp
do i =1, npoints
spline(3,i) = spline(6,i)/delta
spline(2,i) = 2.0_wp*spline(5,i)/delta
spline(1,i) = 3.0_wp*spline(4,i)/delta
end do
end subroutine interpolate
end module math

190
src/min_arrays.f90 Normal file
View file

@ -0,0 +1,190 @@
module min_arrays
!This module contains all of the important min_arrays
use parameters
use elements
use errors
implicit none
!Arrrays needed for conjugate gradient minimization
real(kind=wp), allocatable, save :: hatom(:,:), hnode(:,:,:), gatom(:,:), gnode(:,:,:), rzeroatom(:,:), rzeronode(:,:,:)
public
contains
subroutine alloc_min_arrays
!Initialize cg variables
if(ele_num > 0) then
if(allocated(rzeronode)) deallocate(rzeronode, hnode, gnode)
allocate(hnode(3, max_basisnum, node_num_l), gnode(3, max_basisnum, node_num_l), &
rzeronode(3, max_basisnum, node_num_l))
end if
if(atom_num > 0) then
if(allocated(rzeroatom)) deallocate(rzeroatom, hatom, gatom)
allocate(hatom(3,atom_num_l), gatom(3,atom_num_l), rzeroatom(3, atom_num_l))
end if
end subroutine alloc_min_arrays
subroutine grow_at_min_arrays(seg_real)
!Grow atom min arrays
integer, intent(in), optional :: seg_real
integer :: seg_num
real(kind=wp), allocatable :: r_array(:,:), g_array(:,:), h_array(:,:)
if(present(seg_real)) then
seg_num = seg_real
else
seg_num = 1024
end if
allocate(r_array(3, size(rzeroatom,2)+seg_num), g_array(3, size(gatom,2)+seg_num), h_array(3, size(hatom,2)+seg_num))
r_array=0.0_wp
g_array=0.0_wp
h_array=0.0_wp
r_array(:,1:size(rzeroatom,2)) = rzeroatom
g_array(:,1:size(gatom,2)) = gatom
h_array(:,1:size(hatom,2)) = hatom
call move_alloc(r_array, rzeroatom)
call move_alloc(g_array, gatom)
call move_alloc(h_array, hatom)
return
end subroutine grow_at_min_arrays
subroutine grow_ele_min_arrays(seg_real)
!Grow atom min arrays
integer, intent(in), optional :: seg_real
integer :: seg_num
real(kind=wp), allocatable :: r_array(:,:,:), g_array(:,:,:), h_array(:,:,:)
if(present(seg_real)) then
seg_num = seg_real
else
seg_num = 1024
end if
allocate(r_array(3, max_basisnum, size(rzeronode,3)+seg_num), &
g_array(3, max_basisnum, size(gnode,3)+seg_num), h_array(3, max_basisnum, size(hnode,3)+seg_num))
r_array=0.0_wp
g_array=0.0_wp
h_array=0.0_wp
r_array(:,:,1:size(rzeronode,3)) = rzeronode
g_array(:,:,1:size(gnode,3)) = gnode
h_array(:,:,1:size(hnode,3)) = hnode
call move_alloc(r_array, rzeronode)
call move_alloc(g_array, gnode)
call move_alloc(h_array, hnode)
return
end subroutine grow_ele_min_arrays
subroutine pack_atom_cg(r0,g,h, send_cg)
!This subroutine packs the atom information for conjugate gradient minimization
real(kind=wp), dimension(3), intent(in) :: r0, g, h
real(kind=wp), dimension(:), intent(out) :: send_cg
integer :: i, k
!Now pack send_cg
k = 1
do i = 1,3
send_cg(k) = r0(i)
k=k+1
send_cg(k) = g(i)
k=k+1
send_cg(k) = h(i)
k=k+1
end do
return
end subroutine pack_atom_cg
subroutine unpack_atom_cg(recv_cg, r0, g, h)
!This subroutine unpacks the atom information for cg minimization
real(kind=wp), dimension(:), intent(in) :: recv_cg
real(kind=wp), dimension(3), intent(out) :: r0, g, h
integer :: i, k
!Now unpack recv_cg
k = 1
do i = 1,3
r0(i) = recv_cg(k)
k=k+1
g(i) = recv_cg(k)
k=k+1
h(i) = recv_cg(k)
k=k+1
end do
return
end subroutine unpack_atom_cg
subroutine pack_ele_cg(n, b, r0,g,h, send_cg)
!This subroutine packs the atom information for conjugate gradient minimization
integer, intent(in) :: n, b
real(kind=wp), dimension(3, max_basisnum, ng_max_node), intent(in) :: r0, g, h
real(kind=wp), dimension(:), intent(out) :: send_cg
integer :: i, k, inod, ibasis
!Now pack send_cg
k = 1
do inod = 1, n
do ibasis = 1, b
do i = 1,3
send_cg(k) = r0(i,ibasis,inod)
k=k+1
send_cg(k) = g(i,ibasis,inod)
k=k+1
send_cg(k) = h(i,ibasis,inod)
k=k+1
end do
end do
end do
return
end subroutine pack_ele_cg
subroutine unpack_ele_cg(n, b, recv_cg, r0, g, h)
!This subroutine packs the atom information for conjugate gradient minimization
integer, intent(in) :: n, b
real(kind=wp), dimension(:), intent(in) :: recv_cg
real(kind=wp), dimension(3, max_basisnum, ng_max_node), intent(out) :: r0, g, h
integer :: i, k, inod, ibasis
!Now pack send_cg
k = 1
do inod = 1, n
do ibasis = 1, b
do i = 1,3
r0(i,ibasis,inod)=recv_cg(k)
k=k+1
g(i,ibasis,inod)=recv_cg(k)
k=k+1
h(i,ibasis,inod)=recv_cg(k)
k=k+1
end do
end do
end do
return
end subroutine unpack_ele_cg
end module min_arrays

309
src/minimize.f90 Normal file
View file

@ -0,0 +1,309 @@
module minimize
!This code contains the code needed to run the various types of dynamics
use parameters
use fire
use cg
use neighbors
use forces
use comms
use potential
use dump
use thermo
use errors
use logger
use debug
implicit none
real(kind=wp), save :: force_tol, energy_tol
integer :: min_style, max_iter, reset_num
real(kind=wp), parameter :: eps_energy = 1d-8
private :: pre_iter, post_iter, iterate
public
contains
subroutine min_defaults
!Set defaults
min_style = 1
end subroutine min_defaults
subroutine parse_min_style(line)
!parse the run command
character(len = *), intent(in) :: line
character(len=read_len) :: tmptxt, min_string, args(20)
integer ::iospara, j, i
real(kind=wp) :: val
read(line, *, iostat = iospara) tmptxt, min_string
if (iospara > 0) call read_error("Invalid read of min_string from min_style command", iospara)
select case(min_string)
case('fire', 'FIRE')
call fire_defaults
min_style = 1
j = tok_count(line)
if(j > 2) then
read(line, *) args(1:j)
i = 3
do while(i <= j)
select case(args(i))
case('alpha0', 'tmax', 'tmin')
i = i + 1
!Read param val
read(args(i), *) val
!set param val
call set_fire_param(args(i-1),val)
case default
write(tmptxt, *) "Cannot pass argument ", trim(adjustl(args(i))), " to min_style with style cg"
call misc_error(tmptxt)
end select
i = i + 1
end do
end if
case('cg', 'CG')
call command_error('Conjugate gradient not correctly working in this implementation of CAC')
call cg_defaults
min_style = 2
!Now parse additional options for cg
j = tok_count(line)
if(j > 2) then
read(line, *) args(1:j)
i = 3
do while(i <= j)
select case(args(i))
case('reset')
i = i + 1
!Read the number of iterations between resets
read(args(i), *) reset_num
case default
write(tmptxt, *) "Cannot pass argument ", trim(adjustl(args(i))), " to min_style with style cg"
call misc_error(tmptxt)
end select
i = i + 1
end do
end if
case default
call command_error("Min_style "//trim(adjustl(min_string))//" is not currently accepted as an option for dynamics")
end select
end subroutine parse_min_style
subroutine parse_minimize(line)
character(len=*), intent(in) :: line
character(len=read_len) :: tmptxt
integer :: iospara
read(line, *, iostat = iospara) tmptxt, energy_tol, force_tol, max_iter
if (iospara > 0) call read_error("Invalid read of minimize command", iospara)
if ((force_tol < 0)) then
write(tmptxt, *) "Force tolerance ", force_tol, " should not be less than zero"
call misc_error(tmptxt)
end if
if ((energy_tol< 0)) then
write(tmptxt, *) "Energy tolerance ", energy_tol, " should not be less than zero"
call misc_error(tmptxt)
end if
if ((max_iter<= 0)) then
write(tmptxt, *) "max_iter", max_iter, " should not be less than or equal to zero"
call misc_error(tmptxt)
end if
begin_step = iter
call run_min
end subroutine parse_minimize
subroutine run_min
!This subroutine actually runs the minimization
integer ::i, exit_cond, delay, nevals
real(kind=wp) :: old_energy, old_f_norm, pe, fnorm
character(len=read_len) :: msg
!Init neighbor list and do initial calc
if (nei_init) then
call update_neighbor(iter, .false.,.true.)
else
if(ele_num > 0) call ghost_cg
if(atom_num> 0) call ghost_at
call neighbor_lists
end if
call update_force
!Initialize minimizer
delay = 0
select case(min_style)
case(1)
!Check to make sure that the timestep has been set
if (is_equal(time_step, 0.0_wp)) then
call misc_error("Time step must be set prior to calling minimize with min_style fire")
end if
call fire_init
delay = get_fire_delaystep()
!Initialize velocities to 0
if(node_num_l > 0) vel(:,:,:) = 0
if(atom_num_l > 0) vel_atom(:,:) = 0
case(2)
call cg_init
end select
if(first_run) call write_dump(iter, .true.)
call write_thermo_style
call write_thermo_out(iter)
exit_cond = 0
pe = compute_pe(1, .true.)
fnorm = compute_fnorm(1)
do i = 1, max_iter
iter = iter + 1
!Save old energies
old_energy=pe
old_f_norm =fnorm
!Call pre_iterate
call pre_iter(i)
!Call iterate
call iterate(i, exit_cond)
if(min_style == 2) then
call update_neighbor(iter, .true.)
call update_force
!Now check to see if we want to reset the conjugate direction. This may slow down the convergance, but may be
!useful for finite elements
if(reset_num > 0) then
if(mod(i, reset_num) == 0) then
call reset_cg_dir
end if
end if
end if
!Exit if the minimizer tells us to
if(exit_cond > 0) exit
!Check tolerances
if (i > delay) then
pe = compute_pe(1, .true.)
fnorm = compute_fnorm(1)
if(abs(old_energy-pe)< energy_tol*0.5_wp*(abs(pe)+abs(old_energy)+eps_energy)) then
exit_cond = 1
exit
else if (fnorm < force_tol) then
exit_cond = 2
exit
end if
end if
!Call post iterate
call post_iter(i)
end do
!Compute virial
need_virial = .true.
call update_force
!Post completion thermo and dump
call write_thermo_out(iter)
call write_dump(iter, .true.)
!Post minimizer message to user
!First print the exit conditions
if(exit_cond < 3) then
select case(exit_cond)
case(0)
write(msg, *) "Max Iterations"
case(1)
write(msg, *) "Energy Tolerance"
case(2)
write(msg, *) "Force Tolerance"
end select
else
select case(min_style)
case(1)
if(exit_cond == 3) write(msg, *) "Max P < 0 iterations"
case(2)
if(exit_cond == 4) write(msg, *) "Exit because search direction isn't downhill"
if(exit_cond == 5) write(msg, *) "All search direction components are 0"
if(exit_cond == 6) write(msg, *) "Alpha is equal to 0"
if(exit_cond == 11) write(msg, *) "No change to force between line search iterations"
end select
end if
call log_msg("Minimizer out with stopping condition: "//trim(adjustl(msg)))
!Now print number of evaluations and iterations and clean up
select case(min_style)
case(1)
nevals = get_fire_neval()
call fire_clean
case(2)
nevals = get_cg_neval()
call cg_clean
end select
write(msg,*) "Total number of iterations: ", i
call log_msg(msg)
write(msg, *) "Total number of force evals: ", nevals
call log_msg(msg)
write(msg, *) "Final and second-to-last energies: ", pe, old_energy
call log_msg(msg)
call log_neighbor_info
first_run = .false.
end subroutine run_min
subroutine pre_iter(i)
!This subroutine is run before each dynamics timeiter is calculated
!i is the current iter number
integer, intent(in) ::i
!If we are dumping this timeiter then we need to calculate the virial stress
if(need_dump(i)) need_virial = .true.
if((mod(i,thermo_every)==0).and.need_p) need_virial = .true.
return
end subroutine pre_iter
subroutine post_iter(i)
integer, intent(in) :: i
integer :: pre_atom_num
!This subroutine is run after the dynamics timeiter is calculated
!i is the current iter number
!Dump if we need it
call write_dump(i)
!Thermo if we need it
if(mod(i,thermo_every)==0) call write_thermo_out(iter)
!Debug if we need it
if(dflag) call run_debug
need_virial = .false.
return
end subroutine post_iter
subroutine iterate(i, mincode)
!This subroutine calls the correct dynamics to run
!i is the current iter number
integer, intent(in) :: i
integer, intent(out) :: mincode
select case(min_style)
case(1)
call fire_iterate(i, mincode)
case(2)
call cg_iterate(mincode)
end select
end subroutine iterate
end module minimize

48
src/modify.f90 Normal file
View file

@ -0,0 +1,48 @@
module modify
!This module is in charge of code which changes various aspects of current running program, mainly regarding options
use parameters
use neighbors
use elements
use potential
implicit none
public
contains
subroutine parse_modify(line)
character(len=*), intent(in) :: line
integer :: i, acount, iospara
character(len=read_len) :: args(20), msg
logical :: l
acount = tok_count(line)
read(line,*) (args(i), i=1, acount)
i = 2
do while (i < acount)
select case(args(i))
case('atomap_neighbors')
i=i+1
read(args(i), *, iostat=iospara, iomsg=msg) l
if(iospara > 0) call read_error(msg, iospara)
if(l) then
atomap_neighbors = .true.
else
atomap_neighbors = .false.
end if
case default
write(msg, *) "Argument ", args(i), " not accepted in modify command"
end select
i = i + 1
end do
end subroutine parse_modify
end module modify

268
src/morse.f90 Normal file
View file

@ -0,0 +1,268 @@
module morse
!subroutines for pairtab potential style
use parameters
use math
use forces
use elements
use integration
use neighbors
use comms
use errors
use atom_types
implicit none
real(kind=wp), allocatable, private, save :: pair_spline(:,:,:)
real(kind=wp), private, dimension(max_atom_types, max_atom_types), save :: pair_cutoffsq, d0, alpha, r0, morse1
public
contains
subroutine add_morse_potential(type1, type2, ind0, inalpha, inr0, incutoff)
integer, intent(in) :: type1, type2
real(kind=wp), intent(in) :: ind0, inalpha, inr0, incutoff
integer :: i,j, mn
d0(type1, type2) = ind0
d0(type2, type1) = ind0
alpha(type1, type2) = inalpha
alpha(type2, type1) = inalpha
r0(type1, type2) = inr0
r0(type2, type1) = inr0
pair_cutoffsq(type1, type2)=incutoff*incutoff
pair_cutoffsq(type2, type1)=incutoff*incutoff
morse1(type1, type2) = 2*inalpha*ind0
morse1(type2, type1) = 2*inalpha*ind0
if((types_to_pot_type(type1, type2) > 0).or.(types_to_pot_type(type2,type1) > 0)) then
call misc_error("Can't define multiple potentials for single pair interaction")
end if
types_to_pot_type(type1,type2)=ibset(types_to_pot_type(type1,type2),2)
types_to_pot_type(type2,type1)=ibset(types_to_pot_type(type2,type1),2)
if(.not.def_nei) then
def_nei=.true.
rc_neigh=incutoff
else
rc_neigh=max(rc_neigh, incutoff)
end if
end subroutine add_morse_potential
subroutine update_force_morse
integer :: i, ia, ie, iep, jep, ja, iatomap, jatomap, iatom, latomtype, katomtype, nei, mn_list, ibasis, ic, inod, ip
real(kind=wp) :: rl(3), rk(3), rlk(4), dr, d_exp, pair, flk(3), eshape, rsq
real(kind=wp), dimension(max_basisnum*max_intpo_num, ele_num_l) :: energy_intpo
real(kind=wp), dimension(3, max_basisnum*max_intpo_num, ele_num_l) :: force_intpo
real(kind=wp), dimension(3,3, max_basisnum*max_intpo_num, ele_num_l) :: virial_intpo
mn_list=0
do i = 1,2
if(potential_types(i)) mn_list=mn_list+1
end do
energy_intpo=0.0_wp
force_intpo=0.0_wp
virial_intpo=0.0_wp
!First calculate energy/force for atoms
if (atom_num > 0) then
do ia=1, atom_num_l
rl(:)=r_atom(:, ia)
latomtype=type_atom(ia)
do nei = 1, n_at_at(ia, mn_list)
ja= at_nei_at(nei,ia,mn_list)
rk=r_atom(:, ja)
katomtype=type_atom(ja)
rlk(1:3)=rl-rk
rsq = rlk(1)*rlk(1) + rlk(2)*rlk(2) + rlk(3)*rlk(3)
if(rsq < pair_cutoffsq(latomtype, katomtype)) then
rlk(4)=sqrt(rsq)
dr=rlk(4)-r0(latomtype, katomtype)
d_exp=exp(-alpha(latomtype, katomtype)*dr)
flk=rlk(1:3)*(morse1(latomtype, katomtype)*(d_exp*d_exp-d_exp)/rlk(4))
pair=d0(latomtype, katomtype)*(d_exp*d_exp-2.0_wp*d_exp)
energy_atom(ia)=energy_atom(ia)+pair/2.0_wp
force_atom(:, ia) = force_atom(:, ia) + flk
if(need_virial) then
do ic = 1, 3
virial_atom(:, ic, ia) = virial_atom(:, ic, ia) + flk(:) * rlk(ic) / 2.0_wp
end do
end if
if (ja <= atom_num_l) then
energy_atom(ja)=energy_atom(ja) + pair/2.0_wp
force_atom(:, ja) = force_atom(:, ja) - flk
if(need_virial) then
do ic = 1, 3
virial_atom(:, ic, ja) = virial_atom(:, ic, ja) + flk(:) * rlk(ic) / 2.0_wp
end do
end if
end if
end if
end do
do nei=1, n_at_cg(ia, mn_list)
ja = at_nei_cg(nei, ia, mn_list)
rk(:) = r_atomap(:, ja)
katomtype = type_atomap(ja)
rlk(1:3) = rl - rk
rsq = rlk(1)*rlk(1) + rlk(2)*rlk(2) + rlk(3)*rlk(3)
if(rsq < pair_cutoffsq(latomtype, katomtype)) then
rlk(4)=sqrt(rsq)
dr=rlk(4)-r0(latomtype, katomtype)
d_exp=exp(-alpha(latomtype, katomtype)*dr)
flk=rlk(1:3)*(morse1(latomtype, katomtype)*(d_exp*d_exp-d_exp)/rlk(4))
pair=d0(latomtype, katomtype)*(d_exp*d_exp-2.0_wp*d_exp)
energy_atom(ia)=energy_atom(ia)+pair/2.0_wp
force_atom(:, ia) = force_atom(:, ia) + flk
if(need_virial) then
do ic = 1, 3
virial_atom(:, ic, ia) = virial_atom(:, ic, ia) + flk(:) * rlk(ic) / 2.0_wp
end do
end if
if(ja <= atomap_num_l) then
if(atomap_to_intpo(1,ja)/=0) then
jep = atomap_to_intpo(1,ja)
ie = atomap_to_intpo(2,ja)
force_intpo(:,jep,ie) = force_intpo(:,jep,ie) - flk(:)
if(need_virial)then
do ic = 1, 3
virial_intpo(:, ic, jep,ie) = virial_intpo(:, ic, jep,ie) + flk(:) * rlk(ic) / 2.0_wp
end do
end if
energy_intpo(jep,ie) = energy_intpo(jep,ie) + pair/2.0_wp
end if
end if
end if
end do
end do
end if
if(ele_num_l > 0) then
do ie=1, ele_num_l
do iep=1, intpo_count(itype(ie))
do ibasis=1, basis_num(ie)
jep=basis_num(ie)*(iep-1)+ibasis
if(who_has_intpo(iep, ie)) then
iatom = atom_intpo(iep, size_to_shape(size_ele(ie)), itype(ie))
iatomap = cg_atomap(basis_num(ie)*(iatom-1) + ibasis, ie)
latomtype = type_atomap(iatomap)
rl(:) = r_atomap(:, iatomap)
do nei=1, n_cg_cg(jep, ie, mn_list)
ja=cg_nei_cg(nei, jep, ie, mn_list)
rk(:) = r_atomap(:, ja)
katomtype = type_atomap(ja)
rlk(1:3) = rl - rk
rsq = rlk(1)*rlk(1) + rlk(2)*rlk(2) + rlk(3)*rlk(3)
if(rsq < pair_cutoffsq(latomtype,katomtype)) then
rlk(4) = sqrt(rsq)
dr=rlk(4)-r0(latomtype, katomtype)
d_exp=exp(-alpha(latomtype, katomtype)*dr)
flk=rlk(1:3)*(morse1(latomtype, katomtype)*(d_exp*d_exp-d_exp)/rlk(4))
pair=d0(latomtype, katomtype)*(d_exp*d_exp-2.0_wp*d_exp)
force_intpo(:, jep, ie) = force_intpo(:, jep, ie) + flk(:)
if(need_virial) then
do ic = 1, 3
virial_intpo(:, ic, jep, ie) = virial_intpo(:, ic, jep, ie) + flk(:) * rlk(ic) / 2.0_wp
end do
end if
energy_intpo(jep, ie) = energy_intpo(jep, ie)+ pair/2.0_wp
end if
end do
do nei=1, n_cg_at(jep, ie, mn_list)
ja = cg_nei_at(nei, jep, ie, mn_list)
rk(:) = r_atom(:, ja)
katomtype = type_atom(ja)
rlk(1:3) = rl - rk
rsq = rlk(1)*rlk(1) + rlk(2)*rlk(2) + rlk(3)*rlk(3)
if(rsq < pair_cutoffsq(latomtype,katomtype)) then
rlk(4) = sqrt(rsq)
dr=rlk(4)-r0(latomtype, katomtype)
d_exp=exp(-alpha(latomtype, katomtype)*dr)
flk=rlk(1:3)*(morse1(latomtype, katomtype)*(d_exp*d_exp-d_exp)/rlk(4))
pair=d0(latomtype, katomtype)*(d_exp*d_exp-2.0_wp*d_exp)
force_intpo(:, jep, ie) = force_intpo(:, jep, ie) + flk(:)
if(need_virial) then
do ic = 1, 3
virial_intpo(:, ic, jep, ie) = virial_intpo(:, ic, jep, ie) + flk(:) * rlk(ic) / 2.0_wp
end do
end if
energy_intpo(jep, ie) = energy_intpo(jep, ie)+ pair/2.0_wp
end if
end do
end if
end do
end do
do iep= 1, intpo_count(itype(ie))
do ibasis=1, basis_num(ie)
jep=basis_num(ie)*(iep-1)+ibasis
iatom=atom_intpo(iep, size_to_shape(size_ele(ie)), itype(ie))
iatomap=cg_atomap(basis_num(ie)*(iatom-1)+ ibasis, ie)
if(who_has_intpo(jep, ie).eqv..true.) then
force_intpo(:, jep, ie) = force_intpo(:, jep, ie) &
* weight_intpo(iep, size_to_shape(size_ele(ie)), itype(ie))
if(need_virial) then
virial_intpo(:, :, jep, ie) = virial_intpo(:, :, jep, ie) &
* weight_intpo(iep, size_to_shape(size_ele(ie)), itype(ie))
end if
energy_intpo(jep, ie) = energy_intpo(jep, ie) &
* weight_intpo(iep, size_to_shape(size_ele(ie)), itype(ie))
end if
end do
end do
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do iep = 1, intpo_count(itype(ie))
do ibasis = 1, basis_num(ie)
jep = basis_num(ie)*(iep-1) + ibasis
if(who_has_intpo(iep, ie).eqv..true.) then
iatom = atom_intpo(iep, size_to_shape(size_ele(ie)), itype(ie))
eshape = a_interpo(inod, iatom, size_to_shape(size_ele(ie)), etype(ie))
force(:, ibasis, ip ) = force(:, ibasis, ip) + eshape * force_intpo(:, jep, ie)
if(need_virial) then
virial(:, :, ibasis, ip) = virial(:, :, ibasis, ip) &
+ eshape * virial_intpo(:, :, jep, ie)
end if
energy(ibasis, ip) = energy(ibasis, ip) + eshape*energy_intpo(jep, ie)
end if
end do
end do
end do
end do
end if
end subroutine update_force_morse
end module morse

2413
src/neighbors.f90 Normal file

File diff suppressed because it is too large Load diff

50
src/parameters.f90 Normal file
View file

@ -0,0 +1,50 @@
module parameters
implicit none
!Precision defining parameters
integer, parameter :: dp = selected_real_kind(15, 307), & ! double real
qp = selected_real_kind(33, 4931), & ! quadrupole real
wp = dp
integer, parameter :: read_len = 500 !Read 500 characters for every input command
integer :: error
!Integers for which hold error codes
integer, save :: allostat, deallostat, ierr
!Chunk size for arrays
integer, parameter :: seg_num = 1024
!Various parameters used for tolerance checks
integer, save :: lim_large_int = huge(1)
real(kind = wp), parameter :: lim_zero = epsilon(1.0_wp), lim_small = epsilon(1.0), lim_large = huge(1.0)
real(kind=dp), parameter :: zerotol=1E-8
!constant in physics
real(kind = wp), parameter :: &
boltzmann = 8.6173324e-5_wp, &
avogadro = 6.02214129e23_wp, &
electron_volt = 1.602176565e-19_wp, &
amu = 1.6605402e-27_wp, &
pi = 3.14159265358979323846_wp, &
!nktv2p is a conversion that lammps uses to get virial stress to atm
nktv2p = 1602176.5_wp, &
!ftm2v is another conversion factor from lammps used in fire
ftm2v = 1.0_wp/1.0364269d-4
!constant in motion equation and kinetic energy calculation, conversion from mv^2 to energy
real(kind = wp), parameter :: const_motion = 0.00010364269_wp
!Parameters for simulator
character(len = 20), save :: simulator
logical,save :: need_virial = .false., first_run
!Cutoff radiuses
real(kind=wp), save :: rc_off, rc_min, rc_neigh, rc_sq
!Comm variables
integer, parameter :: root = 0
integer :: rank
end module parameters

217
src/potential.f90 Normal file
View file

@ -0,0 +1,217 @@
module potential
use parameters
use eam
use morse
use forces
use comms
use str
use atom_types
use force_mod
implicit none
!This code handles controlling which potentials are called and processes potential input
integer :: pnum, enum, fnum
!Spline arrays
public
contains
subroutine potential_defaults
potential_types=.false.
rdr = 0.0_wp
rdrho = 0.0_wp
nr = 0.0_wp
nrho = 0.0_wp
pnum = 0
enum = 0
fnum = 0
type_to_pair = 0
type_to_dens = 0
pot_map = 0
end subroutine potential_defaults
subroutine parse_potential(line)
character(len = *), intent(in) :: line
character(len = 100) :: tmptxt, tmptxt2
character(len = 100) potential_type
character(len = read_len) :: filename
character(len=2), dimension(max_atom_types) :: names
integer :: iospara, i, j, mn, itype, jtype, lim
real(kind=wp) :: ind0, inalpha, inr0,cutoff
real(kind=wp), allocatable :: mpair(:)
read(line, *, iostat = iospara) tmptxt, potential_type
select case(potential_type)
case('eam','fs')
potential_types(1)=.true.
read(line, *, iostat = iospara) tmptxt, potential_type, filename
if(iospara> 0) call read_error("Error parsing potential command", iospara)
!Now if we have more than 2 tokens we read them in as atom types unless we have already set the atom types
j = tok_count(line)-3
if ((j > 0).and.(.not.atom_types_set)) then
read(line, *, iostat = iospara) tmptxt, potential_type, filename, (names(i), i = 1, j)
!and pass it to atom_types
call set_atom_types(j, names)
end if
if(potential_type == "eam") then
call eam_lammps(filename, 1)
else if(potential_type == "fs") then
call eam_lammps(filename, 1, .true.)
end if
call eamarray2spline
!Set cutoff radiuses
rc_off = min(d_finish, p_finish)
rc_sq = rc_off**2
rc_min = max(d_start, p_start, lim_small)
if (.not.def_nei) then
def_nei = .true.
rc_neigh = rc_off
end if
call set_eam_map_arrays
case('morse')
potential_types(2)=.true.
!atom types have to be set before morse potential is called, it usually needs to be set with the types command
if(.not.atom_types_set) call misc_error("Atom types need to be set using types command before morse is used")
read(line, *) tmptxt, tmptxt2, itype, jtype, ind0, inalpha, inr0, cutoff
call add_morse_potential(itype, jtype, ind0, inalpha, inr0, cutoff)
case default
call command_error(trim(adjustl(potential_type))//" is not an acceptable potential type")
end select
!Log the atom types
!call log_types
end subroutine parse_potential
subroutine alloc_potential_arrays
!Initialize potential arrays
if (potential_types(1)) then
call alloc_eam_arrays
end if
end subroutine alloc_potential_arrays
subroutine update_force
real(kind=wp) :: t_start, t_end
call pre_force
t_start = mpi_wtime()
!First zero arrays
if(ele_num > 0) then
if(allocated(force)) then
if(node_num_l > size(force, 3)) then
deallocate(force, energy)
allocate(force(3, max_basisnum, node_num_l), energy(max_basisnum, node_num_l), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate force/energy", allostat)
if(need_virial) then
deallocate(virial)
allocate(virial(3, 3, max_basisnum, node_num_l), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate virial", allostat)
end if
end if
else
allocate(force(3, max_basisnum, node_num_l), energy(max_basisnum, node_num_l), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate force/energy", allostat)
if(need_virial) then
allocate(virial(3, 3, max_basisnum, node_num_l), stat = allostat)
if(allostat /= 0) call alloc_error("Failure to allocate virial", allostat)
end if
end if
!Initialize force and energy
force(:, :, :) = 0.0_wp
energy(:, :) = 0.0_wp
!Allocate virial if needed
if(need_virial) then
virial(:, :, :, :) = 0.0_wp
end if
end if
if(atom_num > 0) then
force_atom=0.0_wp
energy_atom=0.0_wp
if(need_virial) then
virial_atom=0.0_wp
end if
end if
if(potential_types(1)) call update_force_eam
if(potential_types(2)) call update_force_morse
!Now call update_equiv to update equivalent node value arrays if there are finite elements
if(ele_num > 0) call update_equiv
t_end = mpi_wtime()
walltime(2) = walltime(2) + (t_end-t_start)
call post_force
end subroutine update_force
subroutine pre_force
return
end subroutine pre_force
subroutine post_force
if(set_force_num > 0) call run_set_force
if((add_force_num > 0)) call add_force
end subroutine post_force
! subroutine pairtab(line)
! !This subroutine reads in tabulated morse potential files
! character(len=*), intent(in) :: line
!
! integer :: i, itype, jtype, mn
! real(kind=wp) :: mdr
! real(kind=wp), allocatable :: mpair(:)
! character(len = read_len) :: tmptxt, filename
!
! read(line, *) tmptxt, itype, jtype, filename
! open(unit=21, file=trim(adjustl(filename)), status='old', action='read', position='rewind')
! !First read the step size and number of units, these start from the first step
!
! read(21, *) mn, mdr
!
! !Now check to make sure the morse step size is equivalent to the eam step size if the eam has already been defined
! if(rdr > 0.0_wp) then
! if(.not.is_equal(1/mdr, rdr)) call misc_error("Step size for morse potential must match step size for eam potential")
! else
! rdr = 0.0
! end if
!
! !Now read in the potential table
! allocate(mpair(mn))
! do i = 1, mn
! read(21,*) mpair(i)
! end do
!
! !Now add the pair potential to the spline arrays
! call spline_arrays(mn,mn,mn,1,1,1)
! !
! embed_spline(:,:, size(embed_spline,3)) = 0.0_wp
! edens_spline(:,:, size(edens_spline,3)) = 0.0_wp
!
! call interpolate(mn, mdr, mpair, pair_spline(:,:,size(pair_spline,3)))
!
! return
!
! end subroutine pairtab
end module potential

110
src/quenched_dynamics.f90 Normal file
View file

@ -0,0 +1,110 @@
module quenched_dynamics
use parameters
use comms
use elements
use neighbors
use potential
use time
use vel_verlet
implicit none
public
contains
subroutine qd(i)
integer, intent(in) :: i
call update_r
call update_neighbor(i)
call update_force
call quenched_vel
end subroutine qd
subroutine quenched_vel
integer :: ia, ie, inod, ip, ibasis
real(kind = wp) :: pro_vel_p, pro_force_normsq, force_normsq, force_norm, vel_p
pro_vel_p = 0.0_wp
pro_force_normsq = 0.0_wp
!coarse-grained domain
if(ele_num /= 0) then
do ie = 1, ele_num_l
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
vel(:, ibasis, ip) = vel(:, ibasis, ip) &
+ 0.5_wp * (force_eq_pre(:, ibasis, ip) &
+ force_eq(:, ibasis, ip)) / masses(basis_type(ibasis,ie)) &
* time_step / const_motion
if(who_has_ele(ie).eqv..true.) then
pro_vel_p = pro_vel_p + dot_product(vel(:, ibasis, ip), force_eq(:, ibasis, ip))
pro_force_normsq = pro_force_normsq + dot_product(force_eq(:, ibasis, ip), force_eq(:, ibasis, ip))
end if
end do
end do
end do
end if
!atomistic domain
if(atom_num /= 0) then
do ia = 1, atom_num_l
vel_atom(:, ia) = vel_atom(:, ia) &
+ 0.5_wp * (force_atom_pre(:, ia) &
+ force_atom(:, ia)) / masses(type_atom(ia)) &
* time_step / const_motion
pro_vel_p = pro_vel_p + dot_product(vel_atom(:, ia), force_atom(:, ia))
pro_force_normsq = pro_force_normsq + dot_product(force_atom(:, ia), force_atom(:, ia))
end do
end if
!sum pro_vel_p and pro_force_normsq up to get vel_p and force_normsq
if(pro_num == 1) then
vel_p = pro_vel_p
force_normsq = pro_force_normsq
else
call mpi_allreduce(pro_vel_p, vel_p, 1, mpi_wp, mpi_sum, world, ierr)
call mpi_allreduce(pro_force_normsq, force_normsq, 1, mpi_wp, mpi_sum, world, ierr)
end if
force_norm = sqrt(force_normsq)
! decide if the nodes/atoms should be freezed
! coarse-grained domain
if(ele_num /= 0) then
do ip = 1, node_num_l
if((vel_p < 0.0_wp).or. (abs(vel_p) < lim_zero)) then
vel(:, :, ip) = 0.0_wp
else
!only use the component of the velocity parallel to the force
vel(:, :, ip) = vel_p * force_eq(:, :, ip) / force_normsq
end if
end do
end if
!atomistic domain
if(atom_num /= 0) then
do ia = 1, atom_num_l
if((vel_p < 0.0_wp).or. (abs(vel_p) < lim_zero)) then
vel_atom(:, ia) = 0.0_wp
else
!only use the component of the velocity parallel to the force
vel_atom(:, ia) = vel_p * force_atom(:, ia) / force_normsq
end if
end do
end if
end subroutine quenched_vel
end module quenched_dynamics

339
src/read_data.f90 Normal file
View file

@ -0,0 +1,339 @@
module read_data
! This code is in charge of all the reading and writing
use mpi
use math
use parameters
use elements
use box
use integration
use potential
use dynamics
use comms
use logger
use str
use time
public
contains
subroutine parse_read(line)
character(len = *), intent(in) :: line
character(len=read_len) :: tmptxt, filename
integer :: iospara
read(line, *, iostat = iospara) tmptxt, filename
if(iospara > 0) call read_error(" Invalid read of read_data command",iospara)
if(rank == root) then
write(tmptxt,*) "Reading data from ", trim(adjustl(filename))
call log_msg(tmptxt)
end if
call read_model(filename)
end subroutine
subroutine read_model(filename)
!This subroutine is in charge of reading the model and distributing the model to the different processors
character(len=read_len), intent(in) :: filename
character(len=read_len) :: msg
integer :: numbers(8), i, j, k
real(kind=wp) :: box_mat_bcast(9)
numbers(:) = 0
if (rank == root) then
!First read the file if root and broadcast numbers, currently only accepts restart files
call read_restart(filename)
if (ele_num > 0) atomap_max = max_basisnum*(maxval(size_ele_scatter)+1)**3
numbers(1) = ele_num
numbers(2) = node_num
numbers(3) = atom_num
numbers(4) = atomap_num
numbers(5) = ng_max_node
numbers(6) = max_basisnum
numbers(7) = atomap_max
numbers(8) = iter
call mpi_bcast(numbers, 8, mpi_integer, root, world, ierr)
write(msg, *) "Read ", ele_num, " elements with ", node_num, " nodes and read ", atom_num, " atoms"
call log_msg(msg)
else
!If not root then wait to recieve files and then initialize numbers
call mpi_bcast(numbers, 8, mpi_integer, root, world, ierr)
ele_num = numbers(1)
node_num = numbers(2)
atom_num = numbers(3)
atomap_num = numbers(4)
ng_max_node = numbers(5)
max_basisnum= numbers(6)
atomap_max = numbers(7)
iter = numbers(8)
end if
!Communicate masses
call mpi_bcast(masses, max_atom_types, mpi_wp, root, world, ierr)
!Now we initialize the local variable nums
ele_num_l = nint((real(ele_num,wp) / pro_num) * 1.2_wp)
node_num_l = nint((real(node_num,wp) / pro_num) * 1.2_wp)
atom_num_l = nint((real(atom_num,wp) / pro_num) * 1.2_wp)
atomap_num_l = nint((real(atomap_num,wp) / pro_num) * 1.2_wp)
ele_num_lr = ele_num_l
node_num_lr = node_num_l
atom_num_lr = atom_num_l
atomap_num_lr = atomap_num_l
!Now get the largest number of atoms per element
!Now broadcast and record box information
call mpi_bcast(box_bd, 6, mpi_wp, root, world, ierr)
!Calculate box length
do i = 1, 3
box_length(i) = box_bd(2*i) - box_bd(2*i-1)
end do
!Now set the original box bounds
orig_box_bd = box_bd
orig_box_length = box_length
!Now broadcast box_bcs
call mpi_bcast(boundary, 3, mpi_character, root, world, ierr)
!Define logical periodic boundary variables
do i = 1,3
if (boundary(i:i) == 'p') then
period(i) = .true.
else
period(i) = .false.
end if
end do
periodic = any(period)
!Now prepare box_mat buffer and broadcast
if(rank == root) then
k = 1
do i = 1,3
do j = 1, 3
box_mat_bcast(k) = box_mat(i,j)
k = k+1
end do
end do
end if
call mpi_bcast(box_mat_bcast, 9, mpi_wp, root, world, ierr)
if(rank /= root) then
k = 1
do i = 1,3
do j = 1, 3
box_mat(i,j) = box_mat_bcast(k)
k = k+1
end do
end do
end if
!Now initialize original box_mat
box_mat_ori = box_mat
!Now communicate the velocity flag
call mpi_bcast(need_vel, 1, mpi_logical, root, world, ierr)
!Now scatter the models
call divide_domain
if(ele_num > 0) then
call alloc_cg_arrays
call scatter_cg
call init_integration
end if
if (atom_num > 0) then
call alloc_at_arrays
call scatter_at
end if
!Init all group
call init_group_all
end subroutine read_model
subroutine read_restart(filename)
!First we read the restart file headers
character(len=read_len), intent(in) :: filename
integer :: iospara, i, j, ip, ib, inod, ibasis, bnum
character(len=2) :: paraline
character(len=read_len) :: line, label
character(len=100) :: ioerror, msg
logical :: read_vel_cg, read_vel_at
etype_present(:) = .false.
!open the file for reading
open(1, file = trim(adjustl(filename)), status='old', action='read', position='rewind')
!Read the time
read(1, '(a)', iostat=iospara) paraline
read(1, *, iostat=iospara, iomsg=ioerror) iter, t
if(iospara>0) call read_error(ioerror, iospara)
!Read element number
read(1, '(a)', iostat=iosline) paraline
read(1, *, iostat=iospara) ele_num
if(iospara>0) call read_error("Invalid read of element_num ", iospara)
!Read max interpolated atoms per element
read(1, '(a)', iostat=iosline) paraline
read(1, *, iostat=iospara) atomap_max_ele
if(iospara>0) call read_error("Invalid read of max interpo", iospara)
!Read node number
read(1, '(a)', iostat=iosline) paraline
read(1, *, iostat=iospara) node_num
if(iospara>0) call read_error("Invalid read of node_num ", iospara)
!Read max nodes per element and basisnum
read(1, '(a)', iostat=iosline) paraline
read(1, *, iostat=iospara) ng_max_node, max_basisnum
if(iospara>0) call read_error("Invalid read of ng_max_node/max_basisnum ", iospara)
!Read number of atoms
read(1, '(a)', iostat=iosline) paraline
read(1, *, iostat=iospara) atom_num
if(iospara>0) call read_error("Invalid read of atom_num", iospara)
!Now read the atom type masses and map them to the right potential
read(1, '(a)', iostat=iosline) paraline
read(1, '(a)', iostat=iosline) line
if(iospara>0) call read_error("Invalid read of atom_types ", iospara)
j = tok_count(line)
read(line, *, iostat=iospara) (masses(i), i = 1, j)
!Now read the box boundary definition
read(1, '(a)', iostat=iosline) paraline
read(1, *, iostat=iospara) boundary
if(iospara>0) then
print *, "Error: Invalid read of boundary with error code", iospara
call mpi_abort(mpi_comm_world, 1, ierr)
end if
!Read box_bd
box_bd(:) = 0.0_wp
read(1, '(a)', iostat=iosline) paraline
read(1, *, iostat=iospara) box_bd(1:6)
if(iospara>0) then
print *, "Error: Invalid read of box_bd with error code", iospara
print *, "Last line read was: ", paraline
call mpi_abort(mpi_comm_world, 1, ierr)
end if
!Read box matrix
box_mat(:,:)=0.0_wp
read(1, '(a)', iostat=iosline) paraline
do i=1,3
read( 1, *, iostat=iospara) box_mat(1:3, i)
if(iospara>0) then
print *, "Error: Invalid read of box_mat with error code", iospara
call mpi_abort(mpi_comm_world, 1, ierr)
end if
end do
read_vel_cg=.false.
read_vel_at=.false.
! Now read the cg part of the restart file
if(ele_num > 0) then
!Initialize some coarse-grained variables and allocate scatter arrays
call scatter_cg_array
atomap_num = 0
!Read info lines
read(1, '(a)', iostat=iospara) line
read(1, '(a)', iostat=iospara) line
!Figure out if velocity information is included in this restart file
read(1, '(a)', iostat=iospara) line
read(line, *, iostat=iospara) (label, i =1 ,7)
if(trim(adjustl(label)) == 'velx') then
read_vel_cg = .true.
end if
!Now start reading the element information
do i=1,ele_num
read(1, '(a)', iostat=iospara) line
read(line, *, iostat=iospara, iomsg=msg) tag_ele_scatter(i), bnum, etype_scatter(i), size_ele_scatter(i)
if(iospara>0) call read_error(msg, iospara)
!Set the element_type to true
etype_present(etype_scatter(i)) = .true.
!Now read the nodes
do inod = 1, ng_node(etype_scatter(i))
do ibasis =1 , bnum
if(read_vel_cg) then
read(1, *, iostat=iospara) ip,ib,basis_type_scatter(ib, i),r_scatter(:, ib, ip),vel_scatter(:, ib, ip)
if(iospara>0) then
print *, "Error: Invalid read of node information with error code", iospara
call mpi_abort(mpi_comm_world, 1, ierr)
end if
else
read(1, *, iostat=iospara) ip, ib, basis_type_scatter(ib,i), r_scatter(:, ib, ip)
if(iospara>0) then
print *, "Error: Invalid read of node information with error code", iospara
call mpi_abort(mpi_comm_world, 1, ierr)
end if
end if
end do
basis_num_scatter(i) = bnum
end do
!Figure out the number of atoms needed
select case(etype_scatter(i))
case(1,2,3)
atomap_num =atomap_num + bnum*((size_ele_scatter(i)+1)**3)
end select
end do
end if
!Now read the atomistic information from the restart files
if(atom_num > 0) then
!First allocate the data
call scatter_at_array
read(1, '(a)', iostat=iospara) line
!Figure out if velocity information is included in this restart file
read(1, '(a)', iostat=iospara) line
read(line, *, iostat=iospara) (label, i =1 ,6)
if(trim(adjustl(label)) == 'velx') then
read_vel_at = .true.
end if
!Now read the atomistic information
do i =1, atom_num
if(read_vel_at) then
read(1, *, iostat=iospara) tag_atom_scatter(i), type_atom_scatter(i), &
r_atom_scatter(1:3,i), vel_atom_scatter(1:3,i)
if(iospara>0) then
print *, "Error: Invalid read of atom information with error code", iospara
call mpi_abort(mpi_comm_world, 1, ierr)
end if
else
read(1, *, iostat=iospara) tag_atom_scatter(i), type_atom_scatter(i), r_atom_scatter(1:3,i)
if(iospara>0) then
print *, "Error: Invalid read of atom information with error code", iospara
call mpi_abort(mpi_comm_world, 1, ierr)
end if
end if
end do
end if
!Figure out if we need velocity, need_vel is only true if read_vel_cg and read_vel_at are both true
if ((ele_num > 0).and.(atom_num > 0)) then
need_vel = (read_vel_cg ).and. read_vel_at
else if(ele_num > 0) then
need_vel = read_vel_cg
else if(atom_num > 0) then
need_vel = read_vel_at
end if
close(1)
return
end subroutine read_restart
end module read_data

140
src/set.f90 Normal file
View file

@ -0,0 +1,140 @@
module set
! sets per-atom values as required for random alloys, temperature initializiation
use elements
use group
use logger
implicit none
public
contains
subroutine parse_set(line)
! parse the 'set' command and set appropriate per-atom vals
character(len = *), intent(in) :: line
character(len=read_len) :: tmptxt, g, mode, msg
integer :: iospara, gnum, j, i
real(kind=wp) :: args(20)
if((ele_num == 0).and.(atom_num == 0)) call misc_error("Model must be read in before calling set command")
read(line, *, iostat=iospara) tmptxt, g, mode
if(iospara > 0) call read_error("Failure to read set command", iospara)
gnum = get_group_index(g)
if(gnum == 0) then
call misc_error("Group "//trim(adjustl(g))// " in set command has not been defined. "// &
"Please define group before use")
end if
! read in remaining arguments
j = tok_count(line)-3
if (j > 0) then
read(line, *, iostat = iospara) tmptxt, g, mode, (args(i), i = 1, j)
end if
select case(mode)
case('type')
write(msg,*) "set ", group_counts(1,gnum), " atoms and ", group_counts(2,gnum), " elements to type: ", args(1)
call log_msg(msg)
call set_type(gnum, int(args(1)))
case('fraction')
if(group_counts(2,gnum) > 0) then
call command_error("Set fraction cannot operate on group "//trim(adjustl(group_name(group_num))) &
//" containing elements")
endif
call set_fraction(gnum, int(args(1)), args(2))
case('etype')
if(group_counts(1,gnum) > 0) call command_error("Set etype cannot operate on groups containing atoms")
call set_etype(gnum, int(args(1)))
case('vel')
if(allocated(vel_atom)) then
write(msg,*) "set ", group_counts(1,gnum), " atoms and ", group_counts(2,gnum), " elements to vel ", args(1:3)
call set_vel(gnum, real(args(1),wp),real(args(2),wp), real(args(3),wp))
else
write(msg,*) "Velocity arrays are not allocated, velocity cannot be set"
call log_msg(msg)
end if
call log_msg(msg)
end select
end subroutine parse_set
subroutine set_type(gnum, type)
integer, intent(in) :: gnum, type
integer :: ia, ip, ibasis, ie
do ia = 1, atom_num_l
if(btest(a_mask(ia), gnum)) then
type_atom(ia) = type
end if
end do
do ip = 1, node_num_l
ie = node_cg(ip)
if(btest(e_mask(ie), gnum)) then
do ibasis = 1, basis_num(ie)
basis_type(ibasis,ie) = type
end do
end if
end do
end subroutine set_type
subroutine set_vel(gnum, vx, vy, vz)
integer, intent(in) :: gnum
real(kind = wp), intent(in) :: vx, vy, vz
integer ::ia, ip, ibasis, ie
real(kind=wp) :: v(3)
v(1) = vx
v(2) = vy
v(3) = vz
do ia = 1, atom_num_l
if(btest(a_mask(ia), gnum)) then
vel_atom(:, ia) = v
end if
end do
do ip = 1, node_num_l
ie = node_cg(ip)
if(btest(e_mask(ie), gnum)) then
do ibasis = 1, basis_num(ie)
vel(:,ibasis,ip) = v
end do
end if
end do
end subroutine set_vel
subroutine set_fraction(gnum, type, frac)
integer, intent(in) :: gnum, type
real(kind=wp), intent(in) :: frac
character(len = read_len) :: msg
real(kind=wp) :: rand
integer :: ia, count
! allocate fraction of group_counts array for randomization
count = 0
do ia = 1, atom_num_l
if(btest(a_mask(ia), gnum)) then
call random_number(rand)
if (rand > frac) cycle
type_atom(ia) = type
count = count + 1
end if
end do
write(msg, *) "Set fraction swapped ", count, " atoms to type ", type
call log_msg(msg)
!group_counts(1,gnum)
end subroutine set_fraction
subroutine set_etype(gnum, et)
integer, intent(in) :: gnum, et
integer :: i
do i = 1, ele_num_l
if (btest(e_mask(i), gnum)) then
etype(i) = et
end if
end do
return
end subroutine set_etype
end module set

46
src/str.f90 Normal file
View file

@ -0,0 +1,46 @@
module str
!this module has some string manipulation commands
public
contains
pure function tok_count(text)
!counts number of tokens in a string
character(len = *), intent(in) :: text
integer :: tok_count
integer :: i, j
logical :: in_tok
j = len(trim(adjustl(text)))
in_tok = .false.
tok_count = 0
do i = 1, j
!This checks if it is a white space character which is the delimiter
if(trim(adjustl(text(i:i))) == ' ') then
!If previously we were in token and the current character is the delimiter
!Then we are no longer in the token
if(in_tok) in_tok = .false.
!If the character isn't a white space character and we previously weren't in the token then set in_tok
!to true and increment token count
else if(.not.in_tok) then
in_tok = .true.
tok_count = tok_count + 1
end if
end do
return
end function tok_count
subroutine to_lower(str)
character(*), intent(in out) :: str
integer :: i
do i = 1, len(str)
select case(str(i:i))
case("A":"Z")
str(i:i) = achar(iachar(str(i:i))+32)
end select
end do
end subroutine to_lower
end module str

427
src/temp.f90 Normal file
View file

@ -0,0 +1,427 @@
module temp
use parameters
use mpi
use comms
use elements
use group
use compute
use time
use atom_types
implicit none
integer :: Nf(2), gnum
real(kind=wp) :: Tinit=0.0_wp !This is the target temp for the velocity initialization subroutine
!Target temp and kinetic energy for rescaling subroutines, we have three Ktarget values currently for testing
real(kind=wp) :: Ttarget = 0.0_wp, Ktarget(3), urms(max_atom_types)
real(kind=wp) :: time_constant !The time constant used to calculate the rescaling coefficient
real(kind=wp) :: omega !This is the average frequency used for applying an oscillation to the interpolated atoms
logical :: tflag
integer, private :: delay
public
contains
subroutine parse_temp(line)
!This subroutine parses the temperature command
character(len = *), intent(in) :: line
character(len = read_len) :: tmptxt, group_id, command, args(10)
integer :: iospara, i, g
real(kind = wp) :: K(3), T
delay = 1.0_wp
read(line, *, iostat = iospara) tmptxt, command
!Initialize random number generator
call random_seed()
select case(command)
case('create')
!Read the temperature command
read(line, *, iostat = iospara) tmptxt, command, group_id, T
case('control')
omega = 0.0_wp
!Read the temperature command
read(line, *, iostat = iospara) tmptxt, command, group_id, T, time_constant
!Check for optional arguments
read(line, *, iostat = iospara) args
i = 5
do while(i < tok_count(line))
i = i + 1
select case(args(i))
case('delay')
i = i + 1
read(args(i), *) delay
end select
end do
end select
g = get_group_index(group_id)
if(g == 0) then
call misc_error("Missing group name in temp command")
end if
!Nf(1) is atomic dof and Nf(2) is elemental dof
Nf(1) = 3*group_counts(1, g)
Nf(2) = 3*group_counts(4, g)
!1st component is target energy for all degrees of freedom
K(1) = (Nf(1)+Nf(2))*boltzmann*T/2.0_wp
!2nd is for atomic dof
K(2) = (Nf(1))*boltzmann*T/2.0_wp
K(3) = (Nf(2))*boltzmann*T/2.0_wp
select case(command)
case('create')
write(tmptxt, *) "Initializing velocities to match temperature", T, " with kinetic energy", K(1)
call log_msg(tmptxt)
call init_vel(g,K)
case('control')
Ktarget = K
Ttarget = T
gnum = g
!if (Nf(2) > 0) then
! !Check to see if we have elements, if we do then we have to pass a frequency suboption dictating the frequency of
! !oscillation to apply to the elements
! read(line, *, iostat = iospara, iomsg = msg) tmptxt, command, group_id, Ttarget, time_constant, tmptxt, om
! if(iospara > 0) call read_error(msg, iospara)
! !Inputted frequency must be converted to angular frequency, the input frequency should be given in THz
! omega = omega*2*pi*10.0_wp**12
! !Now calculate the root mean-squared displacement magnitude for each different atom type
! do i = 1, natom_types
! urms(i) = sqrt((3*10d20*boltzmann*electron_volt*Ttarget)/(masses(i)*amu*omega**2))
! end do
!end if
write(tmptxt, *) "Running temperature control: Target Ke is ", Ktarget(1), " target temp is ", &
Ttarget, " with a time", "constant of ", time_constant
call log_msg(tmptxt)
if(omega>lim_small) then
write(tmptxt,*) "Applying a mean square displacement of ", (urms(i), i =1, natom_types), " for atom types ", &
(i, i =1, natom_types)
call log_msg(tmptxt)
end if
tflag = .true.
end select
return
end subroutine parse_temp
subroutine init_vel(g, Ktar)
!This subroutine initializes velocities to a random gaussian distribution around the desired temperature
real(kind=wp), intent(in) :: Ktar(3)
integer, intent(in) :: g
integer :: i, j, ibasis, id, inod, ip, ie
real(kind=wp) :: alpha, Kme, Kall, vel_buff(3*ele_num*max_basisnum*ng_max_node)
Kme = 0.0_wp
!Allocate velocity arrays if not already allocated
if(.not. need_vel) call alloc_velocity_arrays
if(atom_num > 0) then
do i=1, atom_num_l
if (btest(a_mask(i), g)) then
do j = 1, 3
vel_atom(j,i) = gasdev()
Kme = Kme + 0.5_wp * masses(type_atom(i))*const_motion*(vel_atom(j,i)*vel_atom(j,i))
end do
end if
end do
end if
if(ele_num > 0) then
do ie = 1, ele_num_l
if(who_has_ele(ie).and.btest(e_mask(ie), g)) then
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
do j = 1, 3
vel(j,ibasis,ip) = gasdev()
Kme = Kme + 0.5_wp * masses(basis_type(ibasis,ie))*const_motion &
* mass_mat_coeff(size_ele(ie), etype(ie))*(vel(j,ibasis,ip)*vel(j,ibasis,ip))
end do
end do
end do
end if
end do
end if
!Now sum all of the kinetic energies from all processors, calculate the scaling factor and apply it to the velocities
call mpi_allreduce(Kme, Kall, 1, mpi_wp, mpi_sum, world, ierr)
alpha=sqrt(Ktar(1)/Kall)
if(atom_num > 0) vel_atom = alpha*vel_atom
if(ele_num > 0) then
vel=alpha*vel
!Now communicate the element node velocities for shared elements
vel_buff = 0
do ie = 1, ele_num_l
if(who_has_ele(ie)) then
id = ele_glob_id(ie)
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
do j = 1, 3
vel_buff(3*(id-1)*ng_max_node*max_basisnum + 3*(inod-1)*max_basisnum + 3*(ibasis-1)+j) &
= vel(j, ibasis, ip)
end do
end do
end do
end if
end do
call mpi_allreduce(mpi_in_place, vel_buff, 3*ng_max_node*ele_num*max_basisnum, mpi_wp, mpi_sum, world, ierr)
do ie = 1, ele_num_l
id = ele_glob_id(ie)
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
do j = 1, 3
vel(j, ibasis, ip) &
=vel_buff(3*(id-1)*ng_max_node*max_basisnum + 3*(inod-1)*max_basisnum + 3*(ibasis-1)+j)
end do
end do
end do
end do
end if
end subroutine init_vel
subroutine rescale_v
!rescale velocity
integer :: i, j, ibasis, ie
real(kind=wp) :: alpha, avg_vel(3), ke(2), rr, rsum, c1, c2, max_vel(3)
if (mod(iter, delay) == 0) then
!First calculate the kinetic energy
avg_vel = compute_avgvel(gnum)
ke = compute_ke(gnum, avg_vel)
if(Nf(1) > 0) then
if(rank == root) then
!Sample the target kinetic energy from the canonical ensemble
if (is_equal(time_constant, 0.0_wp)) then
c1=0
else
c1 = exp(-1/(time_constant))
end if
c2 = (1.0-c1)*Ktarget(2)/(ke(1))/(Nf(1))
rr = gasdev()
rsum = sumnoises(Nf(1)-1)
!This sampling formulation comes from:
!Bussi, Giovanni et al. "Canonical sampling through velocity rescaling" The Journal of Chemical Physics (2007)
alpha = sqrt(c1 + c2*(rr*rr + rsum) + 2.0_wp*rr*sqrt(c1*c2))
end if
call mpi_bcast(alpha, 1, mpi_wp, root, world, ierr)
!Now rescale the velocities
max_vel=0.0_wp
do i = 1, atom_num_l
do j = 1, 3
vel_atom(j,i) = avg_vel(j) + alpha*(vel_atom(j,i)-avg_vel(j))
end do
if(norm2(vel_atom(:, i)) > norm2(max_vel)) max_vel = vel_atom(:,i)
end do
end if
if (Nf(2) > 0) then
if(rank == root) then
!Sample the target kinetic energy from the canonical ensemble
if (is_equal(time_constant, 0.0_wp)) then
c1=0
else
c1 = exp(-1/(time_constant))
end if
c2 = (1.0-c1)*Ktarget(3)/(ke(2))/(Nf(2))
rr = gasdev()
rsum = sumnoises(Nf(2)-1)
!This sampling formulation comes from:
!Bussi, Giovanni et al. "Canonical sampling through velocity rescaling" The Journal of Chemical Physics (2007)
alpha = sqrt(c1 + c2*(rr*rr + rsum) + 2.0_wp*rr*sqrt(c1*c2))
end if
call mpi_bcast(alpha, 1, mpi_wp, root, world, ierr)
do i = 1, node_num_l
ie = node_cg(i)
do ibasis=1, basis_num(ie)
do j = 1, 3
vel(j, ibasis, i) = avg_vel(j)+alpha*(vel(j,ibasis,i)-avg_vel(j))
end do
end do
end do
end if
end if
return
end subroutine rescale_v
function sumnoises(nn)
!Returns the sum of n independent gaussian noises squared. This is equivalent to a chi-squared dist
implicit none
integer, intent(in) :: nn
real(kind=wp) :: sumnoises
if (nn==0) then
sumnoises=0.0_wp
else if (nn==1) then
sumnoises=gasdev()**2.0_wp
else if (modulo(nn,2)==0) then
sumnoises=2.0_wp*gamdev(nn/2)
else
sumnoises=2.0_wp*gamdev((nn-1)/2) + gasdev()**2.0_wp
end if
end function sumnoises
function gamdev(ia)
!gamma-distributed random number, implemented as described in numerical recipes
implicit none
integer, intent(in) :: ia
integer :: j
real(kind=wp) :: am, e, s, v1, v2, x, y, rand
real(kind=wp) :: gamdev
if(ia < 1) then
print *, "Bad argument to function gamdev"
call mpi_abort(mpi_comm_world, 1, ierr)
else if (ia < 6) then
x = 1
do j = 1, ia
call random_number(rand)
x = x*rand
end do
if( x < lim_small) then
x = 708.4
else
x = -log(x)
end if
else
do while(.true.)
call random_number(rand)
v1=rand
call random_number(rand)
v2=2.0_wp*rand-1.0_wp
if((v1*v1+v2*v2)>1.0_wp) cycle
y = v2/v1
am=ia-1
s=sqrt(2.0_wp*am+1.0_wp)
x=s*y+am
if ( x <= 0) cycle
if (((am*log(x/am)-s*y) < -700) .or.(v1<0.00001)) cycle
e = (1.0_wp + y**2.0_wp)*exp(am*log(x/am)-s*y)
call random_number(rand)
if(rand > e) cycle
exit
end do
end if
gamdev=x
end function gamdev
function gasdev()
! gaussian-distributed random number, implemented as described in numerical recipes
implicit none
integer, save :: iset=0
real(kind=wp), save :: gset
real(kind=wp) :: rand, fac, rsq, v1, v2
real(kind=wp) gasdev
if(iset ==0) then
do while(.true.)
call random_number(rand)
v1=2.0_wp*rand-1.0_wp
call random_number(rand)
v2=2.0_wp*rand-1.0_wp
rsq = v1**2.0_wp + v2**2.0_wp
if((rsq>=1.0_wp).or.((rsq>-lim_small).and.(rsq < lim_small))) then
cycle
else
exit
end if
end do
fac=sqrt(-2.0_wp*log(rsq)/rsq)
gset=v1*fac
gasdev=v2*fac
iset=1
else
gasdev=gset
iset=0
end if
end function gasdev
subroutine apply_perturbation
!This subroutine applies the appropriate perturbation to the interpolated atoms of an element
integer :: ie, iatom, virt_count, iatomap, ibasis
real(kind = wp) :: bvec(3,3), r1, r2, r3, v(3), max_disp
max_disp = 0
do ie = 1, ele_num_l
select case(etype(ie))
case(1,2)
virt_count = (size_ele(ie)+1)**3
end select
!First calculate the lattice basis vectors from the element nodal positions
bvec(:,1) = r(:,1,cg_node(2,ie)) - r(:,1, cg_node(1,ie))
bvec(:,2) = r(:,1,cg_node(4,ie)) - r(:,1, cg_node(1,ie))
bvec(:,3) = r(:,1,cg_node(5,ie)) - r(:,1, cg_node(1,ie))
!Now normalize the lattice vectors
bvec(:,1) = bvec(:,1)/norm2(bvec(:,1))
bvec(:,2) = bvec(:,2)/norm2(bvec(:,2))
bvec(:,3) = bvec(:,3)/norm2(bvec(:,3))
!Loop over all interpolated atoms
do iatom = 1, virt_count
do ibasis = 1, basis_num(ie)
iatomap = cg_atomap(basis_num(ie)*(iatom-1) + ibasis, ie)
if (iatomap /= 0) then
!Get 3 random numbers
r1 = gasdev()
r2 = gasdev()
r3 = gasdev()
!Now scale the lattice vectors by the gaussian number
v = 0.0_wp
v = v+r1*bvec(:,1)
v = v+r2*bvec(:,2)
v = v+r3*bvec(:,3)
!Add the vectors together to get the perturbation vector and then scale the magnitude to the mean sqaured
!oscillation magnitude
v = v*(urms(type_atomap(iatomap))/norm2(v))
max_disp = norm2(v)
r_atomap(:, iatomap) = r_atomap(:, iatomap) + v
end if
end do
end do
end do
end subroutine apply_perturbation
end module temp

165
src/thermo.f90 Normal file
View file

@ -0,0 +1,165 @@
module thermo
!this is similar to the lammps thermo output which logs simulation information
use elements
use forces
use compute
use str
implicit none
integer :: thermo_every, thermo_style_num
character(len=read_len) :: thermo_style(100)
logical :: need_k, need_t, need_p
public
contains
subroutine thermo_defaults
thermo_every = huge(1)
thermo_style_num = 2
thermo_style(1) = "pe"
thermo_style(2) = "fnorm"
need_k = .false.
need_t = .false.
need_p = .false.
end subroutine thermo_defaults
subroutine parse_thermo_style(line)
!This subroutine parses the thermo style command
character(len=*), intent(in) :: line
integer :: iospara, i
character(len=read_len) :: tmptxt
thermo_style_num = tok_count(line)-1
need_k = .false.
need_t = .false.
need_p = .false.
read(line, *, iostat=iospara) tmptxt, (thermo_style(i), i=1, thermo_style_num)
!Now loop over all of the thermo_style items to make sure that they are acceptable
do i= 1, thermo_style_num
select case(thermo_style(i))
case('pe','fnorm', 'lx', 'ly', 'lz')
continue
case('ke','ke_c','ke_a')
need_k = .true.
case('temp', 'temp_c', 'temp_a')
need_k = .true.
need_t = .true.
case('px', 'py', 'pz')
need_p = .true.
case default
write(tmptxt, *) trim(adjustl(thermo_style(i))), " is not an acceptable thermostyle command, please check " //&
" the documentation for acceptable commands."
end select
end do
end subroutine parse_thermo_style
subroutine parse_thermo(line)
!This subroutine parses the thermo command
character(len=*) :: line
character(len = read_len) :: tmptxt
integer :: iospara
read(line, *, iostat = iospara) tmptxt, thermo_every
if (iospara > 0) call read_error("Failure to read thermo command", iospara)
!Make sure thermo_every is greater than 0
if(thermo_every < 0) call misc_error("Thermo_every should be greater than 0 in thermo command")
if(thermo_every == 0) then
thermo_every = huge(1)
end if
end subroutine parse_thermo
subroutine write_thermo_style
integer :: i
character(len=read_len) :: style_out
write(style_out, *) "thermo: ", (trim(adjustl(thermo_style(i)))//" ", i = 1, thermo_style_num)
call log_msg(style_out, 0)
end subroutine write_thermo_style
subroutine write_thermo_out(time)
!Preliminary write thermo out
integer, intent(in) :: time
integer :: i
real(kind=wp) :: ke(2), T(3), thermo_out(100), P(3,3)
character(len=read_len) :: msg
thermo_out = 0.0_wp
!Calculate temp, ke, and press if needed
if(need_k) then
if(need_vel) then
ke = compute_ke(1)
else
ke = 0.0_wp
end if
end if
if(need_t) then
if(need_vel) then
T = compute_temp(1, ke)
else
T = 0.0_wp
end if
end if
if(need_p) P = compute_box_press()
!Loop over all thermo_style commands and put the desired outputs into thermo_out
do i= 1, thermo_style_num
select case(thermo_style(i))
case('pe')
!Get pe all
thermo_out(i) = compute_pe(1)
case('fnorm')
!Get fnorm all
thermo_out(i) = compute_fnorm(1)
case('ke')
thermo_out(i) = ke(1) + ke(2)
case('ke_a')
thermo_out(i) = ke(1)
case('ke_c')
thermo_out(i) = ke(2)
case('temp')
thermo_out(i) = T(1)
case('temp_a')
thermo_out(i) = T(2)
case('temp_c')
thermo_out(i) = T(3)
case('lx')
thermo_out(i) = box_bd(2) - box_bd(1)
case('ly')
thermo_out(i) = box_bd(4) - box_bd(3)
case('lz')
thermo_out(i) = box_bd(6) - box_bd(5)
case('px')
thermo_out(i) = P(1,1)
case('py')
thermo_out(i) = P(2,2)
case('pz')
thermo_out(i) = P(3,3)
end select
end do
if(rank == root) then
write(msg,*) time, thermo_out(1:thermo_style_num)
call log_msg(msg)
end if
return
end subroutine write_thermo_out
end module thermo

50
src/time.f90 Normal file
View file

@ -0,0 +1,50 @@
module time
use parameters
use logger
use errors
use mpi
implicit none
!This subroutine contains variables and subroutines associated with timestepping
!also contains code for timing
integer :: itime_start, iter, run_steps, begin_step
real(kind=wp) :: t, time_step, orig_time_step
real(kind=wp) :: walltime(2), program_start, program_end
public
contains
subroutine time_defaults
t = 0.0_wp
time_step = 0.001_wp
iter = 0
orig_time_step = 0.001_wp
walltime=0.0_wp
end subroutine time_defaults
subroutine parse_timestep(line)
character(len=*), intent(in) :: line
character(len=read_len) :: tmptxt
integer :: iospara
read(line, *, iostat = iospara) tmptxt, time_step
if(time_step <= 0.0_wp) then
call misc_error("time step must be greater than 0")
end if
orig_time_step = time_step
end subroutine parse_timestep
subroutine log_time
character(len=read_len) :: msg
program_end = mpi_wtime()
write(msg, *) walltime(1), " spent neighboring and ", walltime(2), " spent computing forces"
call log_msg(msg)
write(msg, *) "Total time is:", program_end - program_start
call log_msg(msg)
end subroutine log_time
end module time

109
src/vel_verlet.f90 Normal file
View file

@ -0,0 +1,109 @@
module vel_verlet
!This module contains all the code for running steps of the verlet algorithm
!This is equivalent to the NVE ensemble
use parameters
use comms
use elements
use neighbors
use potential
use time
implicit none
public
contains
subroutine verlet(i)
integer, intent(in) :: i
!This subroutine advances the model by 1 timestep using the velocity verlet algorithm
!Update position at time i+1
call update_r
!Update neighbor at time i+1
call update_neighbor(i)
!Update neighbor at time i+1
call update_force
!Update velocity at time i+1
call update_vel(time_step)
end subroutine verlet
subroutine update_r
!This subroutine updates the positions for all atoms and coarse-grained element nodes
integer :: ia, ip, ibasis
if(ele_num /= 0) then
do ip = 1, node_num_l
do ibasis = 1, basis_num(node_cg(ip))
r(:,ibasis,ip) = r(:, ibasis, ip) + vel(:, ibasis, ip) * time_step &
+ 0.5_wp * force_eq(:, ibasis, ip)/ masses(basis_type(ibasis,node_cg(ip))) &
* time_step**2.0_wp / const_motion
end do
end do
force_eq_pre(:,:,1:node_num_l) = force_eq(:,:,1:node_num_l)
end if
if(atom_num /= 0) then
do ia = 1, atom_num_l
r_atom(:,ia) = r_atom(:,ia) + vel_atom(:, ia)*time_step + 0.5_wp*force_atom(:,ia)/masses(type_atom(ia)) &
* time_step**2.0_wp / const_motion
end do
force_atom_pre(:,1:atom_num_l) = force_atom(:, 1:atom_num_l)
end if
return
end subroutine update_r
subroutine update_vel(time_step)
real(kind=wp), intent(in) :: time_step
!update the velocity
integer :: ie, inod, ip, ia, ibasis
real(kind = wp) :: pro_force_normsq, avg(3)
pro_force_normsq = 0.0_wp
if(ele_num /= 0) then
do ie = 1, ele_num_l
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
vel(:, ibasis, ip) = vel(:, ibasis, ip) + 0.5_wp * (force_eq_pre(:, ibasis, ip) &
+ force_eq(:, ibasis, ip)) / masses(basis_type(ibasis,ie)) &
* time_step / const_motion
end do
end do
end do
end if
!atomistic domain
if(atom_num /= 0) then
do ia = 1, atom_num_l
vel_atom(:, ia) = vel_atom(:, ia) + 0.5_wp * (force_atom_pre(:, ia) &
+ force_atom(:, ia)) / masses(type_atom(ia)) &
* time_step / const_motion
end do
end if
!Now take out the average velocity
avg = compute_avgvel(1)
if(ele_num > 0) then
do ie = 1, ele_num_l
do inod = 1, ng_node(etype(ie))
ip = cg_node(inod, ie)
do ibasis = 1, basis_num(ie)
vel(:, ibasis, ip) = vel(:, ibasis, ip) - avg
end do
end do
end do
end if
!atomistic domain
if(atom_num /= 0) then
do ia = 1, atom_num_l
vel_atom(:, ia) = vel_atom(:, ia) - avg
end do
end if
end subroutine update_vel
end module vel_verlet