├── 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 | --------------------------------------------------------------------------------