├── MesoscopicDynamics
├── dump.py
└── langevin-mag.py
└── MolecularDynamics
├── dump.py
└── langevin.py
/MesoscopicDynamics/dump.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on September 22, 2018
3 | @author: Andrew Abi-Mansour
4 | '''
5 |
6 | # !/usr/bin/python
7 | # -*- coding: utf8 -*-
8 | # -------------------------------------------------------------------------
9 | #
10 | # A simple molecular dynamics solver that simulates the motion
11 | # of non-interacting particles in the canonical ensemble using
12 | # a Langevin thermostat.
13 | #
14 | # --------------------------------------------------------------------------
15 | #
16 | # This program is free software: you can redistribute it and/or modify
17 | # it under the terms of the GNU General Public License as published by
18 | # the Free Software Foundation, either version 2 of the License, or
19 | # (at your option) any later version.
20 |
21 | # This program is distributed in the hope that it will be useful,
22 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | # GNU General Public License for more details.
25 |
26 | # You should have received a copy of the GNU General Public License
27 | # along with this program. If not, see .
28 |
29 | # -------------------------------------------------------------------------
30 |
31 | import numpy as np
32 |
33 | def writeOutput(filename, natoms, timestep, box, **data):
34 | """ Writes the output (in dump format) """
35 |
36 | axis = ('x', 'y', 'z')
37 |
38 | with open(filename, 'a') as fp:
39 |
40 | fp.write('ITEM: TIMESTEP\n')
41 | fp.write('{}\n'.format(timestep))
42 |
43 | fp.write('ITEM: NUMBER OF ATOMS\n')
44 | fp.write('{}\n'.format(natoms))
45 |
46 | fp.write('ITEM: BOX BOUNDS' + ' f' * len(box) + '\n')
47 | for box_bounds in box:
48 | fp.write('{} {}\n'.format(*box_bounds))
49 |
50 | for i in range(len(axis) - len(box)):
51 | fp.write('0 0\n')
52 |
53 | keys = list(data.keys())
54 |
55 | for key in keys:
56 | isMatrix = len(data[key].shape) > 1
57 |
58 | if isMatrix:
59 | _, nCols = data[key].shape
60 |
61 | for i in range(nCols):
62 | if key == 'pos':
63 | data['{}'.format(axis[i])] = data[key][:,i]
64 | else:
65 | data['{}_{}'.format(key,axis[i])] = data[key][:,i]
66 |
67 | del data[key]
68 |
69 | keys = data.keys()
70 |
71 | fp.write('ITEM: ATOMS' + (' {}' * len(data)).format(*data) + '\n')
72 |
73 | output = []
74 | for key in keys:
75 | output = np.hstack((output, data[key]))
76 |
77 | if len(output):
78 | np.savetxt(fp, output.reshape((natoms, len(data)), order='F'))
79 |
--------------------------------------------------------------------------------
/MesoscopicDynamics/langevin-mag.py:
--------------------------------------------------------------------------------
1 | """
2 | Created on Wed Mar 18 12:55:05 2020
3 |
4 | @author: User
5 | """
6 |
7 | import numpy as np
8 | import matplotlib.pylab as plt
9 | import math
10 | import dump
11 |
12 | Avogadro = 6.02214076e23
13 | Boltzmann = 1.38064852e-23
14 |
15 | def wallHitCheck(pos, vels, box):
16 | """ This function enforces reflective boundary conditions.
17 | All particles that hit a wall have their velocity updated
18 | in the opposite direction.
19 | @pos: atomic positions (ndarray)
20 | @vels: atomic velocity (ndarray, updated if collisions detected)
21 | @box: simulation box size (tuple)
22 | """
23 | ndims = len(box)
24 |
25 | for i in range(ndims):
26 | vels[((pos[:,i] <= box[i][0]) | (pos[:,i] >= box[i][1])),i] *= -0.4
27 |
28 | def integrate(pos, vels, forces, mass, dt):
29 | """ A simple forward Euler integrator that moves the system in time
30 | @pos: atomic positions (ndarray, updated)
31 | @vels: atomic velocity (ndarray, updated)
32 | """
33 | pos += vels * dt
34 |
35 | vels += forces * dt / mass[np.newaxis].T
36 |
37 | def computeForce(pos, mass, vels, sepP, sepF, permFS, radius, temp, relax, dt, step):
38 | """ Computes the Langevin force for all particles
39 | @mass: particle mass (ndarray)
40 | @vels: particle velocities (ndarray)
41 | @temp: temperature (float)
42 | @relax: thermostat constant (float)
43 | @dt: simulation timestep (float)
44 | returns forces (ndarray)
45 | """
46 |
47 | natoms, ndims = vels.shape
48 |
49 | time = step * dt
50 |
51 | #Magnetic Force
52 | switch = 0.15e-11
53 | B0_og = 0.8e-3
54 |
55 | if switch <= time < 3*switch:
56 | B0 = (B0_og/switch)*time - B0_og
57 | f = -1
58 | elif 3*switch <= time:
59 | B0=0
60 | f =1
61 | else:
62 | B0 = -(B0_og/switch)*time + B0_og
63 | f = 1
64 | dis = 100*1e-8/500
65 | c = (((4/3)*math.pi*radius**3 * (sepP - sepF) * B0**2)/permFS)
66 | a = 1e-3
67 |
68 | magForce = (-1*f) * c[np.newaxis].T * (pos+dis)/((pos+dis)**2 + a**2)**4
69 | magForce[:, [0,2]] = 0
70 |
71 |
72 | #Brownian motion
73 |
74 | sigma = np.sqrt(2.0 * mass * temp * Boltzmann / (relax * dt))
75 |
76 | noise = np.random.randn(natoms, ndims) * sigma[np.newaxis].T
77 |
78 | #Viscous force
79 |
80 | visc = - (vels * mass[np.newaxis].T) / relax
81 |
82 | force = magForce + noise + visc
83 |
84 | return force
85 |
86 | def removeCOM(pos, mass):
87 | """ Removes center of mass motion. This function is not used. """
88 | pos -= np.dot(mass, pos) / mass.sum()
89 |
90 | def run(**args):
91 | """ This is the main function that solves Langevin's equations for
92 | a system of natoms usinga forward Euler scheme, and returns an output
93 | list that stores the time and the temperture.
94 |
95 | @natoms (int): number of particles
96 | @temp (float): temperature (in Kelvin)
97 | @mass (float): particle mass (in Kg)
98 | @relax (float): relaxation constant (in seconds)
99 | @dt (float): simulation timestep (s)
100 | @nsteps (int): total number of steps the solver performs
101 | @box (tuple): simulation box size (in meters) of size dimensions x 2
102 | e.g. box = ((-1e-9, 1e-9), (-1e-9, 1e-9)) defines a 2D square
103 | @ofname (string): filename to write output to
104 | @freq (int): write output every 'freq' steps
105 |
106 | @[radius]: particle radius (for visualization)
107 |
108 | Returns a list (of size nsteps x 2) containing the time and temperature.
109 |
110 | """
111 |
112 | natoms, dt, temp, box = args['natoms'], args['dt'], args['temp'], args['box']
113 | mass, relax, nsteps, radius = args['mass'], args['relax'], args['steps'], args['radius']
114 | sepP, sepF, permFS = args['sepP'], args['sepF'], args['permFS']
115 | ofname, freq = args['ofname'], args['freq']
116 |
117 | dim = len(box)
118 |
119 | #set to appear in a specific location of the box
120 | pos = np.random.rand(natoms,dim)
121 | for i in range(dim):
122 | if i == 0:
123 | pos[:,i] = box[0][1]/10
124 | if i == 1:
125 | pos[:,i] = box[1][1]/2
126 | if i == 2:
127 | pos[:,i] = 0
128 |
129 | if 'initial_vels' not in args:
130 | vels = np.random.rand(natoms,3)
131 | else:
132 | vels = np.ones((natoms,3)) * args['initial_vels']
133 |
134 | mass = np.ones(natoms) * mass #/ Avogadro #I define in grams in paras
135 | radius = np.ones(natoms) * radius
136 | step = 0
137 |
138 | output = []
139 |
140 |
141 | while step <= nsteps:
142 | step += 1
143 |
144 | # Compute all forces
145 | forces = computeForce(pos, mass, vels, sepP, sepF, permFS, radius, temp, relax, dt, step)
146 |
147 | # Move the system in time
148 | integrate(pos, vels, forces, mass, dt)
149 |
150 | # Check if any particle has collided with the wall
151 | wallHitCheck(pos,vels,box)
152 |
153 | # Compute output (temperature)
154 | ins_temp = np.sum(np.dot(mass, (vels - vels.mean(axis=0))**2)) / (Boltzmann * dim * natoms)
155 | output.append([step * dt, ins_temp])
156 |
157 | if not step%freq:
158 | dump.writeOutput(ofname, natoms, step, box, radius=radius, pos=pos, v=vels)
159 |
160 | return np.array(output)
161 |
162 | if __name__ == '__main__':
163 |
164 | # params = {
165 | # 'natoms': 10,
166 | # 'temp': 300,
167 | # 'mass': 0.001,
168 | # 'radius': 120e-12,
169 | # 'relax': 1e-13,
170 | # 'dt': 1e-15,
171 | # 'steps': 10000,
172 | # 'freq': 100,
173 | # 'box': ((0, 1e-8), (0, 1e-8), (0, 240e-12)),
174 | # 'sepP': 7200,
175 | # 'sepF': 0.712e-6,
176 | # 'permFS': math.pi*4e-7,
177 | # 'ofname': 'traj-hydrogen-3D.dump'
178 | # }
179 |
180 | """These are the new parameters that no longer cause an overload"""
181 | params = {
182 | 'natoms': 10,
183 | 'temp': 3e13,
184 | 'mass': 27e-12,
185 | 'radius': 50e-6,
186 | 'initial_vels': 1e-6,
187 | 'relax': 1e-6,
188 | 'dt': 1e-8,
189 | 'steps': 100000,
190 | 'freq': 1000,
191 | 'box': ((0, 1e-3), (0, 1e-3), (0, 1e-3)),
192 | 'sepP': 7200,
193 | 'sepF': 0.712e-6,
194 | 'permFS': math.pi*4e-7,
195 | 'ofname': 'traj-hydrogen-3D.dump'
196 | }
197 | output = run(**params)
198 |
199 | plt.plot(output[:,0], output[:,1])
200 | plt.xlabel('Time (ps)')
201 | plt.ylabel('Temp (K)')
202 | plt.show()
203 |
--------------------------------------------------------------------------------
/MolecularDynamics/dump.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on September 22, 2018
3 | @author: Andrew Abi-Mansour
4 | '''
5 |
6 | # !/usr/bin/python
7 | # -*- coding: utf8 -*-
8 | # -------------------------------------------------------------------------
9 | #
10 | # A simple molecular dynamics solver that simulates the motion
11 | # of non-interacting particles in the canonical ensemble using
12 | # a Langevin thermostat.
13 | #
14 | # --------------------------------------------------------------------------
15 | #
16 | # This program is free software: you can redistribute it and/or modify
17 | # it under the terms of the GNU General Public License as published by
18 | # the Free Software Foundation, either version 2 of the License, or
19 | # (at your option) any later version.
20 |
21 | # This program is distributed in the hope that it will be useful,
22 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | # GNU General Public License for more details.
25 |
26 | # You should have received a copy of the GNU General Public License
27 | # along with this program. If not, see .
28 |
29 | # -------------------------------------------------------------------------
30 |
31 | import numpy as np
32 |
33 | def writeOutput(filename, natoms, timestep, box, **data):
34 | """ Writes the output (in dump format) """
35 |
36 | axis = ('x', 'y', 'z')
37 |
38 | with open(filename, 'a') as fp:
39 |
40 | fp.write('ITEM: TIMESTEP\n')
41 | fp.write('{}\n'.format(timestep))
42 |
43 | fp.write('ITEM: NUMBER OF ATOMS\n')
44 | fp.write('{}\n'.format(natoms))
45 |
46 | fp.write('ITEM: BOX BOUNDS' + ' f' * len(box) + '\n')
47 | for box_bounds in box:
48 | fp.write('{} {}\n'.format(*box_bounds))
49 |
50 | for i in range(len(axis) - len(box)):
51 | fp.write('0 0\n')
52 |
53 | keys = list(data.keys())
54 |
55 | for key in keys:
56 | isMatrix = len(data[key].shape) > 1
57 |
58 | if isMatrix:
59 | _, nCols = data[key].shape
60 |
61 | for i in range(nCols):
62 | if key == 'pos':
63 | data['{}'.format(axis[i])] = data[key][:,i]
64 | else:
65 | data['{}_{}'.format(key,axis[i])] = data[key][:,i]
66 |
67 | del data[key]
68 |
69 | keys = data.keys()
70 |
71 | fp.write('ITEM: ATOMS' + (' {}' * len(data)).format(*data) + '\n')
72 |
73 | output = []
74 | for key in keys:
75 | output = np.hstack((output, data[key]))
76 |
77 | if len(output):
78 | np.savetxt(fp, output.reshape((natoms, len(data)), order='F'))
79 |
--------------------------------------------------------------------------------
/MolecularDynamics/langevin.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on September 22, 2018
3 | @author: Andrew Abi-Mansour
4 | '''
5 |
6 | # !/usr/bin/python
7 | # -*- coding: utf8 -*-
8 | # -------------------------------------------------------------------------
9 | #
10 | # A simple molecular dynamics solver that simulates the motion
11 | # of non-interacting particles in the canonical ensemble using
12 | # a Langevin thermostat.
13 | #
14 | # --------------------------------------------------------------------------
15 | #
16 | # This program is free software: you can redistribute it and/or modify
17 | # it under the terms of the GNU General Public License as published by
18 | # the Free Software Foundation, either version 2 of the License, or
19 | # (at your option) any later version.
20 |
21 | # This program is distributed in the hope that it will be useful,
22 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | # GNU General Public License for more details.
25 |
26 | # You should have received a copy of the GNU General Public License
27 | # along with this program. If not, see .
28 |
29 | # -------------------------------------------------------------------------
30 |
31 | import numpy as np
32 | import matplotlib.pylab as plt
33 | import dump
34 |
35 | # Define global physical constants
36 | Avogadro = 6.02214086e23
37 | Boltzmann = 1.38064852e-23
38 |
39 | def wallHitCheck(pos, vels, box):
40 | """ This function enforces reflective boundary conditions.
41 | All particles that hit a wall have their velocity updated
42 | in the opposite direction.
43 |
44 | @pos: atomic positions (ndarray)
45 | @vels: atomic velocity (ndarray, updated if collisions detected)
46 | @box: simulation box size (tuple)
47 | """
48 | ndims = len(box)
49 |
50 | for i in range(ndims):
51 | vels[((pos[:,i] <= box[i][0]) | (pos[:,i] >= box[i][1])),i] *= -1
52 |
53 | def integrate(pos, vels, forces, mass, dt):
54 | """ A simple forward Euler integrator that moves the system in time
55 |
56 | @pos: atomic positions (ndarray, updated)
57 | @vels: atomic velocity (ndarray, updated)
58 | """
59 |
60 | pos += vels * dt
61 | vels += forces * dt / mass[np.newaxis].T
62 |
63 | def computeForce(mass, vels, temp, relax, dt):
64 | """ Computes the Langevin force for all particles
65 |
66 | @mass: particle mass (ndarray)
67 | @vels: particle velocities (ndarray)
68 | @temp: temperature (float)
69 | @relax: thermostat constant (float)
70 | @dt: simulation timestep (float)
71 |
72 | returns forces (ndarray)
73 | """
74 |
75 | natoms, ndims = vels.shape
76 |
77 | sigma = np.sqrt(2.0 * mass * temp * Boltzmann / (relax * dt))
78 | noise = np.random.randn(natoms, ndims) * sigma[np.newaxis].T
79 |
80 | force = - (vels * mass[np.newaxis].T) / relax + noise
81 |
82 | return force
83 |
84 | def removeCOM(pos, mass):
85 | """ Removes center of mass motion. This function is not used. """
86 | pos -= np.dot(mass, pos) / mass.sum()
87 |
88 | def run(**args):
89 | """ This is the main function that solves Langevin's equations for
90 | a system of natoms usinga forward Euler scheme, and returns an output
91 | list that stores the time and the temperture.
92 |
93 | @natoms (int): number of particles
94 | @temp (float): temperature (in Kelvin)
95 | @mass (float): particle mass (in Kg)
96 | @relax (float): relaxation constant (in seconds)
97 | @dt (float): simulation timestep (s)
98 | @nsteps (int): total number of steps the solver performs
99 | @box (tuple): simulation box size (in meters) of size dimensions x 2
100 | e.g. box = ((-1e-9, 1e-9), (-1e-9, 1e-9)) defines a 2D square
101 | @ofname (string): filename to write output to
102 | @freq (int): write output every 'freq' steps
103 |
104 | @[radius]: particle radius (for visualization)
105 |
106 | Returns a list (of size nsteps x 2) containing the time and temperature.
107 |
108 | """
109 |
110 | natoms, box, dt, temp = args['natoms'], args['box'], args['dt'], args['temp']
111 | mass, relax, nsteps = args['mass'], args['relax'], args['steps']
112 | ofname, freq, radius = args['ofname'], args['freq'], args['radius']
113 |
114 | dim = len(box)
115 | pos = np.random.rand(natoms,dim)
116 |
117 | for i in range(dim):
118 | pos[:,i] = box[i][0] + (box[i][1] - box[i][0]) * pos[:,i]
119 |
120 | vels = np.random.rand(natoms,dim)
121 | mass = np.ones(natoms) * mass / Avogadro
122 | radius = np.ones(natoms) * radius
123 | step = 0
124 |
125 | output = []
126 |
127 | while step <= nsteps:
128 |
129 | step += 1
130 |
131 | # Compute all forces
132 | forces = computeForce(mass, vels, temp, relax, dt)
133 |
134 | # Move the system in time
135 | integrate(pos, vels, forces, mass, dt)
136 |
137 | # Check if any particle has collided with the wall
138 | wallHitCheck(pos,vels,box)
139 |
140 | # Compute output (temperature)
141 | ins_temp = np.sum(np.dot(mass, (vels - vels.mean(axis=0))**2)) / (Boltzmann * dim * natoms)
142 | output.append([step * dt, ins_temp])
143 |
144 | if not step%freq:
145 | dump.writeOutput(ofname, natoms, step, box, radius=radius, pos=pos, v=vels)
146 |
147 | return np.array(output)
148 |
149 | if __name__ == '__main__':
150 |
151 | params = {
152 | 'natoms': 1000,
153 | 'temp': 300,
154 | 'mass': 0.001,
155 | 'radius': 120e-12,
156 | 'relax': 1e-13,
157 | 'dt': 1e-15,
158 | 'steps': 10000,
159 | 'freq': 100,
160 | 'box': ((0, 1e-8), (0, 1e-8), (0, 1e-8)),
161 | 'ofname': 'traj-hydrogen-3D.dump'
162 | }
163 |
164 | output = run(**params)
165 |
166 | plt.plot(output[:,0] * 1e12, output[:,1])
167 | plt.xlabel('Time (ps)')
168 | plt.ylabel('Temp (K)')
169 | plt.show()
170 |
--------------------------------------------------------------------------------