1
1
.. _chapter1-label :
2
2
3
- Start coding
3
+ Start Coding
4
4
============
5
5
6
+ Let's start with the Python script. During this first chapter, some Python
7
+ files will be created and filled in a minimal fashion. At the end of this
8
+ chapter, a small test will be set up to ensure that the files were correctly
9
+ created.
10
+
6
11
Presentation
7
12
------------
8
13
@@ -34,94 +39,89 @@ containing either Python functions or classes:
34
39
35
40
* - File Name
36
41
- Content
37
- * - * Prepare.py *
42
+ * - Prepare.py
38
43
- *Prepare * class: Methods for preparing the non-dimensionalization of the
39
44
units
40
- * - *Utilities.py *
41
- - *Utilities * class: General-purpose methods, inherited by all other classes
42
- * - *InitializeSimulation.py *
45
+ * - Utilities.py
46
+ - *Utilities * class: General-purpose methods, inherited by all other
47
+ classes
48
+ * - InitializeSimulation.py
43
49
- *InitializeSimulation * class: Methods necessary to set up the system and
44
50
prepare the simulation, inherited by all the classes below
45
- * - * MinimizeEnergy.py *
51
+ * - MinimizeEnergy.py
46
52
- *MinimizeEnergy * class: Methods for performing energy minimization
47
- * - * MonteCarlo.py *
53
+ * - MonteCarlo.py
48
54
- *MonteCarlo * class: Methods for performing Monte Carlo simulations in
49
55
different ensembles (e.g., Grand Canonical, Canonical)
50
- * - * MolecularDynamics.py *
56
+ * - MolecularDynamics.py
51
57
- *MolecularDynamics * class: Methods for performing molecular dynamics in
52
58
different ensembles (NVE, NPT, NVT)
53
- * - * measurements .py*
54
- - Functions for performing specific measurements on the system
59
+ * - Measurements .py
60
+ - * Measurements * class: Methods for for performing specific measurements on the system
55
61
* - *potentials.py *
56
62
- Functions for calculating the potentials and forces between atoms
57
- * - * tools .py*
63
+ * - logger .py
58
64
- Functions for outputting data into text files
65
+ * - dumper.py
66
+ - Functions for outputting data into trajectory files for visualization
67
+ * - reader.py
68
+ - Functions for importing data from text files
59
69
70
+ Some of these files are created in this chapter; others will be created later
71
+ on. All of these files must be created within the same folder.
60
72
61
- Potential for inter-atomic interaction
73
+ Potential for Inter-Atomic Interaction
62
74
--------------------------------------
63
75
64
- In molecular simulations, potential functions are used to mimic the interaction
65
- between atoms. Although more complicated options exist, potentials are usually
76
+ In molecular simulations, potential functions are used to model the interaction
77
+ between atoms. Although more complex options exist, potentials are usually
66
78
defined as functions of the distance :math: `r` between two atoms.
67
79
68
- Within a dedicated folder, create the first file named *potentials.py *. This
69
- file will contain a function called *potentials *. Two types of potential can
70
- be returned by this function: the Lennard-Jones potential (LJ), and the
71
- hard-sphere potential.
80
+ Create a file named *potentials.py *. This file will contain a function called
81
+ *potentials *. For now, the only potential that can be returned by this function
82
+ is the Lennard-Jones (LJ) potential, but this may change in the future.
72
83
73
84
Copy the following lines into *potentials.py *:
74
85
75
86
.. label :: start_potentials_class
76
87
77
88
.. code-block :: python
78
89
79
- import numpy as np
80
-
81
- def potentials (potential_type , epsilon , sigma , r , derivative = False ):
82
- if potential_type == " Lennard-Jones" :
83
- if derivative:
84
- return 48 * epsilon * ((sigma / r) ** 12 - 0.5 * (sigma / r) ** 6 ) / r
85
- else :
86
- return 4 * epsilon * ((sigma / r) ** 12 - (sigma / r) ** 6 )
87
- elif potential_type == " Hard-Sphere" :
88
- if derivative:
89
- # Derivative is not defined for Hard-Sphere potential.
90
- # --> return 0
91
- return np.zeros(len (r))
92
- else :
93
- return np.where(r > sigma, 0 , 1000 )
90
+ def potentials (epsilon , sigma , r , derivative = False ):
91
+ if derivative:
92
+ return 48 * epsilon * ((sigma / r) ** 12 - 0.5 * (sigma / r) ** 6 ) / r
94
93
else :
95
- raise ValueError ( f " Unknown potential type: { potential_type } " )
94
+ return 4 * epsilon * ((sigma / r) ** 12 - (sigma / r) ** 6 )
96
95
97
96
.. label :: end_potentials_class
98
97
99
- The hard-sphere potential either returns a value of 0 when the distance between
100
- the two particles is larger than the parameter, :math: `r > \sigma `, or 1000 when
101
- :math: `r < \sigma `. The value of *1000 * was chosen to be large enough to ensure
102
- that any Monte Carlo move that would lead to the two particles to overlap will
103
- be rejected.
104
-
105
- In the case of the LJ potential, depending on the value of the optional
106
- argument *derivative *, which can be either *False * or *True *, the *LJ_potential *
107
- function will return the force:
98
+ Depending on the value of the optional argument *derivative *, which can be
99
+ either *False * or *True *, this function returns the derivative of the potential,
100
+ i.e., the force, :math: `F_\text {LJ} = - \mathrm {d} U_\text {LJ} / \mathrm {d} r`:
108
101
109
102
.. math ::
110
103
111
- F_\text {LJ} = 48 \dfrac {\epsilon }{r} \left [ \left ( \frac {\sigma }{r} \right )^{12 } - \frac {1 }{2 } \left ( \frac {\sigma }{r} \right )^6 \right ],
104
+ F_\text {LJ} = 48 \dfrac {\epsilon }{r} \left [ \left ( \frac {\sigma }{r} \right )^{12 }
105
+ - \frac {1 }{2 } \left ( \frac {\sigma }{r} \right )^6 \right ], ~ \text {for} ~ r < r_\text {c},
112
106
113
107
or the potential energy:
114
108
115
109
.. math ::
116
110
117
- U_\text {LJ} = 4 \epsilon \left [ \left ( \frac {\sigma }{r} \right )^{12 } - \left ( \frac {\sigma }{r} \right )^6 \right ].
111
+ U_\text {LJ} = 4 \epsilon \left [ \left ( \frac {\sigma }{r} \right )^{12 }
112
+ - \left ( \frac {\sigma }{r} \right )^6 \right ], ~ \text {for} ~ r < r_\text {c}.
113
+
114
+ Here, :math: `\sigma ` is the distance at which the potential :math: `U_\text {LJ}`
115
+ is zero, :math: `\epsilon ` is the depth of the potential well, and
116
+ :math: `r_\text {c}` is a cutoff distance. For :math: `r > r_\text {c}`,
117
+ :math: `U_\text {LJ} = 0 ` and :math: `F_\text {LJ} = 0 `.
118
118
119
119
Create the Classes
120
120
------------------
121
121
122
- Let's create the files with the minimal information about the classes and
123
- their inheritance. The classes will be developed progressively in the
124
- following chapters.
122
+ Let's create the files with some minimal details about the classes and their
123
+ inheritance. The classes will be developed progressively in the following
124
+ chapters.
125
125
126
126
The first class is the *Prepare * class, which will be used for the
127
127
nondimensionalization of the parameters. In the same folder as *potentials.py *,
@@ -157,20 +157,22 @@ copy the following lines:
157
157
158
158
.. label :: end_Utilities_class
159
159
160
- The line *from potentials import LJ_potential * is used to import the
161
- * LJ_potential * function.
160
+ The line *from potentials import potentials * is used to import the
161
+ previously created * potentials * function.
162
162
163
163
Within the *InitializeSimulation.py * file, copy the following lines:
164
164
165
165
.. label :: start_InitializeSimulation_class
166
166
167
167
.. code-block :: python
168
168
169
+ import os
169
170
import numpy as np
170
171
from Prepare import Prepare
172
+ from Utilities import Utilities
171
173
172
174
173
- class InitializeSimulation (Prepare ):
175
+ class InitializeSimulation (Prepare , Utilities ):
174
176
def __init__ (self ,
175
177
* args ,
176
178
** kwargs ,
@@ -180,39 +182,50 @@ Within the *InitializeSimulation.py* file, copy the following lines:
180
182
.. label :: end_InitializeSimulation_class
181
183
182
184
The *InitializeSimulation * class inherits from the previously created
183
- *Prepare * class. Additionally, we anticipate that *NumPy * will be required.
185
+ *Prepare * and *Utilities * classes. Additionally, we anticipate that |NumPy |
186
+ will be required :cite: `harris2020array `. We also anticipate that the *os *
187
+ module, which provides a way to interact with the operating system, will
188
+ be required :cite: `Rossum2009Python3 `.
189
+
190
+ .. |NumPy | raw :: html
191
+
192
+ <a href="https://numpy.org/" target="_blank">NumPy</a>
184
193
185
194
Within the *Measurements.py * file, copy the following lines:
186
195
187
196
.. label :: start_Measurements_class
188
197
189
198
.. code-block :: python
190
199
200
+ import numpy as np
191
201
from InitializeSimulation import InitializeSimulation
192
- from Utilities import Utilities
193
202
194
203
195
- class Measurements (InitializeSimulation , Utilities ):
204
+ class Measurements (InitializeSimulation ):
196
205
def __init__ (self ,
197
206
* args ,
198
207
** kwargs ):
199
208
super ().__init__ (* args, ** kwargs)
200
209
201
210
.. label :: end_Measurements_class
202
211
203
- The *Measurements * class inherits both the *InitializeSimulation * and
204
- *Utilities * classes.
212
+ The *Measurements * class inherits from *InitializeSimulation * (and thus
213
+ also inherits from the *Prepare * and *Utilities * classes).
214
+
215
+ Finally, let us create the three remaining classes: *MinimizeEnergy *,
216
+ *MonteCarlo *, and *MolecularDynamics *. Each of these classes inherits
217
+ from the *Measurements * class (and thus also from the *Prepare *, *Utilities *,
218
+ and *InitializeSimulation * classes).
205
219
206
- Finally, let us create the three remaining classes, named *MinimizeEnergy *,
207
- *MonteCarlo *, and *MolecularDynamics *. Each of these three classes inherits
208
- from the *Measurements * class, and thus from the classes inherited by
209
- *Measurements *. Within the *MinimizeEnergy.py * file, copy the following lines:
220
+ Within the *MinimizeEnergy.py * file, copy the following lines:
210
221
211
222
.. label :: start_MinimizeEnergy_class
212
223
213
224
.. code-block :: python
214
225
215
226
from Measurements import Measurements
227
+ import numpy as np
228
+ import copy
216
229
import os
217
230
218
231
@@ -224,19 +237,17 @@ from the *Measurements* class, and thus from the classes inherited by
224
237
225
238
.. label :: end_MinimizeEnergy_class
226
239
227
- We anticipate that the * os * module , which provides a way to interact with the
228
- operating system, will be required :cite: ` Rossum2009Python3 ` .
240
+ The * copy * library , which provides functions to create shallow or deep copies of
241
+ objects, is imported, along with * NumPy * and * os * .
229
242
230
243
Within the *MonteCarlo.py * file, copy the following lines:
231
244
232
245
.. label :: start_MonteCarlo_class
233
246
234
247
.. code-block :: python
235
248
236
- from scipy import constants as cst
237
249
import numpy as np
238
250
import copy
239
- import os
240
251
from Measurements import Measurements
241
252
242
253
import warnings
@@ -251,12 +262,10 @@ Within the *MonteCarlo.py* file, copy the following lines:
251
262
252
263
.. label :: end_MonteCarlo_class
253
264
254
- Several libraries were imported, namely *Constants * from *SciPy *, *NumPy *, *copy *
255
- and *os *.
256
-
257
- The *warnings * was placed to avoid the anoying message "*RuntimeWarning: overflow
258
- encountered in exp *" that is sometimes triggered by the exponential of the
259
- *acceptation_probability * (see :ref: `chapter6-label `).
265
+ The *ignore warnings * commands are optional; they were added to avoid the
266
+ annoying message "*RuntimeWarning: overflow encountered in exp *" that is sometimes
267
+ triggered by the exponential function of *acceptation_probability * (see the
268
+ :ref: `chapter6-label ` chapter).
260
269
261
270
Finally, within the *MolecularDynamics.py * file, copy the following lines:
262
271
@@ -294,12 +303,14 @@ and copy the following lines into it:
294
303
295
304
# Make sure that MonteCarlo correctly inherits from Utilities
296
305
def test_montecarlo_inherits_from_utilities ():
297
- assert issubclass (MonteCarlo, Utilities), " MonteCarlo should inherit from Utilities"
306
+ assert issubclass (MonteCarlo, Utilities), \
307
+ " MonteCarlo should inherit from Utilities"
298
308
print (" MonteCarlo correctly inherits from Utilities" )
299
309
300
310
# Make sure that Utilities does not inherit from MonteCarlo
301
311
def test_utilities_does_not_inherit_from_montecarlo ():
302
- assert not issubclass (Utilities, MonteCarlo), " Utilities should not inherit from MonteCarlo"
312
+ assert not issubclass (Utilities, MonteCarlo), \
313
+ " Utilities should not inherit from MonteCarlo"
303
314
print (" Utilities does not inherit from MonteCarlo, as expected" )
304
315
305
316
# In the script is launched with Python, call Pytest
@@ -317,15 +328,15 @@ any *AssertionError*:
317
328
Utilities does not inherit from MonteCarlo, as expected
318
329
MonteCarlo correctly inherits from Utilities
319
330
320
- Alternatively, this test can also be launched using Pytest by typing in a terminal:
331
+ Alternatively, this test can also be launched using * Pytest * by typing in a terminal:
321
332
322
333
.. code-block :: bash
323
334
324
335
pytest .
325
336
326
- We can also test that calling the *__init__ *
327
- method of the * MonteCarlo * class does not return any error. In new Python file
328
- called * test_1b.py *, copy the following lines:
337
+ We can also test that calling the *__init__ * method of the * MonteCarlo * class
338
+ does not return any error. In new Python file called * test_1b.py *, copy the
339
+ following lines:
329
340
330
341
.. label :: start_test_1b_class
331
342
0 commit comments