Skip to content

Commit 6a53083

Browse files
Merge pull request #11 from mdcourse/add-gcmc
Add GCMC to doc + simplify energy non-dimentionalisation
2 parents ee3c275 + 8951d8b commit 6a53083

11 files changed

+1079
-841
lines changed

docs/source/chapters/chapter1.rst

+87-76
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
.. _chapter1-label:
22

3-
Start coding
3+
Start Coding
44
============
55

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+
611
Presentation
712
------------
813

@@ -34,94 +39,89 @@ containing either Python functions or classes:
3439

3540
* - File Name
3641
- Content
37-
* - *Prepare.py*
42+
* - Prepare.py
3843
- *Prepare* class: Methods for preparing the non-dimensionalization of the
3944
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
4349
- *InitializeSimulation* class: Methods necessary to set up the system and
4450
prepare the simulation, inherited by all the classes below
45-
* - *MinimizeEnergy.py*
51+
* - MinimizeEnergy.py
4652
- *MinimizeEnergy* class: Methods for performing energy minimization
47-
* - *MonteCarlo.py*
53+
* - MonteCarlo.py
4854
- *MonteCarlo* class: Methods for performing Monte Carlo simulations in
4955
different ensembles (e.g., Grand Canonical, Canonical)
50-
* - *MolecularDynamics.py*
56+
* - MolecularDynamics.py
5157
- *MolecularDynamics* class: Methods for performing molecular dynamics in
5258
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
5561
* - *potentials.py*
5662
- Functions for calculating the potentials and forces between atoms
57-
* - *tools.py*
63+
* - logger.py
5864
- 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
5969

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.
6072

61-
Potential for inter-atomic interaction
73+
Potential for Inter-Atomic Interaction
6274
--------------------------------------
6375

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
6678
defined as functions of the distance :math:`r` between two atoms.
6779

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.
7283

7384
Copy the following lines into *potentials.py*:
7485

7586
.. label:: start_potentials_class
7687

7788
.. code-block:: python
7889
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
9493
else:
95-
raise ValueError(f"Unknown potential type: {potential_type}")
94+
return 4 * epsilon * ((sigma / r) ** 12 - (sigma / r) ** 6)
9695
9796
.. label:: end_potentials_class
9897

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`:
108101

109102
.. math::
110103
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},
112106
113107
or the potential energy:
114108

115109
.. math::
116110
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`.
118118

119119
Create the Classes
120120
------------------
121121

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.
125125

126126
The first class is the *Prepare* class, which will be used for the
127127
nondimensionalization of the parameters. In the same folder as *potentials.py*,
@@ -157,20 +157,22 @@ copy the following lines:
157157
158158
.. label:: end_Utilities_class
159159

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.
162162

163163
Within the *InitializeSimulation.py* file, copy the following lines:
164164

165165
.. label:: start_InitializeSimulation_class
166166

167167
.. code-block:: python
168168
169+
import os
169170
import numpy as np
170171
from Prepare import Prepare
172+
from Utilities import Utilities
171173
172174
173-
class InitializeSimulation(Prepare):
175+
class InitializeSimulation(Prepare, Utilities):
174176
def __init__(self,
175177
*args,
176178
**kwargs,
@@ -180,39 +182,50 @@ Within the *InitializeSimulation.py* file, copy the following lines:
180182
.. label:: end_InitializeSimulation_class
181183

182184
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>
184193

185194
Within the *Measurements.py* file, copy the following lines:
186195

187196
.. label:: start_Measurements_class
188197

189198
.. code-block:: python
190199
200+
import numpy as np
191201
from InitializeSimulation import InitializeSimulation
192-
from Utilities import Utilities
193202
194203
195-
class Measurements(InitializeSimulation, Utilities):
204+
class Measurements(InitializeSimulation):
196205
def __init__(self,
197206
*args,
198207
**kwargs):
199208
super().__init__(*args, **kwargs)
200209
201210
.. label:: end_Measurements_class
202211

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).
205219

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:
210221

211222
.. label:: start_MinimizeEnergy_class
212223

213224
.. code-block:: python
214225
215226
from Measurements import Measurements
227+
import numpy as np
228+
import copy
216229
import os
217230
218231
@@ -224,19 +237,17 @@ from the *Measurements* class, and thus from the classes inherited by
224237
225238
.. label:: end_MinimizeEnergy_class
226239

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*.
229242

230243
Within the *MonteCarlo.py* file, copy the following lines:
231244

232245
.. label:: start_MonteCarlo_class
233246

234247
.. code-block:: python
235248
236-
from scipy import constants as cst
237249
import numpy as np
238250
import copy
239-
import os
240251
from Measurements import Measurements
241252
242253
import warnings
@@ -251,12 +262,10 @@ Within the *MonteCarlo.py* file, copy the following lines:
251262
252263
.. label:: end_MonteCarlo_class
253264

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).
260269

261270
Finally, within the *MolecularDynamics.py* file, copy the following lines:
262271

@@ -294,12 +303,14 @@ and copy the following lines into it:
294303
295304
# Make sure that MonteCarlo correctly inherits from Utilities
296305
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"
298308
print("MonteCarlo correctly inherits from Utilities")
299309
300310
# Make sure that Utilities does not inherit from MonteCarlo
301311
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"
303314
print("Utilities does not inherit from MonteCarlo, as expected")
304315
305316
# In the script is launched with Python, call Pytest
@@ -317,15 +328,15 @@ any *AssertionError*:
317328
Utilities does not inherit from MonteCarlo, as expected
318329
MonteCarlo correctly inherits from Utilities
319330
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:
321332

322333
.. code-block:: bash
323334
324335
pytest .
325336
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:
329340

330341
.. label:: start_test_1b_class
331342

0 commit comments

Comments
 (0)