Skip to content

Commit dbae49f

Browse files
committed
improved descripition chapter 3
1 parent 277678f commit dbae49f

File tree

3 files changed

+73
-64
lines changed

3 files changed

+73
-64
lines changed

docs/source/chapters/chapter3.rst

Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ Initialize the simulation
1515
:align: right
1616
:class: only-light
1717

18-
Here, the *InitializeSimulation* class is created. This class is used to
19-
prepare the simulation box and populate the box randomly with atoms.
20-
Let us improve the previously created *InitializeSimulation* class:
18+
Here, the *InitializeSimulation* class is completed. This class is used to
19+
prepare the simulation box and populate it randomly with atoms. Improve the
20+
previously created *InitializeSimulation* class as follows:
2121

2222
.. label:: start_InitializeSimulation_class
2323

@@ -43,30 +43,20 @@ Let us improve the previously created *InitializeSimulation* class:
4343

4444
Several parameters are provided to the *InitializeSimulation* class:
4545

46-
- The first parameter is the box dimensions, which is a list with a length
47-
corresponding to the dimension of the system. Each element of the list
48-
corresponds to a dimension of the box in Ångström in the x, y, and z
49-
directions, respectively.
50-
- Optionally, a seed can be provided as an integer. If the seed is given
51-
by the user, the random number generator will always produce the same
52-
displacements.
46+
- The box dimensions, which is a list with 3 elements. Each element of
47+
the list corresponds to a dimension of the box in Ångström in the :math:`x`,
48+
:math:`y`, and :math:`z`` directions, respectively.
49+
- The cutoff :math:`r_\text{c}` for the potential interaction.
5350
- Optionally, initial positions for the atoms can be provided as an array
5451
of length corresponding to the number of atoms. If *initial_positions*
55-
is left equal to *None*, positions will be randomly assigned to the
56-
atoms (see below).
57-
- A cut off value with a default of 10 Ångströms is defined.
58-
- The *neighbor* parameter determines the interval between recalculations of
59-
the neighbor lists. By default, a value of 1 is used, meaning that neighbor
60-
list will be reconstructed every steps. In certain cases, the number will be
61-
increased to recude the computation time.
62-
63-
..
64-
- Optionally, a thermo period and a dumping_period can be provided to # TOFIX to be added later
65-
control the outputs from the simulation (it will be implemented # TOFIX to be added later
66-
in :ref:`chapter5-label`). # TOFIX to be added later
67-
68-
Finally, the *dimensions* of the system are calculated as the length of
69-
*box_dimensions*.
52+
is set to *None*, positions will be randomly assigned to the atoms (see
53+
below).
54+
- The *neighbor* parameter determines the interval between recalculations
55+
of the neighbor lists. By default, a value of 1 is used, meaning that the
56+
neighbor list will be reconstructed every step. In certain cases, this
57+
number may be increased to reduce computation time.
58+
59+
Note that the simulation step is initialized to 0.
7060

7161
Nondimensionalize units
7262
-----------------------
@@ -89,8 +79,8 @@ parameters, here the *box_dimensions*, the *cut_off*, as well as the *initial_po
8979
Define the box
9080
--------------
9181

92-
Let us define the simulation box using the values from *box_dimensions*. Add the following
93-
method to the *InitializeSimulation* class:
82+
Let us define the simulation box using the values from *box_dimensions*. Add the
83+
following method to the *InitializeSimulation* class:
9484

9585
.. label:: start_InitializeSimulation_class
9686

@@ -99,10 +89,8 @@ method to the *InitializeSimulation* class:
9989
def define_box(self):
10090
"""Define the simulation box. Only 3D boxes are supported."""
10191
box_boundaries = np.zeros((3, 2))
102-
dim = 0
103-
for L in self.box_dimensions:
92+
for dim, L in enumerate(self.box_dimensions):
10493
box_boundaries[dim] = -L/2, L/2
105-
dim += 1
10694
self.box_boundaries = box_boundaries
10795
box_size = np.diff(self.box_boundaries).reshape(3)
10896
box_geometry = np.array([90, 90, 90])
@@ -113,12 +101,16 @@ method to the *InitializeSimulation* class:
113101
The *box_boundaries* are calculated from the *box_dimensions*. They
114102
represent the lowest and highest coordinates in all directions. By symmetry,
115103
the box is centered at 0 along all axes. A *box_size* is also defined,
116-
following the MDAnalysis conventions: Lx, Ly, Lz, 90, 90, 90, where the
117-
last three numbers are angles in degrees. Values different from *90* for
118-
the angles would define a triclinic (non-orthogonal) box, which is not
119-
currently supported by the existing code.
104+
following the MDAnalysis |mdanalysis_box|: Lx, Ly, Lz, 90, 90, 90, where
105+
the last three numbers are angles in degrees :cite:`michaud2011mdanalysis`.
106+
Values different from *90* for the angles would define a triclinic (non-orthogonal)
107+
box, which is not currently supported by the existing code.
108+
109+
.. |mdanalysis_box| raw:: html
110+
111+
<a href="https://docs.mdanalysis.org/stable/documentation_pages/transformations/boxdimensions.html" target="_blank">conventions</a>
120112

121-
Let us call the *define_box* method from the *__init__* class:
113+
Let us call *define_box()* from the *__init__()* method:
122114

123115
.. label:: start_InitializeSimulation_class
124116

@@ -132,40 +124,41 @@ Let us call the *define_box* method from the *__init__* class:
132124
133125
.. label:: end_InitializeSimulation_class
134126

135-
Populate the box
127+
Populate the Box
136128
----------------
137129

138130
Here, the atoms are placed within the simulation box. If initial
139131
positions were not provided (i.e., *initial_positions = None*), atoms
140-
are placed randomly within the box. If *initial_positions* was provided
141-
as an array, the provided positions are used instead. Note that, in this
132+
are placed randomly within the box. If *initial_positions* is provided
133+
as an array, the provided positions are used instead. Note that in this
142134
case, the array must be of size 'number of atoms' times 'number of dimensions'.
143135

144136
.. label:: start_InitializeSimulation_class
145137

146138
.. code-block:: python
147139
148140
def populate_box(self):
141+
Nat = np.sum(self.number_atoms) # total number of atoms
149142
if self.initial_positions is None:
150-
atoms_positions = np.zeros((np.sum(self.number_atoms), 3))
143+
atoms_positions = np.zeros((Nat, 3))
151144
for dim in np.arange(3):
152145
diff_box = np.diff(self.box_boundaries[dim])
153-
random_pos = np.random.random(np.sum(self.number_atoms))
146+
random_pos = np.random.random(Nat)
154147
atoms_positions[:, dim] = random_pos*diff_box-diff_box/2
155148
self.atoms_positions = atoms_positions
156149
else:
157150
self.atoms_positions = self.initial_positions
158151
159152
.. label:: end_InitializeSimulation_class
160153

161-
In case *initial_positions is None*, and array is first created. Then, random
162-
positions that are constrained within the box boundaries are defined using the
163-
random function of NumPy. Note that, here, the newly added atoms are added
164-
randomly within the box, without taking care of avoiding overlaps with
165-
existing atoms. Overlaps will be dealt with using energy minimization,
166-
see :ref:`chapter4-label`.
154+
In case *initial_positions* is None, an array is first created. Then,
155+
random positions constrained within the box boundaries are defined using
156+
the random function from NumPy. Note that newly added atoms are placed
157+
randomly within the box, without considering overlaps with existing
158+
atoms. Overlaps will be addressed using energy minimization (see
159+
the :ref:`chapter4-label` chapter).
167160

168-
Let us call the *populate_box* method from the *__init__* class:
161+
Let us call *populate_box* from the *__init__* method:
169162

170163
.. label:: start_InitializeSimulation_class
171164

@@ -178,7 +171,7 @@ Let us call the *populate_box* method from the *__init__* class:
178171
179172
.. label:: end_InitializeSimulation_class
180173

181-
Build neighbor lists
174+
Build Neighbor Lists
182175
--------------------
183176

184177
In molecular simulations, it is common practice to identify neighboring atoms
@@ -210,12 +203,16 @@ atom, ensuring that only relevant interactions are considered in the
210203
calculations. These lists will be recalculated at intervals specified by
211204
the *neighbor* input parameter.
212205

213-
Update cross coefficients
206+
For efficiency, the *contact_matrix* function from MDAnalysis is
207+
used :cite:`michaud2011mdanalysis`. The *contact_matrix* function returns
208+
information about atoms located at a distance less than the cutoff from one another.
209+
210+
Update Cross Coefficients
214211
-------------------------
215212

216-
At the same time as the neighbor lists are getting build up, let us also
217-
pre-calculate the cross coefficients. This will make the force calculation
218-
more practical (see below).
213+
As the neighbor lists are being built, let us also pre-calculate the cross
214+
coefficients. This will make the force calculation more efficient (see
215+
below).
219216

220217
.. label:: start_Utilities_class
221218

@@ -250,7 +247,7 @@ Here, the values of the cross coefficients between atom of type 1 and 2,
250247
\sigma_{12} = (\sigma_{11}+\sigma_{22})/2 \\
251248
\epsilon_{12} = (\epsilon_{11}+\epsilon_{22})/2
252249
253-
Finally, import the following library in the *Utilities.py* file:
250+
Finally, import the following libraries in the *Utilities.py* file:
254251

255252
.. label:: start_Utilities_class
256253

@@ -261,8 +258,8 @@ Finally, import the following library in the *Utilities.py* file:
261258
262259
.. label:: end_Utilities_class
263260

264-
Let us call the *update_neighbor_lists* and *update_cross_coefficients*
265-
methods from the *__init__* class:
261+
Let us call *update_neighbor_lists* and *update_cross_coefficients*
262+
from the *__init__* method:
266263

267264
.. label:: start_InitializeSimulation_class
268265

@@ -276,12 +273,11 @@ methods from the *__init__* class:
276273
277274
.. label:: end_InitializeSimulation_class
278275

279-
Test the code
276+
Test the Code
280277
-------------
281278

282-
Let us test the *InitializeSimulation* class to make sure that it does what
283-
is expected, i.e. that it does create atoms that are located within the box
284-
boundaries along all 3 dimensions of space:
279+
Let us test the *InitializeSimulation* class to ensure that it behaves as
280+
expected, i.e., that it creates atoms located within the box boundaries:
285281

286282
.. label:: start_test_3a_class
287283

@@ -333,3 +329,6 @@ boundaries along all 3 dimensions of space:
333329
pytest.main(["-s", __file__])
334330
335331
.. label:: end_test_3a_class
332+
333+
The value of the cutoff, chosen as :math:`2.5 \sigma_{11}`, is relatively
334+
common for Lennard-Jones fluids and will often be the default choice for us.

docs/source/chapters/chapter4.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class:
138138
# Measure distance
139139
rij = self.compute_distance(self.atoms_positions[Ni],
140140
self.atoms_positions[neighbor_of_i],
141-
self.box_size[:3])
141+
self.box_size)
142142
# Measure potential using information about cross coefficients
143143
sigma_ij = self.sigma_ij_list[Ni]
144144
epsilon_ij = self.epsilon_ij_list[Ni]
@@ -158,11 +158,10 @@ class as well:
158158
def compute_distance(self,position_i, positions_j, box_size, only_norm = True):
159159
"""
160160
Measure the distances between two particles.
161-
The nan_to_num is crutial in 2D to avoid nan value along third dimension.
162161
# TOFIX: Move as function instead of a method?
163162
"""
164163
rij_xyz = np.nan_to_num(np.remainder(position_i - positions_j
165-
+ box_size[:3]/2.0, box_size) - box_size[:3]/2.0)
164+
+ box_size[:3]/2.0, box_size[:3]) - box_size[:3]/2.0)
166165
if only_norm:
167166
return np.linalg.norm(rij_xyz, axis=1)
168167
else:
@@ -190,7 +189,7 @@ let us create a new method that is dedicated solely to measuring forces:
190189
# Measure distance
191190
rij, rij_xyz = self.compute_distance(self.atoms_positions[Ni],
192191
self.atoms_positions[neighbor_of_i],
193-
self.box_size[:3], only_norm = False)
192+
self.box_size, only_norm = False)
194193
# Measure force using information about cross coefficients
195194
sigma_ij = self.sigma_ij_list[Ni]
196195
epsilon_ij = self.epsilon_ij_list[Ni]

docs/source/journal-article.bib

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,15 @@ @article{ harris2020array
8484
doi = {10.1038/s41586-020-2649-2},
8585
publisher = {Springer Science and Business Media {LLC}},
8686
url = {https://doi.org/10.1038/s41586-020-2649-2}
87-
}
87+
}
88+
89+
@article{michaud2011mdanalysis,
90+
title={MDAnalysis: a toolkit for the analysis of molecular dynamics simulations},
91+
author={Michaud-Agrawal, Naveen and Denning, Elizabeth J and Woolf, Thomas B and Beckstein, Oliver},
92+
journal={Journal of computational chemistry},
93+
volume={32},
94+
number={10},
95+
pages={2319--2327},
96+
year={2011},
97+
publisher={Wiley Online Library}
98+
}

0 commit comments

Comments
 (0)