nomad-schema-plugin-simulation-workflow 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/LICENSE +202 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/METADATA +17 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/RECORD +22 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/WHEEL +5 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/entry_points.txt +2 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/top_level.txt +1 -0
- simulationworkflowschema/__init__.py +118 -0
- simulationworkflowschema/chemical_reaction.py +167 -0
- simulationworkflowschema/dmft.py +84 -0
- simulationworkflowschema/elastic.py +494 -0
- simulationworkflowschema/equation_of_state.py +213 -0
- simulationworkflowschema/general.py +505 -0
- simulationworkflowschema/geometry_optimization.py +349 -0
- simulationworkflowschema/gw.py +69 -0
- simulationworkflowschema/max_ent.py +80 -0
- simulationworkflowschema/molecular_dynamics.py +2601 -0
- simulationworkflowschema/phonon.py +196 -0
- simulationworkflowschema/photon_polarization.py +75 -0
- simulationworkflowschema/single_point.py +273 -0
- simulationworkflowschema/tb.py +77 -0
- simulationworkflowschema/thermodynamics.py +375 -0
- simulationworkflowschema/xs.py +100 -0
|
@@ -0,0 +1,2601 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
from typing import List, Dict, Any, Callable
|
|
19
|
+
from itertools import chain
|
|
20
|
+
from collections import namedtuple
|
|
21
|
+
import numpy as np
|
|
22
|
+
from array import array
|
|
23
|
+
from nptyping import Int, NDArray
|
|
24
|
+
from scipy import sparse
|
|
25
|
+
from scipy.stats import linregress
|
|
26
|
+
import networkx
|
|
27
|
+
import MDAnalysis
|
|
28
|
+
from MDAnalysis.core.topology import Topology
|
|
29
|
+
from MDAnalysis.core.universe import Universe
|
|
30
|
+
import MDAnalysis.analysis.rdf as MDA_RDF
|
|
31
|
+
from MDAnalysis.core._get_readers import get_reader_for
|
|
32
|
+
|
|
33
|
+
from nomad.datamodel.data import ArchiveSection
|
|
34
|
+
from nomad.metainfo import (
|
|
35
|
+
SubSection,
|
|
36
|
+
Section,
|
|
37
|
+
Quantity,
|
|
38
|
+
MEnum,
|
|
39
|
+
Reference,
|
|
40
|
+
MSection,
|
|
41
|
+
)
|
|
42
|
+
from nomad.datamodel.hdf5 import HDF5Dataset
|
|
43
|
+
from nomad.datamodel.metainfo.workflow import Link
|
|
44
|
+
from runschema.system import System, AtomsGroup
|
|
45
|
+
from runschema.calculation import (
|
|
46
|
+
RadiusOfGyration as RadiusOfGyrationCalculation,
|
|
47
|
+
RadiusOfGyrationValues as RadiusOfGyrationValuesCalculation,
|
|
48
|
+
)
|
|
49
|
+
from nomad.utils import get_logger
|
|
50
|
+
from nomad.units import ureg
|
|
51
|
+
from nomad import atomutils
|
|
52
|
+
from .general import (
|
|
53
|
+
SimulationWorkflowMethod,
|
|
54
|
+
SimulationWorkflowResults,
|
|
55
|
+
SerialSimulation,
|
|
56
|
+
WORKFLOW_METHOD_NAME,
|
|
57
|
+
WORKFLOW_RESULTS_NAME,
|
|
58
|
+
)
|
|
59
|
+
from .thermodynamics import ThermodynamicsResults
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
LOGGER = get_logger(__name__)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BeadGroup(object):
|
|
66
|
+
# see https://github.com/MDAnalysis/mdanalysis/issues/1891#issuecomment-387138110
|
|
67
|
+
# by @richardjgowers with performance improvements
|
|
68
|
+
def __init__(self, atoms, compound='fragments'):
|
|
69
|
+
"""Initialize with an AtomGroup instance.
|
|
70
|
+
Will split based on keyword 'compounds' (residues or fragments).
|
|
71
|
+
"""
|
|
72
|
+
self._atoms = atoms
|
|
73
|
+
self.compound = compound
|
|
74
|
+
self._nbeads = len(getattr(self._atoms, self.compound))
|
|
75
|
+
# for caching
|
|
76
|
+
self._cache = {}
|
|
77
|
+
self._cache['positions'] = None
|
|
78
|
+
self.__last_frame = None
|
|
79
|
+
|
|
80
|
+
def __len__(self):
|
|
81
|
+
return self._nbeads
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def positions(self):
|
|
85
|
+
# cache positions for current frame
|
|
86
|
+
if self.universe.trajectory.frame != self.__last_frame:
|
|
87
|
+
self._cache['positions'] = self._atoms.center_of_mass(
|
|
88
|
+
unwrap=True, compound=self.compound
|
|
89
|
+
)
|
|
90
|
+
self.__last_frame = self.universe.trajectory.frame
|
|
91
|
+
return self._cache['positions']
|
|
92
|
+
|
|
93
|
+
@property # type: ignore
|
|
94
|
+
@MDAnalysis.lib.util.cached('universe')
|
|
95
|
+
def universe(self):
|
|
96
|
+
return self._atoms.universe
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_bond_list_from_model_contributions(
|
|
100
|
+
sec_run: MSection, method_index: int = -1, model_index: int = -1
|
|
101
|
+
) -> List[tuple]:
|
|
102
|
+
"""
|
|
103
|
+
Generates bond list of tuples using the list of bonded force field interactions stored under run[].method[].force_field.model[].
|
|
104
|
+
|
|
105
|
+
bond_list: List[tuple]
|
|
106
|
+
"""
|
|
107
|
+
contributions = []
|
|
108
|
+
if sec_run.m_xpath(
|
|
109
|
+
f'method[{method_index}].force_field.model[{model_index}].contributions'
|
|
110
|
+
):
|
|
111
|
+
contributions = (
|
|
112
|
+
sec_run.method[method_index].force_field.model[model_index].contributions
|
|
113
|
+
)
|
|
114
|
+
bond_list = []
|
|
115
|
+
for contribution in contributions:
|
|
116
|
+
if contribution.type != 'bond':
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
atom_indices = contribution.atom_indices
|
|
120
|
+
if (
|
|
121
|
+
contribution.n_interactions
|
|
122
|
+
): # all bonds have been grouped into one contribution
|
|
123
|
+
bond_list = [tuple(indices) for indices in atom_indices]
|
|
124
|
+
else:
|
|
125
|
+
bond_list.append(tuple(contribution.atom_indices))
|
|
126
|
+
|
|
127
|
+
return bond_list
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def create_empty_universe(
|
|
131
|
+
n_atoms: int,
|
|
132
|
+
n_frames: int = 1,
|
|
133
|
+
n_residues: int = 1,
|
|
134
|
+
n_segments: int = 1,
|
|
135
|
+
atom_resindex: NDArray[Int] = None,
|
|
136
|
+
residue_segindex: NDArray[Int] = None,
|
|
137
|
+
flag_trajectory: bool = False,
|
|
138
|
+
flag_velocities: bool = False,
|
|
139
|
+
flag_forces: bool = False,
|
|
140
|
+
timestep: float = None,
|
|
141
|
+
) -> MDAnalysis.Universe:
|
|
142
|
+
"""Create a blank Universe
|
|
143
|
+
|
|
144
|
+
This function was adapted from the function empty() within the MDA class Universe().
|
|
145
|
+
The only difference is that the Universe() class is imported directly here, whereas in the
|
|
146
|
+
original function is is passed as a function argument, since the function there is a classfunction.
|
|
147
|
+
|
|
148
|
+
Useful for building a Universe without requiring existing files,
|
|
149
|
+
for example for system building.
|
|
150
|
+
|
|
151
|
+
If `flag_trajectory` is set to True, a
|
|
152
|
+
:class:`MDAnalysis.coordinates.memory.MemoryReader` will be
|
|
153
|
+
attached to the Universe.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
n_atoms: int
|
|
158
|
+
number of Atoms in the Universe
|
|
159
|
+
n_residues: int, default 1
|
|
160
|
+
number of Residues in the Universe, defaults to 1
|
|
161
|
+
n_segments: int, default 1
|
|
162
|
+
number of Segments in the Universe, defaults to 1
|
|
163
|
+
atom_resindex: array like, optional
|
|
164
|
+
mapping of atoms to residues, e.g. with 6 atoms,
|
|
165
|
+
`atom_resindex=[0, 0, 1, 1, 2, 2]` would put 2 atoms
|
|
166
|
+
into each of 3 residues.
|
|
167
|
+
residue_segindex: array like, optional
|
|
168
|
+
mapping of residues to segments
|
|
169
|
+
flag_trajectory: bool, optional
|
|
170
|
+
if True, attaches a :class:`MDAnalysis.coordinates.memory.MemoryReader`
|
|
171
|
+
allowing coordinates to be set and written. Default is False
|
|
172
|
+
flag_velocities: bool, optional
|
|
173
|
+
include velocities in the :class:`MDAnalysis.coordinates.memory.MemoryReader`
|
|
174
|
+
flag_forces: bool, optional
|
|
175
|
+
include forces in the :class:`MDAnalysis.coordinates.memory.MemoryReader`
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
MDAnalysis.Universe object
|
|
180
|
+
|
|
181
|
+
Examples
|
|
182
|
+
--------
|
|
183
|
+
For example to create a new Universe with 6 atoms in 2 residues, with
|
|
184
|
+
positions for the atoms and a mass attribute:
|
|
185
|
+
|
|
186
|
+
>>> u = mda.Universe.empty(6, 2,
|
|
187
|
+
atom_resindex=np.array([0, 0, 0, 1, 1, 1]),
|
|
188
|
+
flag_trajectory=True,
|
|
189
|
+
)
|
|
190
|
+
>>> u.add_TopologyAttr('masses')
|
|
191
|
+
|
|
192
|
+
.. versionadded:: 0.17.0
|
|
193
|
+
.. versionchanged:: 0.19.0
|
|
194
|
+
The attached Reader when flag_trajectory=True is now a MemoryReader
|
|
195
|
+
.. versionchanged:: 1.0.0
|
|
196
|
+
Universes can now be created with 0 atoms
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
if not n_atoms:
|
|
200
|
+
n_residues = 0
|
|
201
|
+
n_segments = 0
|
|
202
|
+
|
|
203
|
+
if atom_resindex is None:
|
|
204
|
+
LOGGER.warn(
|
|
205
|
+
'Residues specified but no atom_resindex given. '
|
|
206
|
+
'All atoms will be placed in first Residue.',
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if residue_segindex is None:
|
|
210
|
+
LOGGER.warn(
|
|
211
|
+
'Segments specified but no segment_resindex given. '
|
|
212
|
+
'All residues will be placed in first Segment',
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
topology = Topology(
|
|
216
|
+
n_atoms,
|
|
217
|
+
n_residues,
|
|
218
|
+
n_segments,
|
|
219
|
+
atom_resindex=atom_resindex,
|
|
220
|
+
residue_segindex=residue_segindex,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
universe = Universe(topology)
|
|
224
|
+
|
|
225
|
+
if flag_trajectory:
|
|
226
|
+
coords = np.zeros((n_frames, n_atoms, 3), dtype=np.float32)
|
|
227
|
+
vels = np.zeros_like(coords) if flag_velocities else None
|
|
228
|
+
forces = np.zeros_like(coords) if flag_forces else None
|
|
229
|
+
|
|
230
|
+
# grab and attach a MemoryReader
|
|
231
|
+
universe.trajectory = get_reader_for(coords)(
|
|
232
|
+
coords,
|
|
233
|
+
order='fac',
|
|
234
|
+
n_atoms=n_atoms,
|
|
235
|
+
velocities=vels,
|
|
236
|
+
forces=forces,
|
|
237
|
+
dt=timestep,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return universe
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def archive_to_universe(
|
|
244
|
+
archive,
|
|
245
|
+
system_index: int = 0,
|
|
246
|
+
method_index: int = -1,
|
|
247
|
+
model_index: int = -1,
|
|
248
|
+
) -> MDAnalysis.Universe:
|
|
249
|
+
"""Extract the topology from a provided run section of an archive entry
|
|
250
|
+
|
|
251
|
+
Input:
|
|
252
|
+
|
|
253
|
+
archive_sec_run: section run of an EntryArchive
|
|
254
|
+
|
|
255
|
+
system_index: list index of archive.run[].system to be used for topology extraction
|
|
256
|
+
|
|
257
|
+
method_index: list index of archive.run[].method to be used for atom parameter (charges and masses) extraction
|
|
258
|
+
|
|
259
|
+
model_index: list index of archive.run[].method[].force_field.model for bond list extraction
|
|
260
|
+
|
|
261
|
+
Variables:
|
|
262
|
+
|
|
263
|
+
n_frames (int):
|
|
264
|
+
|
|
265
|
+
n_atoms (int):
|
|
266
|
+
|
|
267
|
+
atom_names (str, shape=(n_atoms)):
|
|
268
|
+
|
|
269
|
+
atom_types (str, shape=(n_atoms)):
|
|
270
|
+
|
|
271
|
+
atom_resindex (str, shape=(n_atoms)):
|
|
272
|
+
|
|
273
|
+
atom_segids (str, shape=(n_atoms)):
|
|
274
|
+
|
|
275
|
+
n_segments (int): Segments correspond to a group of the same type of molecules.
|
|
276
|
+
|
|
277
|
+
n_residues (int): The number of distinct residues (nb - individual molecules are also denoted as a residue).
|
|
278
|
+
|
|
279
|
+
resnames (str, shape=(n_residues)): The name of each residue.
|
|
280
|
+
|
|
281
|
+
residue_segindex (int, shape=(n_residues)): The segment index that each residue belongs to.
|
|
282
|
+
|
|
283
|
+
residue_molnums (int, shape=(n_residues)): The molecule index that each residue belongs to.
|
|
284
|
+
|
|
285
|
+
residue_moltypes (int, shape=(n_residues)): The molecule type of each residue.
|
|
286
|
+
|
|
287
|
+
n_molecules (int):
|
|
288
|
+
|
|
289
|
+
masses (float, shape=(n_atoms)): atom masses, units = amu
|
|
290
|
+
|
|
291
|
+
charges (float, shape=(n_atoms)): atom partial charges, units = e
|
|
292
|
+
|
|
293
|
+
positions (float, shape=(n_frames,n_atoms,3)): atom positions
|
|
294
|
+
|
|
295
|
+
velocities (float, shape=(n_frames,n_atoms,3)): atom velocities
|
|
296
|
+
|
|
297
|
+
dimensions (float, shape=(n_frames,6)): box dimensions (nb - currently assuming a cubic box!)
|
|
298
|
+
|
|
299
|
+
bonds (tuple, shape=([])): list of tuples with the atom indices of each bond
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
sec_run = archive.run[-1]
|
|
304
|
+
sec_system = sec_run.system
|
|
305
|
+
sec_system_top = sec_run.system[system_index]
|
|
306
|
+
sec_atoms = sec_system_top.atoms
|
|
307
|
+
sec_atoms_group = sec_system_top.atoms_group
|
|
308
|
+
sec_calculation = sec_run.calculation
|
|
309
|
+
sec_method = (
|
|
310
|
+
sec_run.method[method_index] if sec_run.get('method') is not None else None
|
|
311
|
+
)
|
|
312
|
+
except IndexError:
|
|
313
|
+
LOGGER.warning(
|
|
314
|
+
'Supplied indices or necessary sections do not exist in archive. Cannot build the MDA universe.'
|
|
315
|
+
)
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
n_atoms = sec_atoms.get('n_atoms')
|
|
319
|
+
if n_atoms is None:
|
|
320
|
+
LOGGER.warning('No atoms found in the archive. Cannot build the MDA universe.')
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
n_frames = len(sec_system) if sec_system is not None else None
|
|
324
|
+
atom_names = sec_atoms.get('labels')
|
|
325
|
+
model_atom_parameters = sec_method.get('atom_parameters')
|
|
326
|
+
atom_types = (
|
|
327
|
+
[atom.label for atom in model_atom_parameters]
|
|
328
|
+
if model_atom_parameters
|
|
329
|
+
else atom_names
|
|
330
|
+
)
|
|
331
|
+
atom_resindex = np.arange(n_atoms)
|
|
332
|
+
atoms_segindices = np.empty(n_atoms)
|
|
333
|
+
atom_segids = np.array(range(n_atoms), dtype='object')
|
|
334
|
+
molecule_groups = sec_atoms_group
|
|
335
|
+
n_segments = len(molecule_groups)
|
|
336
|
+
|
|
337
|
+
n_residues = 0
|
|
338
|
+
n_molecules = 0
|
|
339
|
+
residue_segindex = []
|
|
340
|
+
resnames = []
|
|
341
|
+
residue_moltypes = []
|
|
342
|
+
residue_min_atom_index = []
|
|
343
|
+
residue_n_atoms = []
|
|
344
|
+
molecule_n_res = []
|
|
345
|
+
for mol_group_ind, mol_group in enumerate(molecule_groups):
|
|
346
|
+
atoms_segindices[mol_group.atom_indices] = mol_group_ind
|
|
347
|
+
atom_segids[mol_group.atom_indices] = mol_group.label
|
|
348
|
+
molecules = mol_group.atoms_group if mol_group.atoms_group is not None else []
|
|
349
|
+
for mol in molecules:
|
|
350
|
+
monomer_groups = mol.atoms_group
|
|
351
|
+
mol_res_counter = 0
|
|
352
|
+
if monomer_groups:
|
|
353
|
+
for mon_group in monomer_groups:
|
|
354
|
+
monomers = mon_group.atoms_group
|
|
355
|
+
for mon in monomers:
|
|
356
|
+
resnames.append(mon.label)
|
|
357
|
+
residue_segindex.append(mol_group_ind)
|
|
358
|
+
residue_moltypes.append(mol.label)
|
|
359
|
+
residue_min_atom_index.append(np.min(mon.atom_indices))
|
|
360
|
+
residue_n_atoms.append(len(mon.atom_indices))
|
|
361
|
+
n_residues += 1
|
|
362
|
+
mol_res_counter += 1
|
|
363
|
+
else: # no monomers => whole molecule is it's own residue
|
|
364
|
+
resnames.append(mol.label)
|
|
365
|
+
residue_segindex.append(mol_group_ind)
|
|
366
|
+
residue_moltypes.append(mol.label)
|
|
367
|
+
residue_min_atom_index.append(np.min(mol.atom_indices))
|
|
368
|
+
residue_n_atoms.append(len(mol.atom_indices))
|
|
369
|
+
n_residues += 1
|
|
370
|
+
mol_res_counter += 1
|
|
371
|
+
molecule_n_res.append(mol_res_counter)
|
|
372
|
+
n_molecules += 1
|
|
373
|
+
|
|
374
|
+
# reorder the residues by atom_indices
|
|
375
|
+
residue_data = np.array(
|
|
376
|
+
[
|
|
377
|
+
[
|
|
378
|
+
residue_min_atom_index[i],
|
|
379
|
+
residue_n_atoms[i],
|
|
380
|
+
residue_segindex[i],
|
|
381
|
+
residue_moltypes[i],
|
|
382
|
+
resnames[i],
|
|
383
|
+
]
|
|
384
|
+
for i in range(len(residue_min_atom_index))
|
|
385
|
+
],
|
|
386
|
+
dtype=object,
|
|
387
|
+
)
|
|
388
|
+
residue_data = np.array(sorted(residue_data, key=lambda x: x[0], reverse=False)).T
|
|
389
|
+
residue_n_atoms = residue_data[1].astype(int)
|
|
390
|
+
residue_segindex = residue_data[2].astype(int)
|
|
391
|
+
residue_moltypes = residue_data[3]
|
|
392
|
+
resnames = residue_data[4]
|
|
393
|
+
res_index_counter = 0
|
|
394
|
+
for i_residue, res_n_atoms in enumerate(residue_n_atoms):
|
|
395
|
+
atom_resindex[res_index_counter : res_index_counter + res_n_atoms] = i_residue # type: ignore
|
|
396
|
+
res_index_counter += res_n_atoms
|
|
397
|
+
residue_molnums = np.array(range(n_residues))
|
|
398
|
+
mol_index_counter = 0
|
|
399
|
+
for i_molecule, n_res in enumerate(molecule_n_res):
|
|
400
|
+
residue_molnums[mol_index_counter : mol_index_counter + n_res] = i_molecule
|
|
401
|
+
mol_index_counter += n_res
|
|
402
|
+
|
|
403
|
+
# get the atom masses and charges
|
|
404
|
+
|
|
405
|
+
masses = np.empty(n_atoms)
|
|
406
|
+
charges = np.empty(n_atoms)
|
|
407
|
+
atom_parameters = (
|
|
408
|
+
sec_method.get('atom_parameters') if sec_method is not None else []
|
|
409
|
+
)
|
|
410
|
+
atom_parameters = atom_parameters if atom_parameters is not None else []
|
|
411
|
+
|
|
412
|
+
for atom_ind, atom in enumerate(atom_parameters):
|
|
413
|
+
if atom.get('mass'):
|
|
414
|
+
masses[atom_ind] = ureg.convert(
|
|
415
|
+
atom.mass.magnitude, atom.mass.units, ureg.amu
|
|
416
|
+
)
|
|
417
|
+
if atom.get('charge'):
|
|
418
|
+
charges[atom_ind] = ureg.convert(
|
|
419
|
+
atom.charge.magnitude, atom.charge.units, ureg.e
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# get the atom positions, velocites, and box dimensions
|
|
423
|
+
positions = np.empty(shape=(n_frames, n_atoms, 3))
|
|
424
|
+
velocities = np.empty(shape=(n_frames, n_atoms, 3))
|
|
425
|
+
dimensions = np.empty(shape=(n_frames, 6))
|
|
426
|
+
for frame_ind, frame in enumerate(sec_system):
|
|
427
|
+
sec_atoms_fr = frame.get('atoms')
|
|
428
|
+
if sec_atoms_fr is not None:
|
|
429
|
+
positions_frame = sec_atoms_fr.positions
|
|
430
|
+
positions[frame_ind] = (
|
|
431
|
+
ureg.convert(
|
|
432
|
+
positions_frame.magnitude, positions_frame.units, ureg.angstrom
|
|
433
|
+
)
|
|
434
|
+
if positions_frame is not None
|
|
435
|
+
else None
|
|
436
|
+
)
|
|
437
|
+
velocities_frame = sec_atoms_fr.velocities
|
|
438
|
+
velocities[frame_ind] = (
|
|
439
|
+
ureg.convert(
|
|
440
|
+
velocities_frame.magnitude,
|
|
441
|
+
velocities_frame.units,
|
|
442
|
+
ureg.angstrom / ureg.picosecond,
|
|
443
|
+
)
|
|
444
|
+
if velocities_frame is not None
|
|
445
|
+
else None
|
|
446
|
+
)
|
|
447
|
+
latt_vec_tmp = sec_atoms_fr.get('lattice_vectors')
|
|
448
|
+
if latt_vec_tmp is not None:
|
|
449
|
+
length_conversion = ureg.convert(
|
|
450
|
+
1.0, sec_atoms_fr.lattice_vectors.units, ureg.angstrom
|
|
451
|
+
)
|
|
452
|
+
dimensions[frame_ind] = [
|
|
453
|
+
sec_atoms_fr.lattice_vectors.magnitude[0][0] * length_conversion,
|
|
454
|
+
sec_atoms_fr.lattice_vectors.magnitude[1][1] * length_conversion,
|
|
455
|
+
sec_atoms_fr.lattice_vectors.magnitude[2][2] * length_conversion,
|
|
456
|
+
90,
|
|
457
|
+
90,
|
|
458
|
+
90,
|
|
459
|
+
] # TODO: extend to non-cubic boxes
|
|
460
|
+
|
|
461
|
+
# get the bonds # TODO extend to multiple storage options for interactions
|
|
462
|
+
bonds = sec_atoms.bond_list
|
|
463
|
+
if bonds is None:
|
|
464
|
+
bonds = get_bond_list_from_model_contributions(
|
|
465
|
+
sec_run, method_index=-1, model_index=-1
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# get the system times
|
|
469
|
+
system_timestep = 1.0 * ureg.picosecond
|
|
470
|
+
|
|
471
|
+
def approx(a, b, rel_tol=1e-09, abs_tol=0.0):
|
|
472
|
+
return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
|
|
473
|
+
|
|
474
|
+
system_times = [calc.time for calc in sec_calculation if calc.system_ref]
|
|
475
|
+
if system_times:
|
|
476
|
+
try:
|
|
477
|
+
method = archive.workflow2.method
|
|
478
|
+
system_timestep = (
|
|
479
|
+
method.integration_timestep * method.coordinate_save_frequency
|
|
480
|
+
)
|
|
481
|
+
except Exception:
|
|
482
|
+
LOGGER.warning(
|
|
483
|
+
'Cannot find the system times. MDA universe will contain non-physical times and timestep.'
|
|
484
|
+
)
|
|
485
|
+
else:
|
|
486
|
+
time_steps = [
|
|
487
|
+
system_times[i_time] - system_times[i_time - 1]
|
|
488
|
+
for i_time in range(1, len(system_times))
|
|
489
|
+
]
|
|
490
|
+
if all(approx(time_steps[0], time_step) for time_step in time_steps):
|
|
491
|
+
system_timestep = ureg.convert(
|
|
492
|
+
time_steps[0].magnitude, ureg.second, ureg.picosecond
|
|
493
|
+
)
|
|
494
|
+
else:
|
|
495
|
+
LOGGER.warning(
|
|
496
|
+
'System times are not equally spaced. Cannot set system times in MDA universe.'
|
|
497
|
+
' MDA universe will contain non-physical times and timestep.'
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
system_timestep = ureg.convert(
|
|
501
|
+
system_timestep, system_timestep._units, ureg.picoseconds
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# create the Universe
|
|
505
|
+
metainfo_universe = create_empty_universe(
|
|
506
|
+
n_atoms,
|
|
507
|
+
n_frames=n_frames,
|
|
508
|
+
n_residues=n_residues,
|
|
509
|
+
n_segments=n_segments,
|
|
510
|
+
atom_resindex=atom_resindex,
|
|
511
|
+
residue_segindex=residue_segindex,
|
|
512
|
+
flag_trajectory=True,
|
|
513
|
+
flag_velocities=True,
|
|
514
|
+
timestep=system_timestep.magnitude,
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
# set the positions and velocities
|
|
518
|
+
for frame_ind, frame in enumerate(metainfo_universe.trajectory):
|
|
519
|
+
metainfo_universe.atoms.positions = positions[frame_ind]
|
|
520
|
+
metainfo_universe.atoms.velocities = velocities[frame_ind]
|
|
521
|
+
|
|
522
|
+
# add the atom attributes
|
|
523
|
+
metainfo_universe.add_TopologyAttr('name', atom_names)
|
|
524
|
+
metainfo_universe.add_TopologyAttr('type', atom_types)
|
|
525
|
+
metainfo_universe.add_TopologyAttr('mass', masses)
|
|
526
|
+
metainfo_universe.add_TopologyAttr('charge', charges)
|
|
527
|
+
if n_segments != 0:
|
|
528
|
+
metainfo_universe.add_TopologyAttr('segids', np.unique(atom_segids))
|
|
529
|
+
if n_residues != 0:
|
|
530
|
+
metainfo_universe.add_TopologyAttr('resnames', resnames)
|
|
531
|
+
metainfo_universe.add_TopologyAttr('resids', np.unique(atom_resindex) + 1)
|
|
532
|
+
metainfo_universe.add_TopologyAttr('resnums', np.unique(atom_resindex) + 1)
|
|
533
|
+
if len(residue_molnums) > 0:
|
|
534
|
+
metainfo_universe.add_TopologyAttr('molnums', residue_molnums)
|
|
535
|
+
if len(residue_moltypes) > 0:
|
|
536
|
+
metainfo_universe.add_TopologyAttr('moltypes', residue_moltypes)
|
|
537
|
+
|
|
538
|
+
# add the box dimensions
|
|
539
|
+
for frame_ind, frame in enumerate(metainfo_universe.trajectory):
|
|
540
|
+
metainfo_universe.atoms.dimensions = dimensions[frame_ind]
|
|
541
|
+
|
|
542
|
+
# add the bonds
|
|
543
|
+
if hasattr(metainfo_universe, 'bonds'):
|
|
544
|
+
LOGGER.warning('archive_to_universe() failed, universe already has bonds.')
|
|
545
|
+
return None
|
|
546
|
+
metainfo_universe.add_TopologyAttr('bonds', bonds)
|
|
547
|
+
|
|
548
|
+
return metainfo_universe
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _get_molecular_bead_groups(
|
|
552
|
+
universe: MDAnalysis.Universe, moltypes: List[str] = None
|
|
553
|
+
) -> Dict[str, BeadGroup]:
|
|
554
|
+
"""
|
|
555
|
+
Creates bead groups based on the molecular types as defined by the MDAnalysis universe.
|
|
556
|
+
"""
|
|
557
|
+
if moltypes is None:
|
|
558
|
+
atoms_moltypes = getattr(universe.atoms, 'moltypes', [])
|
|
559
|
+
moltypes = np.unique(atoms_moltypes)
|
|
560
|
+
bead_groups = {}
|
|
561
|
+
for moltype in moltypes:
|
|
562
|
+
ags_by_moltype = universe.select_atoms('moltype ' + moltype)
|
|
563
|
+
ags_by_moltype = ags_by_moltype[
|
|
564
|
+
ags_by_moltype.masses > abs(1e-2)
|
|
565
|
+
] # remove any virtual/massless sites (needed for, e.g., 4-bead water models)
|
|
566
|
+
bead_groups[moltype] = BeadGroup(ags_by_moltype, compound='fragments')
|
|
567
|
+
|
|
568
|
+
return bead_groups
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def calc_molecular_rdf(
|
|
572
|
+
universe: MDAnalysis.Universe,
|
|
573
|
+
n_traj_split: int = 10,
|
|
574
|
+
n_prune: int = 1,
|
|
575
|
+
interval_indices=None,
|
|
576
|
+
max_mols: int = 5000,
|
|
577
|
+
) -> Dict:
|
|
578
|
+
"""
|
|
579
|
+
Calculates the radial distribution functions between for each unique pair of
|
|
580
|
+
molecule types as a function of their center of mass distance.
|
|
581
|
+
|
|
582
|
+
interval_indices: 2D array specifying the groups of the n_traj_split intervals to be averaged
|
|
583
|
+
max_mols: the maximum number of molecules per bead group for calculating the rdf, for efficiency purposes.
|
|
584
|
+
"""
|
|
585
|
+
# TODO 5k default for max_mols was set after > 50k was giving problems. Should do further testing to see where the appropriate limit should be set.
|
|
586
|
+
if (
|
|
587
|
+
not universe
|
|
588
|
+
or not universe.trajectory
|
|
589
|
+
or universe.trajectory[0].dimensions is None
|
|
590
|
+
):
|
|
591
|
+
return {}
|
|
592
|
+
|
|
593
|
+
n_frames = universe.trajectory.n_frames
|
|
594
|
+
if n_frames < n_traj_split:
|
|
595
|
+
n_traj_split = 1
|
|
596
|
+
frames_start = np.array([0])
|
|
597
|
+
frames_end = np.array([n_frames])
|
|
598
|
+
n_frames_split = np.array([n_frames])
|
|
599
|
+
interval_indices = [[0]]
|
|
600
|
+
else:
|
|
601
|
+
run_len = int(n_frames / n_traj_split)
|
|
602
|
+
frames_start = np.arange(n_traj_split) * run_len
|
|
603
|
+
frames_end = frames_start + run_len
|
|
604
|
+
frames_end[-1] = n_frames
|
|
605
|
+
n_frames_split = frames_end - frames_start
|
|
606
|
+
if np.sum(n_frames_split) != n_frames:
|
|
607
|
+
LOGGER.error(
|
|
608
|
+
'Something went wrong with input parameters in calc_molecular_rdf().'
|
|
609
|
+
'Radial distribution functions will not be calculated.'
|
|
610
|
+
)
|
|
611
|
+
return {}
|
|
612
|
+
if not interval_indices:
|
|
613
|
+
interval_indices = [[i] for i in range(n_traj_split)]
|
|
614
|
+
|
|
615
|
+
bead_groups = _get_molecular_bead_groups(universe)
|
|
616
|
+
if not bead_groups:
|
|
617
|
+
return bead_groups
|
|
618
|
+
moltypes = [moltype for moltype in bead_groups.keys()]
|
|
619
|
+
del_list = [
|
|
620
|
+
i_moltype
|
|
621
|
+
for i_moltype, moltype in enumerate(moltypes)
|
|
622
|
+
if bead_groups[moltype]._nbeads > max_mols
|
|
623
|
+
]
|
|
624
|
+
moltypes = np.delete(moltypes, del_list).tolist()
|
|
625
|
+
|
|
626
|
+
min_box_dimension = np.min(universe.trajectory[0].dimensions[:3])
|
|
627
|
+
max_rdf_dist = min_box_dimension / 2
|
|
628
|
+
n_bins = 200
|
|
629
|
+
n_smooth = 2
|
|
630
|
+
|
|
631
|
+
rdf_results: Dict[str, Any] = {}
|
|
632
|
+
rdf_results['n_smooth'] = n_smooth
|
|
633
|
+
rdf_results['n_prune'] = n_prune
|
|
634
|
+
rdf_results['type'] = 'molecular'
|
|
635
|
+
rdf_results['types'] = []
|
|
636
|
+
rdf_results['variables_name'] = []
|
|
637
|
+
rdf_results['bins'] = []
|
|
638
|
+
rdf_results['value'] = []
|
|
639
|
+
rdf_results['frame_start'] = []
|
|
640
|
+
rdf_results['frame_end'] = []
|
|
641
|
+
for i, moltype_i in enumerate(moltypes):
|
|
642
|
+
for j, moltype_j in enumerate(moltypes):
|
|
643
|
+
if j > i:
|
|
644
|
+
continue
|
|
645
|
+
elif (
|
|
646
|
+
i == j and bead_groups[moltype_i].positions.shape[0] == 1
|
|
647
|
+
): # skip if only 1 mol in group
|
|
648
|
+
continue
|
|
649
|
+
|
|
650
|
+
if i == j:
|
|
651
|
+
exclusion_block = (1, 1) # remove self-distance
|
|
652
|
+
else:
|
|
653
|
+
exclusion_block = None
|
|
654
|
+
pair_type = f'{moltype_i}-{moltype_j}'
|
|
655
|
+
rdf_results_interval: Dict[str, Any] = {}
|
|
656
|
+
rdf_results_interval['types'] = []
|
|
657
|
+
rdf_results_interval['variables_name'] = []
|
|
658
|
+
rdf_results_interval['bins'] = []
|
|
659
|
+
rdf_results_interval['value'] = []
|
|
660
|
+
rdf_results_interval['frame_start'] = []
|
|
661
|
+
rdf_results_interval['frame_end'] = []
|
|
662
|
+
for i_interval in range(n_traj_split):
|
|
663
|
+
rdf_results_interval['types'].append(pair_type)
|
|
664
|
+
rdf_results_interval['variables_name'].append(['distance'])
|
|
665
|
+
rdf = MDA_RDF.InterRDF(
|
|
666
|
+
bead_groups[moltype_i],
|
|
667
|
+
bead_groups[moltype_j],
|
|
668
|
+
range=(0, max_rdf_dist),
|
|
669
|
+
exclusion_block=exclusion_block,
|
|
670
|
+
nbins=n_bins,
|
|
671
|
+
).run(frames_start[i_interval], frames_end[i_interval], n_prune)
|
|
672
|
+
rdf_results_interval['frame_start'].append(frames_start[i_interval])
|
|
673
|
+
rdf_results_interval['frame_end'].append(frames_end[i_interval])
|
|
674
|
+
|
|
675
|
+
rdf_results_interval['bins'].append(
|
|
676
|
+
rdf.results.bins[int(n_smooth / 2) : -int(n_smooth / 2)]
|
|
677
|
+
* ureg.angstrom
|
|
678
|
+
)
|
|
679
|
+
rdf_results_interval['value'].append(
|
|
680
|
+
np.convolve(
|
|
681
|
+
rdf.results.rdf, np.ones((n_smooth,)) / n_smooth, mode='same'
|
|
682
|
+
)[int(n_smooth / 2) : -int(n_smooth / 2)]
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
flag_logging_error = False
|
|
686
|
+
for interval_group in interval_indices:
|
|
687
|
+
split_weights = n_frames_split[np.array(interval_group)] / np.sum(
|
|
688
|
+
n_frames_split[np.array(interval_group)]
|
|
689
|
+
)
|
|
690
|
+
if abs(np.sum(split_weights) - 1.0) > 1e-6:
|
|
691
|
+
flag_logging_error = True
|
|
692
|
+
continue
|
|
693
|
+
rdf_values_avg = (
|
|
694
|
+
split_weights[0] * rdf_results_interval['value'][interval_group[0]]
|
|
695
|
+
)
|
|
696
|
+
for i_interval, interval in enumerate(interval_group[1:]):
|
|
697
|
+
if (
|
|
698
|
+
rdf_results_interval['types'][interval]
|
|
699
|
+
!= rdf_results_interval['types'][interval - 1]
|
|
700
|
+
):
|
|
701
|
+
flag_logging_error = True
|
|
702
|
+
continue
|
|
703
|
+
if (
|
|
704
|
+
rdf_results_interval['variables_name'][interval]
|
|
705
|
+
!= rdf_results_interval['variables_name'][interval - 1]
|
|
706
|
+
):
|
|
707
|
+
flag_logging_error = True
|
|
708
|
+
continue
|
|
709
|
+
if not (
|
|
710
|
+
rdf_results_interval['bins'][interval]
|
|
711
|
+
== rdf_results_interval['bins'][interval - 1]
|
|
712
|
+
).all():
|
|
713
|
+
flag_logging_error = True
|
|
714
|
+
continue
|
|
715
|
+
rdf_values_avg += (
|
|
716
|
+
split_weights[i_interval + 1]
|
|
717
|
+
* rdf_results_interval['value'][interval]
|
|
718
|
+
)
|
|
719
|
+
if flag_logging_error:
|
|
720
|
+
LOGGER.error(
|
|
721
|
+
'Something went wrong in calc_molecular_rdf(). Some interval groups were skipped.'
|
|
722
|
+
)
|
|
723
|
+
rdf_results['types'].append(
|
|
724
|
+
rdf_results_interval['types'][interval_group[0]]
|
|
725
|
+
)
|
|
726
|
+
rdf_results['variables_name'].append(
|
|
727
|
+
rdf_results_interval['variables_name'][interval_group[0]]
|
|
728
|
+
)
|
|
729
|
+
rdf_results['bins'].append(
|
|
730
|
+
rdf_results_interval['bins'][interval_group[0]]
|
|
731
|
+
)
|
|
732
|
+
rdf_results['value'].append(rdf_values_avg)
|
|
733
|
+
rdf_results['frame_start'].append(
|
|
734
|
+
int(rdf_results_interval['frame_start'][interval_group[0]])
|
|
735
|
+
)
|
|
736
|
+
rdf_results['frame_end'].append(
|
|
737
|
+
int(rdf_results_interval['frame_end'][interval_group[-1]])
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
return rdf_results
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def __log_indices(first: int, last: int, num: int = 100):
|
|
744
|
+
ls = np.logspace(0, np.log10(last - first + 1), num=num)
|
|
745
|
+
return np.unique(np.int_(ls) - 1 + first)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def __correlation(function, positions: List[float]):
|
|
749
|
+
iterator = iter(positions)
|
|
750
|
+
start_frame = next(iterator)
|
|
751
|
+
return map(lambda f: function(start_frame, f), chain([start_frame], iterator))
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def _calc_diffusion_constant(
|
|
755
|
+
times: NDArray, values: NDArray, dim: int = 3
|
|
756
|
+
) -> tuple[float, float]:
|
|
757
|
+
"""
|
|
758
|
+
Determines the diffusion constant from a fit of the mean squared displacement
|
|
759
|
+
vs. time according to the Einstein relation.
|
|
760
|
+
"""
|
|
761
|
+
linear_model = linregress(times, values)
|
|
762
|
+
slope = linear_model.slope
|
|
763
|
+
error = linear_model.rvalue
|
|
764
|
+
return slope * 1 / (2 * dim), error
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def shifted_correlation_average(
|
|
768
|
+
function: Callable,
|
|
769
|
+
times: NDArray,
|
|
770
|
+
positions: NDArray,
|
|
771
|
+
index_distribution: Callable = __log_indices,
|
|
772
|
+
correlation: Callable = __correlation,
|
|
773
|
+
segments: int = 10,
|
|
774
|
+
window: float = 0.5,
|
|
775
|
+
skip: int = 0,
|
|
776
|
+
) -> tuple[NDArray, NDArray]:
|
|
777
|
+
"""
|
|
778
|
+
Code adapted from MDevaluate module: https://github.com/mdevaluate/mdevaluate.git
|
|
779
|
+
|
|
780
|
+
Calculate the time series for a correlation function.
|
|
781
|
+
|
|
782
|
+
The times at which the correlation is calculated are determined automatically by the
|
|
783
|
+
function given as ``index_distribution``. The default is a logarithmic distribution.
|
|
784
|
+
|
|
785
|
+
The function has been edited so that the average is always calculated, i.e., average=True below.
|
|
786
|
+
|
|
787
|
+
Args:
|
|
788
|
+
function: The function that should be correlated
|
|
789
|
+
positions: The coordinates of the simulation data
|
|
790
|
+
index_distribution (opt.):
|
|
791
|
+
A function that returns the indices for which the timeseries
|
|
792
|
+
will be calculated
|
|
793
|
+
correlation (function, opt.):
|
|
794
|
+
The correlation function
|
|
795
|
+
segments (int, opt.):
|
|
796
|
+
The number of segments the time window will be shifted
|
|
797
|
+
window (float, opt.):
|
|
798
|
+
The fraction of the simulation the time series will cover
|
|
799
|
+
skip (float, opt.):
|
|
800
|
+
The fraction of the trajectory that will be skipped at the beginning,
|
|
801
|
+
if this is None the start index of the frames slice will be used,
|
|
802
|
+
which defaults to 0.
|
|
803
|
+
counter (bool, opt.):
|
|
804
|
+
If True, returns length of frames (in general number of particles specified)
|
|
805
|
+
average (bool, opt.):
|
|
806
|
+
If True,
|
|
807
|
+
Returns:
|
|
808
|
+
tuple:
|
|
809
|
+
A list of length N that contains the indices of the frames at which
|
|
810
|
+
the time series was calculated and a numpy array of shape (segments, N)
|
|
811
|
+
that holds the (non-avaraged) correlation data
|
|
812
|
+
|
|
813
|
+
if has_counter == True: adds number of counts to output tupel.
|
|
814
|
+
if average is returned it will be weighted.
|
|
815
|
+
|
|
816
|
+
Example:
|
|
817
|
+
Calculating the mean square displacement of a coordinates object named ``coords``:
|
|
818
|
+
|
|
819
|
+
>>> indices, data = shifted_correlation(msd, coords)
|
|
820
|
+
"""
|
|
821
|
+
if window + skip >= 1:
|
|
822
|
+
LOGGER.warn(
|
|
823
|
+
'Invalid parameters for shifted_correlation(), ' 'resetting to defaults.',
|
|
824
|
+
)
|
|
825
|
+
window = 0.5
|
|
826
|
+
skip = 0
|
|
827
|
+
|
|
828
|
+
start_frames = np.unique(
|
|
829
|
+
np.linspace(
|
|
830
|
+
len(positions) * skip,
|
|
831
|
+
len(positions) * (1 - window),
|
|
832
|
+
num=segments,
|
|
833
|
+
endpoint=False,
|
|
834
|
+
dtype=int,
|
|
835
|
+
)
|
|
836
|
+
)
|
|
837
|
+
num_frames = int(len(positions) * (window))
|
|
838
|
+
|
|
839
|
+
idx = index_distribution(0, num_frames)
|
|
840
|
+
|
|
841
|
+
def correlate(start_frame):
|
|
842
|
+
shifted_idx = idx + start_frame
|
|
843
|
+
return correlation(function, map(positions.__getitem__, shifted_idx))
|
|
844
|
+
|
|
845
|
+
correlation_times = np.array([times[i] for i in idx]) - times[0]
|
|
846
|
+
|
|
847
|
+
result: NDArray
|
|
848
|
+
for i_start_frame, start_frame in enumerate(start_frames):
|
|
849
|
+
if i_start_frame == 0:
|
|
850
|
+
result = np.array(list(correlate(start_frame)))
|
|
851
|
+
else:
|
|
852
|
+
result += np.array(list(correlate(start_frame)))
|
|
853
|
+
result = np.array(result)
|
|
854
|
+
result = result / len(start_frames)
|
|
855
|
+
|
|
856
|
+
return correlation_times, result
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def calc_molecular_mean_squared_displacements(
|
|
860
|
+
universe: MDAnalysis.Universe, max_mols: int = 5000
|
|
861
|
+
) -> Dict:
|
|
862
|
+
"""
|
|
863
|
+
Calculates the mean squared displacement for the center of mass of each
|
|
864
|
+
molecule type.
|
|
865
|
+
|
|
866
|
+
max_mols: the maximum number of molecules per bead group for calculating the msd, for efficiency purposes.
|
|
867
|
+
50k was tested and is very fast and does not seem to have any memory issues.
|
|
868
|
+
"""
|
|
869
|
+
|
|
870
|
+
def parse_jumps(
|
|
871
|
+
universe: MDAnalysis.Universe, selection: MDAnalysis.AtomGroup
|
|
872
|
+
): # TODO Add output declaration
|
|
873
|
+
"""
|
|
874
|
+
See __get_nojump_positions().
|
|
875
|
+
"""
|
|
876
|
+
__ = universe.trajectory[0]
|
|
877
|
+
prev = np.array(selection.positions)
|
|
878
|
+
box = universe.trajectory[0].dimensions[:3]
|
|
879
|
+
sparse_data = namedtuple('SparseData', ['data', 'row', 'col']) # type: ignore[name-match]
|
|
880
|
+
jump_data = (
|
|
881
|
+
sparse_data(data=array('b'), row=array('l'), col=array('l')),
|
|
882
|
+
sparse_data(data=array('b'), row=array('l'), col=array('l')),
|
|
883
|
+
sparse_data(data=array('b'), row=array('l'), col=array('l')),
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
for i_frame, _ in enumerate(universe.trajectory[1:]):
|
|
887
|
+
curr = np.array(selection.positions)
|
|
888
|
+
delta = ((curr - prev) / box).round().astype(np.int8)
|
|
889
|
+
prev = np.array(curr)
|
|
890
|
+
for d in range(3):
|
|
891
|
+
(col,) = np.where(delta[:, d] != 0)
|
|
892
|
+
jump_data[d].col.extend(col)
|
|
893
|
+
jump_data[d].row.extend([i_frame] * len(col))
|
|
894
|
+
jump_data[d].data.extend(delta[col, d])
|
|
895
|
+
|
|
896
|
+
return jump_data
|
|
897
|
+
|
|
898
|
+
def generate_nojump_matrices(
|
|
899
|
+
universe: MDAnalysis.Universe, selection: MDAnalysis.AtomGroup
|
|
900
|
+
): # TODO Add output declaration
|
|
901
|
+
"""
|
|
902
|
+
See __get_nojump_positions().
|
|
903
|
+
"""
|
|
904
|
+
jump_data = parse_jumps(universe, selection)
|
|
905
|
+
n_frames = len(universe.trajectory)
|
|
906
|
+
n_atoms = selection.positions.shape[0]
|
|
907
|
+
|
|
908
|
+
nojump_matrices = tuple(
|
|
909
|
+
sparse.csr_matrix(
|
|
910
|
+
(np.array(m.data), (m.row, m.col)), shape=(n_frames, n_atoms)
|
|
911
|
+
)
|
|
912
|
+
for m in jump_data
|
|
913
|
+
)
|
|
914
|
+
return nojump_matrices
|
|
915
|
+
|
|
916
|
+
def get_nojump_positions(
|
|
917
|
+
universe: MDAnalysis.Universe, selection: MDAnalysis.AtomGroup
|
|
918
|
+
) -> NDArray:
|
|
919
|
+
"""
|
|
920
|
+
Unwraps the positions to create a continuous trajectory without jumps across periodic boundaries.
|
|
921
|
+
"""
|
|
922
|
+
nojump_matrices = generate_nojump_matrices(universe, selection)
|
|
923
|
+
box = universe.trajectory[0].dimensions[:3]
|
|
924
|
+
|
|
925
|
+
nojump_positions = []
|
|
926
|
+
for i_frame, __ in enumerate(universe.trajectory):
|
|
927
|
+
delta = (
|
|
928
|
+
np.array(
|
|
929
|
+
np.vstack([m[:i_frame, :].sum(axis=0) for m in nojump_matrices]).T
|
|
930
|
+
)
|
|
931
|
+
* box
|
|
932
|
+
)
|
|
933
|
+
nojump_positions.append(selection.positions - delta)
|
|
934
|
+
|
|
935
|
+
return np.array(nojump_positions)
|
|
936
|
+
|
|
937
|
+
def mean_squared_displacement(start: NDArray, current: NDArray):
|
|
938
|
+
"""
|
|
939
|
+
Calculates mean square displacement between current and initial (start) coordinates.
|
|
940
|
+
"""
|
|
941
|
+
vec = start - current
|
|
942
|
+
return (vec**2).sum(axis=1).mean()
|
|
943
|
+
|
|
944
|
+
if (
|
|
945
|
+
not universe
|
|
946
|
+
or not universe.trajectory
|
|
947
|
+
or universe.trajectory[0].dimensions is None
|
|
948
|
+
):
|
|
949
|
+
return {}
|
|
950
|
+
|
|
951
|
+
n_frames = universe.trajectory.n_frames
|
|
952
|
+
if n_frames < 50:
|
|
953
|
+
LOGGER.warn(
|
|
954
|
+
'At least 50 frames required to calculate molecular'
|
|
955
|
+
' mean squared displacements, skipping.',
|
|
956
|
+
)
|
|
957
|
+
return {}
|
|
958
|
+
|
|
959
|
+
dt = getattr(universe.trajectory, 'dt')
|
|
960
|
+
if dt is None:
|
|
961
|
+
LOGGER.warn(
|
|
962
|
+
'Universe is missing time step, cannot calculate molecular'
|
|
963
|
+
' mean squared displacements, skipping.',
|
|
964
|
+
)
|
|
965
|
+
return {}
|
|
966
|
+
times = np.arange(n_frames) * dt
|
|
967
|
+
|
|
968
|
+
bead_groups = _get_molecular_bead_groups(universe)
|
|
969
|
+
if bead_groups is {}:
|
|
970
|
+
return bead_groups
|
|
971
|
+
|
|
972
|
+
moltypes = [moltype for moltype in bead_groups.keys()]
|
|
973
|
+
del_list = []
|
|
974
|
+
for i_moltype, moltype in enumerate(moltypes):
|
|
975
|
+
if bead_groups[moltype]._nbeads > max_mols:
|
|
976
|
+
if max_mols > 50000:
|
|
977
|
+
LOGGER.warn(
|
|
978
|
+
'Calculating mean squared displacements for more than 50k molecules.'
|
|
979
|
+
' Expect long processing times!',
|
|
980
|
+
)
|
|
981
|
+
try:
|
|
982
|
+
# select max_mols nr. of rnd molecules from this moltype
|
|
983
|
+
moltype_indices = np.array(
|
|
984
|
+
[atom._ix for atom in bead_groups[moltype]._atoms]
|
|
985
|
+
)
|
|
986
|
+
molnums = universe.atoms.molnums[moltype_indices]
|
|
987
|
+
molnum_types = np.unique(molnums)
|
|
988
|
+
molnum_types_rnd = np.sort(
|
|
989
|
+
np.random.choice(molnum_types, size=max_mols)
|
|
990
|
+
)
|
|
991
|
+
atom_indices_rnd = np.concatenate(
|
|
992
|
+
[np.where(molnums == molnum)[0] for molnum in molnum_types_rnd]
|
|
993
|
+
)
|
|
994
|
+
selection = ' '.join([str(i) for i in atom_indices_rnd])
|
|
995
|
+
selection = f'index {selection}'
|
|
996
|
+
ags_moltype_rnd = universe.select_atoms(selection)
|
|
997
|
+
bead_groups[moltype] = BeadGroup(ags_moltype_rnd, compound='fragments')
|
|
998
|
+
LOGGER.warn(
|
|
999
|
+
'Maximum number of molecules for calculating the msd has been reached.'
|
|
1000
|
+
' Will make a random selection for calculation.'
|
|
1001
|
+
)
|
|
1002
|
+
except Exception:
|
|
1003
|
+
LOGGER.warn(
|
|
1004
|
+
'Error in selecting random molecules for large group when calculating msd. Skipping this molecule type.'
|
|
1005
|
+
)
|
|
1006
|
+
del_list.append(i_moltype)
|
|
1007
|
+
|
|
1008
|
+
for index in sorted(del_list, reverse=True):
|
|
1009
|
+
del moltypes[index]
|
|
1010
|
+
|
|
1011
|
+
msd_results: Dict[str, Any] = {}
|
|
1012
|
+
msd_results['type'] = 'molecular'
|
|
1013
|
+
msd_results['direction'] = 'xyz'
|
|
1014
|
+
msd_results['value'] = []
|
|
1015
|
+
msd_results['times'] = []
|
|
1016
|
+
msd_results['diffusion_constant'] = []
|
|
1017
|
+
msd_results['error_diffusion_constant'] = []
|
|
1018
|
+
for moltype in moltypes:
|
|
1019
|
+
positions = get_nojump_positions(universe, bead_groups[moltype])
|
|
1020
|
+
results = shifted_correlation_average(
|
|
1021
|
+
mean_squared_displacement, times, positions
|
|
1022
|
+
)
|
|
1023
|
+
if results:
|
|
1024
|
+
msd_results['value'].append(results[1])
|
|
1025
|
+
msd_results['times'].append(results[0])
|
|
1026
|
+
diffusion_constant, error = _calc_diffusion_constant(*results)
|
|
1027
|
+
msd_results['diffusion_constant'].append(diffusion_constant)
|
|
1028
|
+
msd_results['error_diffusion_constant'].append(error)
|
|
1029
|
+
|
|
1030
|
+
msd_results['types'] = moltypes
|
|
1031
|
+
msd_results['times'] = np.array(msd_results['times']) * ureg.picosecond
|
|
1032
|
+
msd_results['value'] = np.array(msd_results['value']) * ureg.angstrom**2
|
|
1033
|
+
msd_results['diffusion_constant'] = (
|
|
1034
|
+
np.array(msd_results['diffusion_constant']) * ureg.angstrom**2 / ureg.picosecond
|
|
1035
|
+
)
|
|
1036
|
+
msd_results['error_diffusion_constant'] = np.array(
|
|
1037
|
+
msd_results['error_diffusion_constant']
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
return msd_results
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
def calc_radius_of_gyration(
|
|
1044
|
+
universe: MDAnalysis.Universe, molecule_atom_indices: NDArray
|
|
1045
|
+
) -> Dict:
|
|
1046
|
+
"""
|
|
1047
|
+
Calculates the radius of gyration as a function of time for the atoms 'molecule_atom_indices'.
|
|
1048
|
+
"""
|
|
1049
|
+
if (
|
|
1050
|
+
not universe
|
|
1051
|
+
or not universe.trajectory
|
|
1052
|
+
or universe.trajectory[0].dimensions is None
|
|
1053
|
+
):
|
|
1054
|
+
return {}
|
|
1055
|
+
selection = ' '.join([str(i) for i in molecule_atom_indices])
|
|
1056
|
+
selection = f'index {selection}'
|
|
1057
|
+
molecule = universe.select_atoms(selection)
|
|
1058
|
+
rg_results: Dict[str, Any] = {}
|
|
1059
|
+
rg_results['type'] = 'molecular'
|
|
1060
|
+
rg_results['times'] = []
|
|
1061
|
+
rg_results['value'] = []
|
|
1062
|
+
time_unit = hasattr(universe.trajectory.time, 'units')
|
|
1063
|
+
for __ in universe.trajectory:
|
|
1064
|
+
rg_results['times'].append(
|
|
1065
|
+
universe.trajectory.time.magnitude
|
|
1066
|
+
if time_unit
|
|
1067
|
+
else universe.trajectory.time
|
|
1068
|
+
)
|
|
1069
|
+
rg_results['value'].append(molecule.radius_of_gyration())
|
|
1070
|
+
rg_results['n_frames'] = len(rg_results['times'])
|
|
1071
|
+
rg_results['times'] = (
|
|
1072
|
+
np.array(rg_results['times']) * time_unit
|
|
1073
|
+
if time_unit
|
|
1074
|
+
else np.array(rg_results['times'])
|
|
1075
|
+
)
|
|
1076
|
+
rg_results['value'] = np.array(rg_results['value']) * ureg.angstrom
|
|
1077
|
+
|
|
1078
|
+
return rg_results
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def calc_molecular_radius_of_gyration(
|
|
1082
|
+
universe: MDAnalysis.Universe, system_topology: MSection
|
|
1083
|
+
) -> List[Dict]:
|
|
1084
|
+
"""
|
|
1085
|
+
Calculates the radius of gyration as a function of time for each polymer in the system.
|
|
1086
|
+
"""
|
|
1087
|
+
if not system_topology:
|
|
1088
|
+
return []
|
|
1089
|
+
|
|
1090
|
+
rg_results = []
|
|
1091
|
+
for molgroup in system_topology:
|
|
1092
|
+
for molecule in molgroup.get('atoms_group'):
|
|
1093
|
+
sec_monomer_groups = molecule.get('atoms_group')
|
|
1094
|
+
group_type = sec_monomer_groups[0].type if sec_monomer_groups else None
|
|
1095
|
+
if group_type != 'monomer_group':
|
|
1096
|
+
continue
|
|
1097
|
+
rg_result = calc_radius_of_gyration(universe, molecule.atom_indices)
|
|
1098
|
+
rg_result['label'] = molecule.label + '-index_' + str(molecule.index)
|
|
1099
|
+
rg_result['atomsgroup_ref'] = molecule
|
|
1100
|
+
rg_results.append(rg_result)
|
|
1101
|
+
|
|
1102
|
+
return rg_results
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def get_molecules_from_bond_list(
|
|
1106
|
+
n_particles: int,
|
|
1107
|
+
bond_list: List[tuple],
|
|
1108
|
+
particle_types: List[str] = None,
|
|
1109
|
+
particles_typeid: array = None,
|
|
1110
|
+
) -> List[Dict]:
|
|
1111
|
+
"""
|
|
1112
|
+
Returns a list of dictionaries with molecule info from each instance in the list of bonds.
|
|
1113
|
+
"""
|
|
1114
|
+
system_graph = networkx.empty_graph(n_particles)
|
|
1115
|
+
system_graph.add_edges_from([(i[0], i[1]) for i in bond_list])
|
|
1116
|
+
molecules = [
|
|
1117
|
+
system_graph.subgraph(c).copy()
|
|
1118
|
+
for c in networkx.connected_components(system_graph)
|
|
1119
|
+
]
|
|
1120
|
+
molecule_info: List[Dict] = []
|
|
1121
|
+
molecule_dict: Dict = {}
|
|
1122
|
+
for mol in molecules:
|
|
1123
|
+
molecule_dict = {}
|
|
1124
|
+
molecule_dict['indices'] = np.array(mol.nodes())
|
|
1125
|
+
molecule_dict['bonds'] = np.array(mol.edges())
|
|
1126
|
+
molecule_dict['type'] = 'molecule'
|
|
1127
|
+
molecule_dict['is_molecule'] = True
|
|
1128
|
+
if particles_typeid is None and len(particle_types) == n_particles:
|
|
1129
|
+
molecule_dict['names'] = [
|
|
1130
|
+
particle_types[int(x)]
|
|
1131
|
+
for x in sorted(np.array(molecule_dict['indices']))
|
|
1132
|
+
]
|
|
1133
|
+
if particle_types is not None and particles_typeid is not None:
|
|
1134
|
+
molecule_dict['names'] = [
|
|
1135
|
+
particle_types[particles_typeid[int(x)]]
|
|
1136
|
+
for x in sorted(np.array(molecule_dict['indices']))
|
|
1137
|
+
]
|
|
1138
|
+
molecule_info.append(molecule_dict)
|
|
1139
|
+
return molecule_info
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
def is_same_molecule(mol_1: dict, mol_2: dict) -> bool:
|
|
1143
|
+
"""
|
|
1144
|
+
Checks whether the 2 input molecule dictionary (see "get_molecules_from_bond_list()" above)
|
|
1145
|
+
represent the same molecule type, i.e., same particle types and corresponding bond connections.
|
|
1146
|
+
"""
|
|
1147
|
+
|
|
1148
|
+
def get_bond_list_dict(mol):
|
|
1149
|
+
mol_shift = np.min(mol['indices'])
|
|
1150
|
+
mol_bonds_shift = mol['bonds'] - mol_shift
|
|
1151
|
+
bond_list = [
|
|
1152
|
+
sorted((mol['names'][i], mol['names'][j])) for i, j in mol_bonds_shift
|
|
1153
|
+
]
|
|
1154
|
+
bond_list_names, bond_list_counts = np.unique(
|
|
1155
|
+
bond_list, axis=0, return_counts=True
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
return {
|
|
1159
|
+
bond[0] + '-' + bond[1]: bond_list_counts[i_bond]
|
|
1160
|
+
for i_bond, bond in enumerate(bond_list_names)
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if sorted(mol_1['names']) != sorted(mol_2['names']):
|
|
1164
|
+
return False
|
|
1165
|
+
|
|
1166
|
+
bond_list_dict_1 = get_bond_list_dict(mol_1)
|
|
1167
|
+
bond_list_dict_2 = get_bond_list_dict(mol_2)
|
|
1168
|
+
|
|
1169
|
+
if bond_list_dict_1 == bond_list_dict_2:
|
|
1170
|
+
return True
|
|
1171
|
+
|
|
1172
|
+
return False
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
def get_composition(children_names: List[str]) -> str:
|
|
1176
|
+
"""
|
|
1177
|
+
Generates a generalized "chemical formula" based on the provided list `children_names`,
|
|
1178
|
+
with the format X(m)Y(n) for children_names X and Y of quantities m and n, respectively.
|
|
1179
|
+
"""
|
|
1180
|
+
children_count_tup = np.unique(children_names, return_counts=True)
|
|
1181
|
+
formula = ''.join([f'{name}({count})' for name, count in zip(*children_count_tup)])
|
|
1182
|
+
return formula
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
def mda_universe_from_nomad_atoms(system, logger=None):
|
|
1186
|
+
"""Returns an instance of mda.Universe from a NOMAD Atoms-section.
|
|
1187
|
+
|
|
1188
|
+
Args:
|
|
1189
|
+
system: The atoms to transform
|
|
1190
|
+
|
|
1191
|
+
Returns:
|
|
1192
|
+
A new mda.Universe created from the given data.
|
|
1193
|
+
"""
|
|
1194
|
+
n_atoms = len(system.positions)
|
|
1195
|
+
n_residues = 1
|
|
1196
|
+
atom_resindex = [0] * n_atoms
|
|
1197
|
+
residue_segindex = [0]
|
|
1198
|
+
|
|
1199
|
+
universe = MDAnalysis.Universe.empty(
|
|
1200
|
+
n_atoms,
|
|
1201
|
+
n_residues=n_residues,
|
|
1202
|
+
atom_resindex=atom_resindex,
|
|
1203
|
+
residue_segindex=residue_segindex,
|
|
1204
|
+
trajectory=True,
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
# Add positions
|
|
1208
|
+
universe.atoms.positions = system.positions.to(ureg.angstrom).magnitude
|
|
1209
|
+
|
|
1210
|
+
# Add atom attributes
|
|
1211
|
+
atom_names = system.labels
|
|
1212
|
+
universe.add_TopologyAttr('name', atom_names)
|
|
1213
|
+
universe.add_TopologyAttr('type', atom_names)
|
|
1214
|
+
universe.add_TopologyAttr('element', atom_names)
|
|
1215
|
+
|
|
1216
|
+
# Add the box dimensions
|
|
1217
|
+
if system.lattice_vectors is not None:
|
|
1218
|
+
universe.atoms.dimensions = atomutils.cell_to_cellpar(
|
|
1219
|
+
system.lattice_vectors.to(ureg.angstrom).magnitude, degrees=True
|
|
1220
|
+
)
|
|
1221
|
+
|
|
1222
|
+
return universe
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
class ThermostatParameters(ArchiveSection):
|
|
1226
|
+
"""
|
|
1227
|
+
Section containing the parameters pertaining to the thermostat for a molecular dynamics run.
|
|
1228
|
+
"""
|
|
1229
|
+
|
|
1230
|
+
m_def = Section(validate=False)
|
|
1231
|
+
|
|
1232
|
+
thermostat_type = Quantity(
|
|
1233
|
+
type=MEnum(
|
|
1234
|
+
'andersen',
|
|
1235
|
+
'berendsen',
|
|
1236
|
+
'brownian',
|
|
1237
|
+
'langevin_goga',
|
|
1238
|
+
'langevin_schneider',
|
|
1239
|
+
'nose_hoover',
|
|
1240
|
+
'velocity_rescaling',
|
|
1241
|
+
'velocity_rescaling_langevin',
|
|
1242
|
+
'velocity_rescaling_woodcock',
|
|
1243
|
+
'langevin_leap_frog',
|
|
1244
|
+
),
|
|
1245
|
+
shape=[],
|
|
1246
|
+
description="""
|
|
1247
|
+
The name of the thermostat used for temperature control. If skipped or an empty string is used, it
|
|
1248
|
+
means no thermostat was applied.
|
|
1249
|
+
|
|
1250
|
+
Allowed values are:
|
|
1251
|
+
|
|
1252
|
+
| Thermostat Name | Description |
|
|
1253
|
+
|
|
1254
|
+
| ---------------------- | ----------------------------------------- |
|
|
1255
|
+
|
|
1256
|
+
| `""` | No thermostat |
|
|
1257
|
+
|
|
1258
|
+
| `"andersen"` | H.C. Andersen, [J. Chem. Phys.
|
|
1259
|
+
**72**, 2384 (1980)](https://doi.org/10.1063/1.439486) |
|
|
1260
|
+
|
|
1261
|
+
| `"berendsen"` | H. J. C. Berendsen, J. P. M. Postma,
|
|
1262
|
+
W. F. van Gunsteren, A. DiNola, and J. R. Haak, [J. Chem. Phys.
|
|
1263
|
+
**81**, 3684 (1984)](https://doi.org/10.1063/1.448118) |
|
|
1264
|
+
|
|
1265
|
+
| `"brownian"` | Brownian Dynamics |
|
|
1266
|
+
|
|
1267
|
+
| `"langevin_goga"` | N. Goga, A. J. Rzepiela, A. H. de Vries,
|
|
1268
|
+
S. J. Marrink, and H. J. C. Berendsen, [J. Chem. Theory Comput. **8**, 3637 (2012)]
|
|
1269
|
+
(https://doi.org/10.1021/ct3000876) |
|
|
1270
|
+
|
|
1271
|
+
| `"langevin_schneider"` | T. Schneider and E. Stoll,
|
|
1272
|
+
[Phys. Rev. B **17**, 1302](https://doi.org/10.1103/PhysRevB.17.1302) |
|
|
1273
|
+
|
|
1274
|
+
| `"nose_hoover"` | S. Nosé, [Mol. Phys. **52**, 255 (1984)]
|
|
1275
|
+
(https://doi.org/10.1080/00268978400101201); W.G. Hoover, [Phys. Rev. A
|
|
1276
|
+
**31**, 1695 (1985) |
|
|
1277
|
+
|
|
1278
|
+
| `"velocity_rescaling"` | G. Bussi, D. Donadio, and M. Parrinello,
|
|
1279
|
+
[J. Chem. Phys. **126**, 014101 (2007)](https://doi.org/10.1063/1.2408420) |
|
|
1280
|
+
|
|
1281
|
+
| `"velocity_rescaling_langevin"` | G. Bussi and M. Parrinello,
|
|
1282
|
+
[Phys. Rev. E **75**, 056707 (2007)](https://doi.org/10.1103/PhysRevE.75.056707) |
|
|
1283
|
+
|
|
1284
|
+
| `"velocity_rescaling_woodcock"` | L. V. Woodcock,
|
|
1285
|
+
[Chem. Phys. Lett. **10**, 257 (1971)](https://doi.org/10.1016/0009-2614(71)80281-6) |
|
|
1286
|
+
|
|
1287
|
+
| `"langevin_leap_frog"` | J.A. Izaguirre, C.R. Sweet, and V.S. Pande
|
|
1288
|
+
[Pac Symp Biocomput. **15**, 240-251 (2010)](https://doi.org/10.1142/9789814295291_0026) |
|
|
1289
|
+
""",
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
reference_temperature = Quantity(
|
|
1293
|
+
type=np.float64,
|
|
1294
|
+
shape=[],
|
|
1295
|
+
unit='kelvin',
|
|
1296
|
+
description="""
|
|
1297
|
+
The target temperature for the simulation. Typically used when temperature_profile is "constant".
|
|
1298
|
+
""",
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
coupling_constant = Quantity(
|
|
1302
|
+
type=np.float64,
|
|
1303
|
+
shape=[],
|
|
1304
|
+
unit='s',
|
|
1305
|
+
description="""
|
|
1306
|
+
The time constant for temperature coupling. Need to describe what this means for the various
|
|
1307
|
+
thermostat options...
|
|
1308
|
+
""",
|
|
1309
|
+
)
|
|
1310
|
+
|
|
1311
|
+
effective_mass = Quantity(
|
|
1312
|
+
type=np.float64,
|
|
1313
|
+
shape=[],
|
|
1314
|
+
unit='kilogram',
|
|
1315
|
+
description="""
|
|
1316
|
+
The effective or fictitious mass of the temperature resevoir.
|
|
1317
|
+
""",
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
temperature_profile = Quantity(
|
|
1321
|
+
type=MEnum('constant', 'linear', 'exponential'),
|
|
1322
|
+
shape=[],
|
|
1323
|
+
description="""
|
|
1324
|
+
Type of temperature control (i.e., annealing) procedure. Can be "constant" (no annealing), "linear", or "exponential".
|
|
1325
|
+
If linear, "temperature_update_delta" specifies the corresponding update parameter.
|
|
1326
|
+
If exponential, "temperature_update_factor" specifies the corresponding update parameter.
|
|
1327
|
+
""",
|
|
1328
|
+
)
|
|
1329
|
+
|
|
1330
|
+
reference_temperature_start = Quantity(
|
|
1331
|
+
type=np.float64,
|
|
1332
|
+
shape=[],
|
|
1333
|
+
unit='kelvin',
|
|
1334
|
+
description="""
|
|
1335
|
+
The initial target temperature for the simulation. Typically used when temperature_profile is "linear" or "exponential".
|
|
1336
|
+
""",
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
reference_temperature_end = Quantity(
|
|
1340
|
+
type=np.float64,
|
|
1341
|
+
shape=[],
|
|
1342
|
+
unit='kelvin',
|
|
1343
|
+
description="""
|
|
1344
|
+
The final target temperature for the simulation. Typically used when temperature_profile is "linear" or "exponential".
|
|
1345
|
+
""",
|
|
1346
|
+
)
|
|
1347
|
+
|
|
1348
|
+
temperature_update_frequency = Quantity(
|
|
1349
|
+
type=int,
|
|
1350
|
+
shape=[],
|
|
1351
|
+
description="""
|
|
1352
|
+
Number of simulation steps between changing the target temperature.
|
|
1353
|
+
""",
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
temperature_update_delta = Quantity(
|
|
1357
|
+
type=np.float64,
|
|
1358
|
+
shape=[],
|
|
1359
|
+
description="""
|
|
1360
|
+
Amount to be added (subtracted if negative) to the current reference_temperature
|
|
1361
|
+
at a frequency of temperature_update_frequency when temperature_profile is "linear".
|
|
1362
|
+
The reference temperature is then replaced by this new value until the next update.
|
|
1363
|
+
""",
|
|
1364
|
+
)
|
|
1365
|
+
|
|
1366
|
+
temperature_update_factor = Quantity(
|
|
1367
|
+
type=np.float64,
|
|
1368
|
+
shape=[],
|
|
1369
|
+
description="""
|
|
1370
|
+
Factor to be multiplied to the current reference_temperature at a frequency of temperature_update_frequency when temperature_profile is exponential.
|
|
1371
|
+
The reference temperature is then replaced by this new value until the next update.
|
|
1372
|
+
""",
|
|
1373
|
+
)
|
|
1374
|
+
|
|
1375
|
+
step_start = Quantity(
|
|
1376
|
+
type=int,
|
|
1377
|
+
shape=[],
|
|
1378
|
+
description="""
|
|
1379
|
+
Trajectory step where this thermostating starts.
|
|
1380
|
+
""",
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
step_end = Quantity(
|
|
1384
|
+
type=int,
|
|
1385
|
+
shape=[],
|
|
1386
|
+
description="""
|
|
1387
|
+
Trajectory step number where this thermostating ends.
|
|
1388
|
+
""",
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
class BarostatParameters(ArchiveSection):
|
|
1393
|
+
"""
|
|
1394
|
+
Section containing the parameters pertaining to the barostat for a molecular dynamics run.
|
|
1395
|
+
"""
|
|
1396
|
+
|
|
1397
|
+
m_def = Section(validate=False)
|
|
1398
|
+
|
|
1399
|
+
barostat_type = Quantity(
|
|
1400
|
+
type=MEnum(
|
|
1401
|
+
'berendsen',
|
|
1402
|
+
'martyna_tuckerman_tobias_klein',
|
|
1403
|
+
'nose_hoover',
|
|
1404
|
+
'parrinello_rahman',
|
|
1405
|
+
'stochastic_cell_rescaling',
|
|
1406
|
+
),
|
|
1407
|
+
shape=[],
|
|
1408
|
+
description="""
|
|
1409
|
+
The name of the barostat used for temperature control. If skipped or an empty string is used, it
|
|
1410
|
+
means no barostat was applied.
|
|
1411
|
+
|
|
1412
|
+
Allowed values are:
|
|
1413
|
+
|
|
1414
|
+
| Barostat Name | Description |
|
|
1415
|
+
|
|
1416
|
+
| ---------------------- | ----------------------------------------- |
|
|
1417
|
+
|
|
1418
|
+
| `""` | No thermostat |
|
|
1419
|
+
|
|
1420
|
+
| `"berendsen"` | H. J. C. Berendsen, J. P. M. Postma,
|
|
1421
|
+
W. F. van Gunsteren, A. DiNola, and J. R. Haak, [J. Chem. Phys.
|
|
1422
|
+
**81**, 3684 (1984)](https://doi.org/10.1063/1.448118) |
|
|
1423
|
+
|
|
1424
|
+
| `"martyna_tuckerman_tobias_klein"` | G.J. Martyna, M.E. Tuckerman, D.J. Tobias, and M.L. Klein,
|
|
1425
|
+
[Mol. Phys. **87**, 1117 (1996)](https://doi.org/10.1080/00268979600100761);
|
|
1426
|
+
M.E. Tuckerman, J. Alejandre, R. López-Rendón, A.L. Jochim, and G.J. Martyna,
|
|
1427
|
+
[J. Phys. A. **59**, 5629 (2006)](https://doi.org/10.1088/0305-4470/39/19/S18)|
|
|
1428
|
+
|
|
1429
|
+
| `"nose_hoover"` | S. Nosé, [Mol. Phys. **52**, 255 (1984)]
|
|
1430
|
+
(https://doi.org/10.1080/00268978400101201); W.G. Hoover, [Phys. Rev. A
|
|
1431
|
+
**31**, 1695 (1985) |
|
|
1432
|
+
|
|
1433
|
+
| `"parrinello_rahman"` | M. Parrinello and A. Rahman,
|
|
1434
|
+
[J. Appl. Phys. **52**, 7182 (1981)](https://doi.org/10.1063/1.328693);
|
|
1435
|
+
S. Nosé and M.L. Klein, [Mol. Phys. **50**, 1055 (1983) |
|
|
1436
|
+
|
|
1437
|
+
| `"stochastic_cell_rescaling"` | M. Bernetti and G. Bussi,
|
|
1438
|
+
[J. Chem. Phys. **153**, 114107 (2020)](https://doi.org/10.1063/1.2408420) |
|
|
1439
|
+
""",
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
coupling_type = Quantity(
|
|
1443
|
+
type=MEnum('isotropic', 'semi_isotropic', 'anisotropic'),
|
|
1444
|
+
shape=[],
|
|
1445
|
+
description="""
|
|
1446
|
+
Describes the symmetry of pressure coupling. Specifics can be inferred from the `coupling constant`
|
|
1447
|
+
|
|
1448
|
+
| Type | Description |
|
|
1449
|
+
|
|
1450
|
+
| ---------------------- | ----------------------------------------- |
|
|
1451
|
+
|
|
1452
|
+
| `isotropic` | Identical coupling in all directions. |
|
|
1453
|
+
|
|
1454
|
+
| `semi_isotropic` | Identical coupling in 2 directions. |
|
|
1455
|
+
|
|
1456
|
+
| `anisotropic` | General case. |
|
|
1457
|
+
""",
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
reference_pressure = Quantity(
|
|
1461
|
+
type=np.float64,
|
|
1462
|
+
shape=[3, 3],
|
|
1463
|
+
unit='pascal',
|
|
1464
|
+
description="""
|
|
1465
|
+
The target pressure for the simulation, stored in a 3x3 matrix, indicating the values for individual directions
|
|
1466
|
+
along the diagonal, and coupling between directions on the off-diagonal. Typically used when pressure_profile is "constant".
|
|
1467
|
+
""",
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1470
|
+
coupling_constant = Quantity(
|
|
1471
|
+
type=np.float64,
|
|
1472
|
+
shape=[3, 3],
|
|
1473
|
+
unit='s',
|
|
1474
|
+
description="""
|
|
1475
|
+
The time constants for pressure coupling, stored in a 3x3 matrix, indicating the values for individual directions
|
|
1476
|
+
along the diagonal, and coupling between directions on the off-diagonal. 0 values along the off-diagonal
|
|
1477
|
+
indicate no-coupling between these directions.
|
|
1478
|
+
""",
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
compressibility = Quantity(
|
|
1482
|
+
type=np.float64,
|
|
1483
|
+
shape=[3, 3],
|
|
1484
|
+
unit='1 / pascal',
|
|
1485
|
+
description="""
|
|
1486
|
+
An estimate of the system's compressibility, used for box rescaling, stored in a 3x3 matrix indicating the values for individual directions
|
|
1487
|
+
along the diagonal, and coupling between directions on the off-diagonal. If None, it may indicate that these values
|
|
1488
|
+
are incorporated into the coupling_constant, or simply that the software used uses a fixed value that is not available in
|
|
1489
|
+
the input/output files.
|
|
1490
|
+
""",
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
pressure_profile = Quantity(
|
|
1494
|
+
type=MEnum('constant', 'linear', 'exponential'),
|
|
1495
|
+
shape=[],
|
|
1496
|
+
description="""
|
|
1497
|
+
Type of pressure control procedure. Can be "constant" (no annealing), "linear", or "exponential".
|
|
1498
|
+
If linear, "pressure_update_delta" specifies the corresponding update parameter.
|
|
1499
|
+
If exponential, "pressure_update_factor" specifies the corresponding update parameter.
|
|
1500
|
+
""",
|
|
1501
|
+
)
|
|
1502
|
+
|
|
1503
|
+
reference_pressure_start = Quantity(
|
|
1504
|
+
type=np.float64,
|
|
1505
|
+
shape=[3, 3],
|
|
1506
|
+
unit='pascal',
|
|
1507
|
+
description="""
|
|
1508
|
+
The initial target pressure for the simulation, stored in a 3x3 matrix, indicating the values for individual directions
|
|
1509
|
+
along the diagonal, and coupling between directions on the off-diagonal. Typically used when pressure_profile is "linear" or "exponential".
|
|
1510
|
+
""",
|
|
1511
|
+
)
|
|
1512
|
+
|
|
1513
|
+
reference_pressure_end = Quantity(
|
|
1514
|
+
type=np.float64,
|
|
1515
|
+
shape=[3, 3],
|
|
1516
|
+
unit='pascal',
|
|
1517
|
+
description="""
|
|
1518
|
+
The final target pressure for the simulation, stored in a 3x3 matrix, indicating the values for individual directions
|
|
1519
|
+
along the diagonal, and coupling between directions on the off-diagonal. Typically used when pressure_profile is "linear" or "exponential".
|
|
1520
|
+
""",
|
|
1521
|
+
)
|
|
1522
|
+
|
|
1523
|
+
pressure_update_frequency = Quantity(
|
|
1524
|
+
type=int,
|
|
1525
|
+
shape=[],
|
|
1526
|
+
description="""
|
|
1527
|
+
Number of simulation steps between changing the target pressure.
|
|
1528
|
+
""",
|
|
1529
|
+
)
|
|
1530
|
+
|
|
1531
|
+
pressure_update_delta = Quantity(
|
|
1532
|
+
type=np.float64,
|
|
1533
|
+
shape=[],
|
|
1534
|
+
description="""
|
|
1535
|
+
Amount to be added (subtracted if negative) to the current reference_pressure
|
|
1536
|
+
at a frequency of pressure_update_frequency when pressure_profile is "linear".
|
|
1537
|
+
The pressure temperature is then replaced by this new value until the next update.
|
|
1538
|
+
""",
|
|
1539
|
+
)
|
|
1540
|
+
|
|
1541
|
+
pressure_update_factor = Quantity(
|
|
1542
|
+
type=np.float64,
|
|
1543
|
+
shape=[],
|
|
1544
|
+
description="""
|
|
1545
|
+
Factor to be multiplied to the current reference_pressure at a frequency of pressure_update_frequency when pressure_profile is exponential.
|
|
1546
|
+
The reference pressure is then replaced by this new value until the next update.
|
|
1547
|
+
""",
|
|
1548
|
+
)
|
|
1549
|
+
|
|
1550
|
+
step_start = Quantity(
|
|
1551
|
+
type=int,
|
|
1552
|
+
shape=[],
|
|
1553
|
+
description="""
|
|
1554
|
+
Trajectory step where this barostating starts.
|
|
1555
|
+
""",
|
|
1556
|
+
)
|
|
1557
|
+
|
|
1558
|
+
step_end = Quantity(
|
|
1559
|
+
type=int,
|
|
1560
|
+
shape=[],
|
|
1561
|
+
description="""
|
|
1562
|
+
Trajectory step number where this barostating ends.
|
|
1563
|
+
""",
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
class Lambdas(ArchiveSection):
|
|
1568
|
+
"""
|
|
1569
|
+
Section for storing all lambda parameters for free energy perturbation
|
|
1570
|
+
"""
|
|
1571
|
+
|
|
1572
|
+
m_def = Section(validate=False)
|
|
1573
|
+
|
|
1574
|
+
type = Quantity(
|
|
1575
|
+
type=MEnum(
|
|
1576
|
+
'output', 'coulomb', 'vdw', 'bonded', 'restraint', 'mass', 'temperature'
|
|
1577
|
+
),
|
|
1578
|
+
shape=[],
|
|
1579
|
+
description="""
|
|
1580
|
+
The type of lambda interpolation
|
|
1581
|
+
|
|
1582
|
+
Allowed values are:
|
|
1583
|
+
|
|
1584
|
+
| type | Description |
|
|
1585
|
+
|
|
1586
|
+
| ---------------------- | ----------------------------------------- |
|
|
1587
|
+
|
|
1588
|
+
| `"output"` | Lambdas for the free energy outputs saved.
|
|
1589
|
+
These will also act as a default in case some
|
|
1590
|
+
relevant lambdas are not specified. |
|
|
1591
|
+
|
|
1592
|
+
| `"coulomb"` | Lambdas for interpolating electrostatic interactions. |
|
|
1593
|
+
|
|
1594
|
+
| `"vdw"` | Lambdas for interpolating van der Waals interactions. |
|
|
1595
|
+
|
|
1596
|
+
| `"bonded"` | Lambdas for interpolating all intramolecular interactions. |
|
|
1597
|
+
|
|
1598
|
+
| `"restraint"` | Lambdas for interpolating restraints. |
|
|
1599
|
+
|
|
1600
|
+
| `"mass"` | Lambdas for interpolating masses. |
|
|
1601
|
+
|
|
1602
|
+
| `"temperature"` | Lambdas for interpolating temperature. |
|
|
1603
|
+
""",
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
value = Quantity(
|
|
1607
|
+
type=np.float64,
|
|
1608
|
+
shape=[],
|
|
1609
|
+
description="""
|
|
1610
|
+
List of lambdas.
|
|
1611
|
+
""",
|
|
1612
|
+
)
|
|
1613
|
+
|
|
1614
|
+
|
|
1615
|
+
class FreeEnergyCalculationParameters(ArchiveSection):
|
|
1616
|
+
"""
|
|
1617
|
+
Section containing the parameters pertaining to a free energy calculation workflow
|
|
1618
|
+
that interpolates between two system states (defined via the interpolation parameter lambda).
|
|
1619
|
+
The parameters are stored for each molecular dynamics run separately, to be referenced
|
|
1620
|
+
by the overarching workflow.
|
|
1621
|
+
"""
|
|
1622
|
+
|
|
1623
|
+
m_def = Section(validate=False)
|
|
1624
|
+
|
|
1625
|
+
type = Quantity(
|
|
1626
|
+
type=MEnum('alchemical', 'umbrella_sampling'),
|
|
1627
|
+
shape=[],
|
|
1628
|
+
description="""
|
|
1629
|
+
Specifies the type of workflow. Allowed values are:
|
|
1630
|
+
|
|
1631
|
+
| kind | Description |
|
|
1632
|
+
|
|
1633
|
+
| ---------------------- | ----------------------------------------- |
|
|
1634
|
+
|
|
1635
|
+
| `"alchemical"` | A non-physical transformation between 2 well-defined systems,
|
|
1636
|
+
typically achieved by smoothly interpolating between Hamiltonians or force fields. |
|
|
1637
|
+
|
|
1638
|
+
| `"umbrella_sampling"` | A sampling of the path between 2 well-defined (sub)states of a system,
|
|
1639
|
+
typically achieved by applying a biasing force to the force field along a
|
|
1640
|
+
specified reaction coordinate.
|
|
1641
|
+
""",
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1644
|
+
lambdas = SubSection(
|
|
1645
|
+
sub_section=Lambdas.m_def,
|
|
1646
|
+
description="""
|
|
1647
|
+
Contains the lists of lambda values defined for the interpolation of the system.
|
|
1648
|
+
""",
|
|
1649
|
+
repeats=True,
|
|
1650
|
+
)
|
|
1651
|
+
|
|
1652
|
+
lambda_index = Quantity(
|
|
1653
|
+
type=int,
|
|
1654
|
+
shape=[],
|
|
1655
|
+
description="""
|
|
1656
|
+
The index of the lambda in `lambdas` corresponding to the state of the current simulation.
|
|
1657
|
+
""",
|
|
1658
|
+
)
|
|
1659
|
+
|
|
1660
|
+
atom_indices = Quantity(
|
|
1661
|
+
type=np.dtype(np.int32),
|
|
1662
|
+
shape=[],
|
|
1663
|
+
description="""
|
|
1664
|
+
List of atom indices involved in the interpolation.
|
|
1665
|
+
""",
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1668
|
+
initial_state_vdw = Quantity(
|
|
1669
|
+
type=bool,
|
|
1670
|
+
shape=[],
|
|
1671
|
+
description="""
|
|
1672
|
+
Specifies whether vdw interactions are on (True) or off (False) in the initial state (i.e., lambda = 0).
|
|
1673
|
+
""",
|
|
1674
|
+
)
|
|
1675
|
+
|
|
1676
|
+
final_state_vdw = Quantity(
|
|
1677
|
+
type=bool,
|
|
1678
|
+
shape=[],
|
|
1679
|
+
description="""
|
|
1680
|
+
Specifies whether vdw interactions are on (True) or off (False) in the final state (i.e., lambda = 0).
|
|
1681
|
+
""",
|
|
1682
|
+
)
|
|
1683
|
+
|
|
1684
|
+
initial_state_coloumb = Quantity(
|
|
1685
|
+
type=bool,
|
|
1686
|
+
shape=[],
|
|
1687
|
+
description="""
|
|
1688
|
+
Specifies whether vdw interactions are on (True) or off (False) in the initial state (i.e., lambda = 0).
|
|
1689
|
+
""",
|
|
1690
|
+
)
|
|
1691
|
+
|
|
1692
|
+
final_state_coloumb = Quantity(
|
|
1693
|
+
type=bool,
|
|
1694
|
+
shape=[],
|
|
1695
|
+
description="""
|
|
1696
|
+
Specifies whether vdw interactions are on (True) or off (False) in the final state (i.e., lambda = 0).
|
|
1697
|
+
""",
|
|
1698
|
+
)
|
|
1699
|
+
|
|
1700
|
+
initial_state_bonded = Quantity(
|
|
1701
|
+
type=bool,
|
|
1702
|
+
shape=[],
|
|
1703
|
+
description="""
|
|
1704
|
+
Specifies whether bonded interactions are on (True) or off (False) in the initial state (i.e., lambda = 0).
|
|
1705
|
+
""",
|
|
1706
|
+
)
|
|
1707
|
+
|
|
1708
|
+
final_state_bonded = Quantity(
|
|
1709
|
+
type=bool,
|
|
1710
|
+
shape=[],
|
|
1711
|
+
description="""
|
|
1712
|
+
Specifies whether bonded interactions are on (True) or off (False) in the final state (i.e., lambda = 0).
|
|
1713
|
+
""",
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
class MolecularDynamicsMethod(SimulationWorkflowMethod):
|
|
1718
|
+
thermodynamic_ensemble = Quantity(
|
|
1719
|
+
type=MEnum('NVE', 'NVT', 'NPT', 'NPH'),
|
|
1720
|
+
shape=[],
|
|
1721
|
+
description="""
|
|
1722
|
+
The type of thermodynamic ensemble that was simulated.
|
|
1723
|
+
|
|
1724
|
+
Allowed values are:
|
|
1725
|
+
|
|
1726
|
+
| Thermodynamic Ensemble | Description |
|
|
1727
|
+
|
|
1728
|
+
| ---------------------- | ----------------------------------------- |
|
|
1729
|
+
|
|
1730
|
+
| `"NVE"` | Constant number of particles, volume, and energy |
|
|
1731
|
+
|
|
1732
|
+
| `"NVT"` | Constant number of particles, volume, and temperature |
|
|
1733
|
+
|
|
1734
|
+
| `"NPT"` | Constant number of particles, pressure, and temperature |
|
|
1735
|
+
|
|
1736
|
+
| `"NPH"` | Constant number of particles, pressure, and enthalpy |
|
|
1737
|
+
""",
|
|
1738
|
+
)
|
|
1739
|
+
|
|
1740
|
+
integrator_type = Quantity(
|
|
1741
|
+
type=MEnum(
|
|
1742
|
+
'brownian',
|
|
1743
|
+
'conjugant_gradient',
|
|
1744
|
+
'langevin_goga',
|
|
1745
|
+
'langevin_schneider',
|
|
1746
|
+
'leap_frog',
|
|
1747
|
+
'rRESPA_multitimescale',
|
|
1748
|
+
'velocity_verlet',
|
|
1749
|
+
'langevin_leap_frog',
|
|
1750
|
+
),
|
|
1751
|
+
shape=[],
|
|
1752
|
+
description="""
|
|
1753
|
+
Name of the integrator.
|
|
1754
|
+
|
|
1755
|
+
Allowed values are:
|
|
1756
|
+
|
|
1757
|
+
| Integrator Name | Description |
|
|
1758
|
+
|
|
1759
|
+
| ---------------------- | ----------------------------------------- |
|
|
1760
|
+
|
|
1761
|
+
| `"langevin_goga"` | N. Goga, A. J. Rzepiela, A. H. de Vries,
|
|
1762
|
+
S. J. Marrink, and H. J. C. Berendsen, [J. Chem. Theory Comput. **8**, 3637 (2012)]
|
|
1763
|
+
(https://doi.org/10.1021/ct3000876) |
|
|
1764
|
+
|
|
1765
|
+
| `"langevin_schneider"` | T. Schneider and E. Stoll,
|
|
1766
|
+
[Phys. Rev. B **17**, 1302](https://doi.org/10.1103/PhysRevB.17.1302) |
|
|
1767
|
+
|
|
1768
|
+
| `"leap_frog"` | R.W. Hockney, S.P. Goel, and J. Eastwood,
|
|
1769
|
+
[J. Comp. Phys. **14**, 148 (1974)](https://doi.org/10.1016/0021-9991(74)90010-2) |
|
|
1770
|
+
|
|
1771
|
+
| `"velocity_verlet"` | W.C. Swope, H.C. Andersen, P.H. Berens, and K.R. Wilson,
|
|
1772
|
+
[J. Chem. Phys. **76**, 637 (1982)](https://doi.org/10.1063/1.442716) |
|
|
1773
|
+
|
|
1774
|
+
| `"rRESPA_multitimescale"` | M. Tuckerman, B. J. Berne, and G. J. Martyna
|
|
1775
|
+
[J. Chem. Phys. **97**, 1990 (1992)](https://doi.org/10.1063/1.463137) |
|
|
1776
|
+
|
|
1777
|
+
| `"langevin_leap_frog"` | J.A. Izaguirre, C.R. Sweet, and V.S. Pande
|
|
1778
|
+
[Pac Symp Biocomput. **15**, 240-251 (2010)](https://doi.org/10.1142/9789814295291_0026) |
|
|
1779
|
+
""",
|
|
1780
|
+
)
|
|
1781
|
+
|
|
1782
|
+
integration_timestep = Quantity(
|
|
1783
|
+
type=np.float64,
|
|
1784
|
+
shape=[],
|
|
1785
|
+
unit='s',
|
|
1786
|
+
description="""
|
|
1787
|
+
The timestep at which the numerical integration is performed.
|
|
1788
|
+
""",
|
|
1789
|
+
)
|
|
1790
|
+
|
|
1791
|
+
n_steps = Quantity(
|
|
1792
|
+
type=int,
|
|
1793
|
+
shape=[],
|
|
1794
|
+
description="""
|
|
1795
|
+
Number of timesteps performed.
|
|
1796
|
+
""",
|
|
1797
|
+
)
|
|
1798
|
+
|
|
1799
|
+
coordinate_save_frequency = Quantity(
|
|
1800
|
+
type=int,
|
|
1801
|
+
shape=[],
|
|
1802
|
+
description="""
|
|
1803
|
+
The number of timesteps between saving the coordinates.
|
|
1804
|
+
""",
|
|
1805
|
+
)
|
|
1806
|
+
|
|
1807
|
+
velocity_save_frequency = Quantity(
|
|
1808
|
+
type=int,
|
|
1809
|
+
shape=[],
|
|
1810
|
+
description="""
|
|
1811
|
+
The number of timesteps between saving the velocities.
|
|
1812
|
+
""",
|
|
1813
|
+
)
|
|
1814
|
+
|
|
1815
|
+
force_save_frequency = Quantity(
|
|
1816
|
+
type=int,
|
|
1817
|
+
shape=[],
|
|
1818
|
+
description="""
|
|
1819
|
+
The number of timesteps between saving the forces.
|
|
1820
|
+
""",
|
|
1821
|
+
)
|
|
1822
|
+
|
|
1823
|
+
thermodynamics_save_frequency = Quantity(
|
|
1824
|
+
type=int,
|
|
1825
|
+
shape=[],
|
|
1826
|
+
description="""
|
|
1827
|
+
The number of timesteps between saving the thermodynamic quantities.
|
|
1828
|
+
""",
|
|
1829
|
+
)
|
|
1830
|
+
|
|
1831
|
+
thermostat_parameters = SubSection(
|
|
1832
|
+
sub_section=ThermostatParameters.m_def, repeats=True
|
|
1833
|
+
)
|
|
1834
|
+
|
|
1835
|
+
barostat_parameters = SubSection(sub_section=BarostatParameters.m_def, repeats=True)
|
|
1836
|
+
|
|
1837
|
+
free_energy_calculation_parameters = SubSection(
|
|
1838
|
+
sub_section=FreeEnergyCalculationParameters.m_def, repeats=True
|
|
1839
|
+
)
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
class Property(ArchiveSection):
|
|
1843
|
+
"""
|
|
1844
|
+
Generic parent section for all property types.
|
|
1845
|
+
"""
|
|
1846
|
+
|
|
1847
|
+
m_def = Section(validate=False)
|
|
1848
|
+
|
|
1849
|
+
type = Quantity(
|
|
1850
|
+
type=MEnum('molecular', 'atomic'),
|
|
1851
|
+
shape=[],
|
|
1852
|
+
description="""
|
|
1853
|
+
Describes if the observable is calculated at the molecular or atomic level.
|
|
1854
|
+
""",
|
|
1855
|
+
)
|
|
1856
|
+
|
|
1857
|
+
label = Quantity(
|
|
1858
|
+
type=str,
|
|
1859
|
+
shape=[],
|
|
1860
|
+
description="""
|
|
1861
|
+
Name or description of the property.
|
|
1862
|
+
""",
|
|
1863
|
+
)
|
|
1864
|
+
|
|
1865
|
+
error_type = Quantity(
|
|
1866
|
+
type=str,
|
|
1867
|
+
shape=[],
|
|
1868
|
+
description="""
|
|
1869
|
+
Describes the type of error reported for this observable.
|
|
1870
|
+
""",
|
|
1871
|
+
)
|
|
1872
|
+
|
|
1873
|
+
|
|
1874
|
+
class PropertyValues(MSection):
|
|
1875
|
+
"""
|
|
1876
|
+
Generic parent section for information regarding the values of a property.
|
|
1877
|
+
"""
|
|
1878
|
+
|
|
1879
|
+
m_def = Section(validate=False)
|
|
1880
|
+
|
|
1881
|
+
label = Quantity(
|
|
1882
|
+
type=str,
|
|
1883
|
+
shape=[],
|
|
1884
|
+
description="""
|
|
1885
|
+
Describes the atoms or molecule types involved in determining the property.
|
|
1886
|
+
""",
|
|
1887
|
+
)
|
|
1888
|
+
|
|
1889
|
+
errors = Quantity(
|
|
1890
|
+
type=np.float64,
|
|
1891
|
+
shape=['*'],
|
|
1892
|
+
description="""
|
|
1893
|
+
Error associated with the determination of the property.
|
|
1894
|
+
""",
|
|
1895
|
+
)
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
class EnsemblePropertyValues(PropertyValues):
|
|
1899
|
+
"""
|
|
1900
|
+
Generic section containing information regarding the values of an ensemble property.
|
|
1901
|
+
"""
|
|
1902
|
+
|
|
1903
|
+
m_def = Section(validate=False)
|
|
1904
|
+
|
|
1905
|
+
n_bins = Quantity(
|
|
1906
|
+
type=int,
|
|
1907
|
+
shape=[],
|
|
1908
|
+
description="""
|
|
1909
|
+
Number of bins.
|
|
1910
|
+
""",
|
|
1911
|
+
)
|
|
1912
|
+
|
|
1913
|
+
frame_start = Quantity(
|
|
1914
|
+
type=int,
|
|
1915
|
+
shape=[],
|
|
1916
|
+
description="""
|
|
1917
|
+
Trajectory frame number where the ensemble averaging starts.
|
|
1918
|
+
""",
|
|
1919
|
+
)
|
|
1920
|
+
|
|
1921
|
+
frame_end = Quantity(
|
|
1922
|
+
type=int,
|
|
1923
|
+
shape=[],
|
|
1924
|
+
description="""
|
|
1925
|
+
Trajectory frame number where the ensemble averaging ends.
|
|
1926
|
+
""",
|
|
1927
|
+
)
|
|
1928
|
+
|
|
1929
|
+
bins_magnitude = Quantity(
|
|
1930
|
+
type=np.float64,
|
|
1931
|
+
shape=['n_bins'],
|
|
1932
|
+
description="""
|
|
1933
|
+
Values of the variable along which the property is calculated.
|
|
1934
|
+
""",
|
|
1935
|
+
)
|
|
1936
|
+
|
|
1937
|
+
bins_unit = Quantity(
|
|
1938
|
+
type=str,
|
|
1939
|
+
shape=[],
|
|
1940
|
+
description="""
|
|
1941
|
+
Unit of the given bins, using UnitRegistry() notation.
|
|
1942
|
+
""",
|
|
1943
|
+
)
|
|
1944
|
+
|
|
1945
|
+
value_magnitude = Quantity(
|
|
1946
|
+
type=np.float64,
|
|
1947
|
+
shape=['n_bins'],
|
|
1948
|
+
description="""
|
|
1949
|
+
Values of the property.
|
|
1950
|
+
""",
|
|
1951
|
+
)
|
|
1952
|
+
|
|
1953
|
+
value_unit = Quantity(
|
|
1954
|
+
type=str,
|
|
1955
|
+
shape=[],
|
|
1956
|
+
description="""
|
|
1957
|
+
Unit of the property, using UnitRegistry() notation.
|
|
1958
|
+
""",
|
|
1959
|
+
)
|
|
1960
|
+
|
|
1961
|
+
|
|
1962
|
+
class RadialDistributionFunctionValues(EnsemblePropertyValues):
|
|
1963
|
+
"""
|
|
1964
|
+
Section containing information regarding the values of
|
|
1965
|
+
radial distribution functions (rdfs).
|
|
1966
|
+
"""
|
|
1967
|
+
|
|
1968
|
+
m_def = Section(validate=False)
|
|
1969
|
+
|
|
1970
|
+
bins = Quantity(
|
|
1971
|
+
type=np.float64,
|
|
1972
|
+
shape=['n_bins'],
|
|
1973
|
+
unit='m',
|
|
1974
|
+
description="""
|
|
1975
|
+
Distances along which the rdf was calculated.
|
|
1976
|
+
""",
|
|
1977
|
+
)
|
|
1978
|
+
|
|
1979
|
+
value = Quantity(
|
|
1980
|
+
type=np.float64,
|
|
1981
|
+
shape=['n_bins'],
|
|
1982
|
+
description="""
|
|
1983
|
+
Values of the property.
|
|
1984
|
+
""",
|
|
1985
|
+
)
|
|
1986
|
+
|
|
1987
|
+
|
|
1988
|
+
class EnsembleProperty(Property):
|
|
1989
|
+
"""
|
|
1990
|
+
Generic section containing information about a calculation of any static observable
|
|
1991
|
+
from a trajectory (i.e., from an ensemble average).
|
|
1992
|
+
"""
|
|
1993
|
+
|
|
1994
|
+
m_def = Section(validate=False)
|
|
1995
|
+
|
|
1996
|
+
n_smooth = Quantity(
|
|
1997
|
+
type=int,
|
|
1998
|
+
shape=[],
|
|
1999
|
+
description="""
|
|
2000
|
+
Number of bins over which the running average was computed for
|
|
2001
|
+
the observable `values'.
|
|
2002
|
+
""",
|
|
2003
|
+
)
|
|
2004
|
+
|
|
2005
|
+
n_variables = Quantity(
|
|
2006
|
+
type=int,
|
|
2007
|
+
shape=[],
|
|
2008
|
+
description="""
|
|
2009
|
+
Number of variables along which the property is determined.
|
|
2010
|
+
""",
|
|
2011
|
+
)
|
|
2012
|
+
|
|
2013
|
+
variables_name = Quantity(
|
|
2014
|
+
type=str,
|
|
2015
|
+
shape=['n_variables'],
|
|
2016
|
+
description="""
|
|
2017
|
+
Name/description of the independent variables along which the observable is defined.
|
|
2018
|
+
""",
|
|
2019
|
+
)
|
|
2020
|
+
|
|
2021
|
+
ensemble_property_values = SubSection(
|
|
2022
|
+
sub_section=EnsemblePropertyValues.m_def, repeats=True
|
|
2023
|
+
)
|
|
2024
|
+
|
|
2025
|
+
|
|
2026
|
+
class RadialDistributionFunction(EnsembleProperty):
|
|
2027
|
+
"""
|
|
2028
|
+
Section containing information about the calculation of
|
|
2029
|
+
radial distribution functions (rdfs).
|
|
2030
|
+
"""
|
|
2031
|
+
|
|
2032
|
+
m_def = Section(validate=False)
|
|
2033
|
+
|
|
2034
|
+
_rdf_results = None
|
|
2035
|
+
|
|
2036
|
+
radial_distribution_function_values = SubSection(
|
|
2037
|
+
sub_section=RadialDistributionFunctionValues.m_def, repeats=True
|
|
2038
|
+
)
|
|
2039
|
+
|
|
2040
|
+
def normalize(self, archive, logger):
|
|
2041
|
+
super().normalize(archive, logger)
|
|
2042
|
+
|
|
2043
|
+
if self._rdf_results:
|
|
2044
|
+
self.type = self._rdf_results.get('type')
|
|
2045
|
+
self.n_smooth = self._rdf_results.get('n_smooth')
|
|
2046
|
+
self.n_prune = self._rdf_results.get('n_prune')
|
|
2047
|
+
self.n_variables = 1
|
|
2048
|
+
self.variables_name = ['distance']
|
|
2049
|
+
for i_pair, pair_type in enumerate(self._rdf_results.get('types', [])):
|
|
2050
|
+
sec_rdf_values = self.m_create(RadialDistributionFunctionValues)
|
|
2051
|
+
sec_rdf_values.label = str(pair_type)
|
|
2052
|
+
sec_rdf_values.n_bins = len(
|
|
2053
|
+
self._rdf_results.get('bins', [[]] * i_pair)[i_pair]
|
|
2054
|
+
)
|
|
2055
|
+
sec_rdf_values.bins = self._rdf_results.get('bins', [[]] * i_pair)[
|
|
2056
|
+
i_pair
|
|
2057
|
+
]
|
|
2058
|
+
sec_rdf_values.value = self._rdf_results.get('value', [[]] * i_pair)[
|
|
2059
|
+
i_pair
|
|
2060
|
+
]
|
|
2061
|
+
sec_rdf_values.frame_start = self._rdf_results.get(
|
|
2062
|
+
'frame_start', [[]] * i_pair
|
|
2063
|
+
)[i_pair]
|
|
2064
|
+
sec_rdf_values.frame_end = self._rdf_results.get(
|
|
2065
|
+
'frame_end', [[]] * i_pair
|
|
2066
|
+
)[i_pair]
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
class TrajectoryProperty(Property):
|
|
2070
|
+
"""
|
|
2071
|
+
Generic section containing information about a calculation of any observable
|
|
2072
|
+
defined and stored at each individual frame of a trajectory.
|
|
2073
|
+
"""
|
|
2074
|
+
|
|
2075
|
+
m_def = Section(validate=False)
|
|
2076
|
+
|
|
2077
|
+
n_frames = Quantity(
|
|
2078
|
+
type=int,
|
|
2079
|
+
shape=[],
|
|
2080
|
+
description="""
|
|
2081
|
+
Number of frames for which the observable is stored.
|
|
2082
|
+
""",
|
|
2083
|
+
)
|
|
2084
|
+
|
|
2085
|
+
frames = Quantity(
|
|
2086
|
+
type=np.int32,
|
|
2087
|
+
shape=['n_frames'],
|
|
2088
|
+
description="""
|
|
2089
|
+
Frames for which the observable is stored.
|
|
2090
|
+
""",
|
|
2091
|
+
)
|
|
2092
|
+
|
|
2093
|
+
times = Quantity(
|
|
2094
|
+
type=np.float64,
|
|
2095
|
+
shape=['n_frames'],
|
|
2096
|
+
unit='s',
|
|
2097
|
+
description="""
|
|
2098
|
+
Times for which the observable is stored.
|
|
2099
|
+
""",
|
|
2100
|
+
)
|
|
2101
|
+
|
|
2102
|
+
value_magnitude = Quantity(
|
|
2103
|
+
type=np.float64,
|
|
2104
|
+
shape=['n_frames'],
|
|
2105
|
+
description="""
|
|
2106
|
+
Values of the property.
|
|
2107
|
+
""",
|
|
2108
|
+
)
|
|
2109
|
+
|
|
2110
|
+
value_unit = Quantity(
|
|
2111
|
+
type=str,
|
|
2112
|
+
shape=[],
|
|
2113
|
+
description="""
|
|
2114
|
+
Unit of the property, using UnitRegistry() notation.
|
|
2115
|
+
""",
|
|
2116
|
+
)
|
|
2117
|
+
|
|
2118
|
+
errors = Quantity(
|
|
2119
|
+
type=np.float64,
|
|
2120
|
+
shape=['*'],
|
|
2121
|
+
description="""
|
|
2122
|
+
Error associated with the determination of the property.
|
|
2123
|
+
""",
|
|
2124
|
+
)
|
|
2125
|
+
|
|
2126
|
+
|
|
2127
|
+
# TODO Rg + TrajectoryPropery should be removed from workflow. All properties dependent on a single configuration should be store in calculation
|
|
2128
|
+
class RadiusOfGyration(TrajectoryProperty):
|
|
2129
|
+
"""
|
|
2130
|
+
Section containing information about the calculation of
|
|
2131
|
+
radius of gyration (Rg).
|
|
2132
|
+
"""
|
|
2133
|
+
|
|
2134
|
+
m_def = Section(validate=False)
|
|
2135
|
+
|
|
2136
|
+
_rg_results = None
|
|
2137
|
+
|
|
2138
|
+
atomsgroup_ref = Quantity(
|
|
2139
|
+
type=Reference(AtomsGroup.m_def),
|
|
2140
|
+
shape=[1],
|
|
2141
|
+
description="""
|
|
2142
|
+
References to the atoms_group section containing the molecule for which Rg was calculated.
|
|
2143
|
+
""",
|
|
2144
|
+
)
|
|
2145
|
+
|
|
2146
|
+
value = Quantity(
|
|
2147
|
+
type=np.float64,
|
|
2148
|
+
shape=['n_frames'],
|
|
2149
|
+
unit='m',
|
|
2150
|
+
description="""
|
|
2151
|
+
Values of the property.
|
|
2152
|
+
""",
|
|
2153
|
+
)
|
|
2154
|
+
|
|
2155
|
+
def normalize(self, archive, logger):
|
|
2156
|
+
super().normalize(archive, logger)
|
|
2157
|
+
|
|
2158
|
+
if self._rg_results:
|
|
2159
|
+
self.type = self._rg_results.get('type')
|
|
2160
|
+
self.label = self._rg_results.get('label')
|
|
2161
|
+
# TODO Fix this assignment fails with TypeError
|
|
2162
|
+
try:
|
|
2163
|
+
self.atomsgroup_ref = self._rg_results.get('atomsgroup_ref')
|
|
2164
|
+
except Exception:
|
|
2165
|
+
pass
|
|
2166
|
+
self.n_frames = self._rg_results.get('n_frames')
|
|
2167
|
+
self.times = self._rg_results.get('times')
|
|
2168
|
+
self.value = self._rg_results.get('value')
|
|
2169
|
+
|
|
2170
|
+
|
|
2171
|
+
class FreeEnergyCalculations(TrajectoryProperty):
|
|
2172
|
+
"""
|
|
2173
|
+
Section containing information regarding the instantaneous (i.e., for a single configuration)
|
|
2174
|
+
values of free energies calculated via thermodynamic perturbation.
|
|
2175
|
+
The values stored are actually infinitesimal changes in the free energy, determined as derivatives
|
|
2176
|
+
of the Hamiltonian with respect to the coupling parameter (lambda) defining each state for the perturbation.
|
|
2177
|
+
"""
|
|
2178
|
+
|
|
2179
|
+
m_def = Section(validate=False)
|
|
2180
|
+
|
|
2181
|
+
method_ref = Quantity(
|
|
2182
|
+
type=Reference(FreeEnergyCalculationParameters.m_def),
|
|
2183
|
+
shape=[],
|
|
2184
|
+
description="""
|
|
2185
|
+
Links the free energy results with the method parameters.
|
|
2186
|
+
""",
|
|
2187
|
+
)
|
|
2188
|
+
|
|
2189
|
+
lambda_index = Quantity(
|
|
2190
|
+
type=int,
|
|
2191
|
+
shape=[],
|
|
2192
|
+
description="""
|
|
2193
|
+
Index of the lambda state for the present simulation within the free energy calculation workflow.
|
|
2194
|
+
I.e., lambda = method_ref.lambdas.values[lambda_index]
|
|
2195
|
+
""",
|
|
2196
|
+
)
|
|
2197
|
+
|
|
2198
|
+
n_states = Quantity(
|
|
2199
|
+
type=int,
|
|
2200
|
+
shape=[],
|
|
2201
|
+
description="""
|
|
2202
|
+
Number of states defined for the interpolation of the system, as indicate in `method_ref`.
|
|
2203
|
+
""",
|
|
2204
|
+
)
|
|
2205
|
+
|
|
2206
|
+
value_unit = Quantity(
|
|
2207
|
+
type=str,
|
|
2208
|
+
shape=[],
|
|
2209
|
+
description="""
|
|
2210
|
+
Unit of the property, using UnitRegistry() notation.
|
|
2211
|
+
In this case, the unit corresponds to all `value` properties stored within this section.
|
|
2212
|
+
""",
|
|
2213
|
+
)
|
|
2214
|
+
|
|
2215
|
+
value_total_energy_magnitude = Quantity(
|
|
2216
|
+
type=HDF5Dataset,
|
|
2217
|
+
shape=[],
|
|
2218
|
+
description="""
|
|
2219
|
+
Value of the total energy for the present lambda state. The expected dimensions are ["n_frames"].
|
|
2220
|
+
This quantity is a reference to the data (file+path), which is stored in an HDF5 file for efficiency.
|
|
2221
|
+
""",
|
|
2222
|
+
)
|
|
2223
|
+
|
|
2224
|
+
value_PV_energy_magnitude = Quantity(
|
|
2225
|
+
type=HDF5Dataset,
|
|
2226
|
+
shape=[],
|
|
2227
|
+
description="""
|
|
2228
|
+
Value of the pressure-volume energy (i.e., P*V) for the present lambda state. The expected dimensions are ["n_frames"].
|
|
2229
|
+
This quantity is a reference to the data (file+path), which is stored in an HDF5 file for efficiency.
|
|
2230
|
+
""",
|
|
2231
|
+
)
|
|
2232
|
+
|
|
2233
|
+
value_total_energy_differences_magnitude = Quantity(
|
|
2234
|
+
type=HDF5Dataset,
|
|
2235
|
+
shape=[],
|
|
2236
|
+
description="""
|
|
2237
|
+
Values correspond to the difference in total energy between each specified lambda state
|
|
2238
|
+
and the reference state, which corresponds to the value of lambda of the current simulation.
|
|
2239
|
+
The expected dimensions are ["n_frames", "n_states"].
|
|
2240
|
+
This quantity is a reference to the data (file+path), which is stored in an HDF5 file for efficiency.
|
|
2241
|
+
""",
|
|
2242
|
+
)
|
|
2243
|
+
|
|
2244
|
+
value_total_energy_derivative_magnitude = Quantity(
|
|
2245
|
+
type=HDF5Dataset,
|
|
2246
|
+
shape=[],
|
|
2247
|
+
description="""
|
|
2248
|
+
Value of the derivative of the total energy with respect to lambda, evaluated for the current
|
|
2249
|
+
lambda state. The expected dimensions are ["n_frames"].
|
|
2250
|
+
This quantity is a reference to the data (file+path), which is stored in an HDF5 file for efficiency.
|
|
2251
|
+
""",
|
|
2252
|
+
)
|
|
2253
|
+
|
|
2254
|
+
|
|
2255
|
+
class DiffusionConstantValues(PropertyValues):
|
|
2256
|
+
"""
|
|
2257
|
+
Section containing information regarding the diffusion constants.
|
|
2258
|
+
"""
|
|
2259
|
+
|
|
2260
|
+
m_def = Section(validate=False)
|
|
2261
|
+
|
|
2262
|
+
value = Quantity(
|
|
2263
|
+
type=np.float64,
|
|
2264
|
+
shape=[],
|
|
2265
|
+
unit='m^2/s',
|
|
2266
|
+
description="""
|
|
2267
|
+
Values of the diffusion constants.
|
|
2268
|
+
""",
|
|
2269
|
+
)
|
|
2270
|
+
|
|
2271
|
+
error_type = Quantity(
|
|
2272
|
+
type=str,
|
|
2273
|
+
shape=[],
|
|
2274
|
+
description="""
|
|
2275
|
+
Describes the type of error reported for this observable.
|
|
2276
|
+
""",
|
|
2277
|
+
)
|
|
2278
|
+
|
|
2279
|
+
|
|
2280
|
+
class CorrelationFunctionValues(PropertyValues):
|
|
2281
|
+
"""
|
|
2282
|
+
Generic section containing information regarding the values of a correlation function.
|
|
2283
|
+
"""
|
|
2284
|
+
|
|
2285
|
+
m_def = Section(validate=False)
|
|
2286
|
+
|
|
2287
|
+
n_times = Quantity(
|
|
2288
|
+
type=int,
|
|
2289
|
+
shape=[],
|
|
2290
|
+
description="""
|
|
2291
|
+
Number of times windows for the calculation of the correlation function.
|
|
2292
|
+
""",
|
|
2293
|
+
)
|
|
2294
|
+
|
|
2295
|
+
times = Quantity(
|
|
2296
|
+
type=np.float64,
|
|
2297
|
+
shape=['n_times'],
|
|
2298
|
+
unit='s',
|
|
2299
|
+
description="""
|
|
2300
|
+
Time windows used for the calculation of the correlation function.
|
|
2301
|
+
""",
|
|
2302
|
+
)
|
|
2303
|
+
|
|
2304
|
+
value_magnitude = Quantity(
|
|
2305
|
+
type=np.float64,
|
|
2306
|
+
shape=['n_times'],
|
|
2307
|
+
description="""
|
|
2308
|
+
Values of the property.
|
|
2309
|
+
""",
|
|
2310
|
+
)
|
|
2311
|
+
|
|
2312
|
+
value_unit = Quantity(
|
|
2313
|
+
type=str,
|
|
2314
|
+
shape=[],
|
|
2315
|
+
description="""
|
|
2316
|
+
Unit of the property, using UnitRegistry() notation.
|
|
2317
|
+
""",
|
|
2318
|
+
)
|
|
2319
|
+
|
|
2320
|
+
|
|
2321
|
+
class MeanSquaredDisplacementValues(CorrelationFunctionValues):
|
|
2322
|
+
"""
|
|
2323
|
+
Section containing information regarding the values of a mean squared displacements (msds).
|
|
2324
|
+
"""
|
|
2325
|
+
|
|
2326
|
+
m_def = Section(validate=False)
|
|
2327
|
+
|
|
2328
|
+
times = Quantity(
|
|
2329
|
+
type=np.float64,
|
|
2330
|
+
shape=['n_times'],
|
|
2331
|
+
unit='s',
|
|
2332
|
+
description="""
|
|
2333
|
+
Time windows used for the calculation of the msds.
|
|
2334
|
+
""",
|
|
2335
|
+
)
|
|
2336
|
+
|
|
2337
|
+
value = Quantity(
|
|
2338
|
+
type=np.float64,
|
|
2339
|
+
shape=['n_times'],
|
|
2340
|
+
unit='m^2',
|
|
2341
|
+
description="""
|
|
2342
|
+
Mean squared displacement values.
|
|
2343
|
+
""",
|
|
2344
|
+
)
|
|
2345
|
+
|
|
2346
|
+
errors = Quantity(
|
|
2347
|
+
type=np.float64,
|
|
2348
|
+
shape=['*'],
|
|
2349
|
+
description="""
|
|
2350
|
+
Error associated with the determination of the msds.
|
|
2351
|
+
""",
|
|
2352
|
+
)
|
|
2353
|
+
|
|
2354
|
+
diffusion_constant = SubSection(
|
|
2355
|
+
sub_section=DiffusionConstantValues.m_def, repeats=False
|
|
2356
|
+
)
|
|
2357
|
+
|
|
2358
|
+
|
|
2359
|
+
class CorrelationFunction(Property):
|
|
2360
|
+
"""
|
|
2361
|
+
Generic section containing information about a calculation of any time correlation
|
|
2362
|
+
function from a trajectory.
|
|
2363
|
+
"""
|
|
2364
|
+
|
|
2365
|
+
m_def = Section(validate=False)
|
|
2366
|
+
|
|
2367
|
+
direction = Quantity(
|
|
2368
|
+
type=MEnum('x', 'y', 'z', 'xy', 'yz', 'xz', 'xyz'),
|
|
2369
|
+
shape=[],
|
|
2370
|
+
description="""
|
|
2371
|
+
Describes the direction in which the correlation function was calculated.
|
|
2372
|
+
""",
|
|
2373
|
+
)
|
|
2374
|
+
|
|
2375
|
+
correlation_function_values = SubSection(
|
|
2376
|
+
sub_section=CorrelationFunctionValues.m_def, repeats=True
|
|
2377
|
+
)
|
|
2378
|
+
|
|
2379
|
+
|
|
2380
|
+
class MeanSquaredDisplacement(CorrelationFunction):
|
|
2381
|
+
"""
|
|
2382
|
+
Section containing information about a calculation of any mean squared displacements (msds).
|
|
2383
|
+
"""
|
|
2384
|
+
|
|
2385
|
+
m_def = Section(validate=False)
|
|
2386
|
+
|
|
2387
|
+
_msd_results = None
|
|
2388
|
+
|
|
2389
|
+
mean_squared_displacement_values = SubSection(
|
|
2390
|
+
sub_section=MeanSquaredDisplacementValues.m_def, repeats=True
|
|
2391
|
+
)
|
|
2392
|
+
|
|
2393
|
+
def normalize(self, archive, logger):
|
|
2394
|
+
super().normalize(archive, logger)
|
|
2395
|
+
|
|
2396
|
+
if not self._msd_results:
|
|
2397
|
+
return
|
|
2398
|
+
|
|
2399
|
+
self.type = self._msd_results.get('type')
|
|
2400
|
+
self.direction = self._msd_results.get('direction')
|
|
2401
|
+
for i_type, moltype in enumerate(self._msd_results.get('types', [])):
|
|
2402
|
+
sec_msd_values = self.m_create(MeanSquaredDisplacementValues)
|
|
2403
|
+
sec_msd_values.label = str(moltype)
|
|
2404
|
+
sec_msd_values.n_times = len(
|
|
2405
|
+
self._msd_results.get('times', [[]] * i_type)[i_type]
|
|
2406
|
+
)
|
|
2407
|
+
sec_msd_values.times = (
|
|
2408
|
+
self._msd_results['times'][i_type]
|
|
2409
|
+
if self._msd_results.get('times') is not None
|
|
2410
|
+
else []
|
|
2411
|
+
)
|
|
2412
|
+
sec_msd_values.value = (
|
|
2413
|
+
self._msd_results['value'][i_type]
|
|
2414
|
+
if self._msd_results.get('value') is not None
|
|
2415
|
+
else []
|
|
2416
|
+
)
|
|
2417
|
+
sec_diffusion = sec_msd_values.m_create(DiffusionConstantValues)
|
|
2418
|
+
sec_diffusion.value = (
|
|
2419
|
+
self._msd_results['diffusion_constant'][i_type]
|
|
2420
|
+
if self._msd_results.get('diffusion_constant') is not None
|
|
2421
|
+
else []
|
|
2422
|
+
)
|
|
2423
|
+
sec_diffusion.error_type = 'Pearson correlation coefficient'
|
|
2424
|
+
if self._msd_results.get('error_diffusion_constant') is not None:
|
|
2425
|
+
errors = self._msd_results['error_diffusion_constant'][i_type]
|
|
2426
|
+
sec_diffusion.errors = (
|
|
2427
|
+
list(errors) if isinstance(errors, (list, np.ndarray)) else [errors]
|
|
2428
|
+
)
|
|
2429
|
+
|
|
2430
|
+
|
|
2431
|
+
class MolecularDynamicsResults(ThermodynamicsResults):
|
|
2432
|
+
finished_normally = Quantity(
|
|
2433
|
+
type=bool,
|
|
2434
|
+
shape=[],
|
|
2435
|
+
description="""
|
|
2436
|
+
Indicates if calculation terminated normally.
|
|
2437
|
+
""",
|
|
2438
|
+
)
|
|
2439
|
+
|
|
2440
|
+
n_steps = Quantity(
|
|
2441
|
+
type=np.int32,
|
|
2442
|
+
shape=[],
|
|
2443
|
+
description="""
|
|
2444
|
+
Number of trajectory steps""",
|
|
2445
|
+
)
|
|
2446
|
+
|
|
2447
|
+
trajectory = Quantity(
|
|
2448
|
+
type=Reference(System),
|
|
2449
|
+
shape=['n_steps'],
|
|
2450
|
+
description="""
|
|
2451
|
+
Reference to the system of each step in the trajectory.
|
|
2452
|
+
""",
|
|
2453
|
+
)
|
|
2454
|
+
|
|
2455
|
+
radial_distribution_functions = SubSection(
|
|
2456
|
+
sub_section=RadialDistributionFunction.m_def, repeats=True
|
|
2457
|
+
)
|
|
2458
|
+
|
|
2459
|
+
ensemble_properties = SubSection(sub_section=EnsembleProperty.m_def, repeats=True)
|
|
2460
|
+
|
|
2461
|
+
correlation_functions = SubSection(
|
|
2462
|
+
sub_section=CorrelationFunction.m_def, repeats=True
|
|
2463
|
+
)
|
|
2464
|
+
|
|
2465
|
+
radial_distribution_functions = SubSection(
|
|
2466
|
+
sub_section=RadialDistributionFunction.m_def, repeats=True
|
|
2467
|
+
)
|
|
2468
|
+
|
|
2469
|
+
radius_of_gyration = SubSection(sub_section=RadiusOfGyration, repeats=True)
|
|
2470
|
+
|
|
2471
|
+
mean_squared_displacements = SubSection(
|
|
2472
|
+
sub_section=MeanSquaredDisplacement.m_def, repeats=True
|
|
2473
|
+
)
|
|
2474
|
+
|
|
2475
|
+
free_energy_calculations = SubSection(
|
|
2476
|
+
sub_section=FreeEnergyCalculations.m_def, repeats=True
|
|
2477
|
+
)
|
|
2478
|
+
|
|
2479
|
+
def normalize(self, archive, logger):
|
|
2480
|
+
super().normalize(archive, logger)
|
|
2481
|
+
|
|
2482
|
+
universe = archive_to_universe(archive)
|
|
2483
|
+
if universe is None:
|
|
2484
|
+
return
|
|
2485
|
+
|
|
2486
|
+
# calculate molecular radial distribution functions
|
|
2487
|
+
if not self.radial_distribution_functions:
|
|
2488
|
+
n_traj_split = (
|
|
2489
|
+
10 # number of intervals to split trajectory into for averaging
|
|
2490
|
+
)
|
|
2491
|
+
interval_indices = [] # 2D array specifying the groups of the n_traj_split intervals to be averaged
|
|
2492
|
+
# first 20% of trajectory
|
|
2493
|
+
interval_indices.append(np.arange(int(n_traj_split * 0.20)))
|
|
2494
|
+
# last 80% of trajectory
|
|
2495
|
+
interval_indices.append(np.arange(n_traj_split)[len(interval_indices[0]) :])
|
|
2496
|
+
# last 60% of trajectory
|
|
2497
|
+
interval_indices.append(
|
|
2498
|
+
np.arange(n_traj_split)[len(interval_indices[0]) * 2 :]
|
|
2499
|
+
)
|
|
2500
|
+
# last 40% of trajectory
|
|
2501
|
+
interval_indices.append(
|
|
2502
|
+
np.arange(n_traj_split)[len(interval_indices[0]) * 3 :]
|
|
2503
|
+
)
|
|
2504
|
+
|
|
2505
|
+
n_prune = int(universe.trajectory.n_frames / len(archive.run[-1].system))
|
|
2506
|
+
rdf_results = calc_molecular_rdf(
|
|
2507
|
+
universe,
|
|
2508
|
+
n_traj_split=n_traj_split,
|
|
2509
|
+
n_prune=n_prune,
|
|
2510
|
+
interval_indices=interval_indices,
|
|
2511
|
+
)
|
|
2512
|
+
if rdf_results:
|
|
2513
|
+
sec_rdfs = RadialDistributionFunction()
|
|
2514
|
+
sec_rdfs._rdf_results = rdf_results
|
|
2515
|
+
sec_rdfs.normalize(archive, logger)
|
|
2516
|
+
self.radial_distribution_functions.append(sec_rdfs)
|
|
2517
|
+
|
|
2518
|
+
# calculate the molecular mean squared displacements
|
|
2519
|
+
if not self.mean_squared_displacements:
|
|
2520
|
+
msd_results = calc_molecular_mean_squared_displacements(universe)
|
|
2521
|
+
if msd_results:
|
|
2522
|
+
sec_msds = MeanSquaredDisplacement()
|
|
2523
|
+
sec_msds._msd_results = msd_results
|
|
2524
|
+
sec_msds.normalize(archive, logger)
|
|
2525
|
+
self.mean_squared_displacements.append(sec_msds)
|
|
2526
|
+
|
|
2527
|
+
# calculate radius of gyration for polymers
|
|
2528
|
+
try:
|
|
2529
|
+
sec_systems = archive.run[-1].system
|
|
2530
|
+
sec_system = sec_systems[0]
|
|
2531
|
+
sec_calc = archive.run[-1].calculation
|
|
2532
|
+
sec_calc = sec_calc if sec_calc is not None else []
|
|
2533
|
+
except Exception:
|
|
2534
|
+
return
|
|
2535
|
+
|
|
2536
|
+
flag_rgs = False
|
|
2537
|
+
for calc in sec_calc:
|
|
2538
|
+
if calc.get('radius_of_gyration'):
|
|
2539
|
+
flag_rgs = True
|
|
2540
|
+
break # TODO Should transfer Rg's to workflow results if they are already supplied in calculation
|
|
2541
|
+
|
|
2542
|
+
if not flag_rgs:
|
|
2543
|
+
sec_rgs_calc = None
|
|
2544
|
+
system_topology = sec_system.atoms_group
|
|
2545
|
+
rg_results = calc_molecular_radius_of_gyration(universe, system_topology)
|
|
2546
|
+
for rg in rg_results:
|
|
2547
|
+
n_frames = rg.get('n_frames')
|
|
2548
|
+
if len(sec_systems) != n_frames:
|
|
2549
|
+
self.logger.warning(
|
|
2550
|
+
'Mismatch in length of system references in calculation and calculated Rg values.'
|
|
2551
|
+
'Will not store Rg values under calculation section'
|
|
2552
|
+
)
|
|
2553
|
+
continue
|
|
2554
|
+
|
|
2555
|
+
sec_rgs = RadiusOfGyration()
|
|
2556
|
+
sec_rgs._rg_results = rg
|
|
2557
|
+
sec_rgs.normalize(archive, logger)
|
|
2558
|
+
self.radius_of_gyration.append(sec_rgs)
|
|
2559
|
+
|
|
2560
|
+
for calc in sec_calc:
|
|
2561
|
+
if not calc.system_ref:
|
|
2562
|
+
continue
|
|
2563
|
+
sys_ind = calc.system_ref.m_parent_index
|
|
2564
|
+
sec_rgs_calc = calc.radius_of_gyration
|
|
2565
|
+
if not sec_rgs_calc:
|
|
2566
|
+
sec_rgs_calc = calc.m_create(RadiusOfGyrationCalculation)
|
|
2567
|
+
sec_rgs_calc.kind = rg.get('type')
|
|
2568
|
+
else:
|
|
2569
|
+
sec_rgs_calc = sec_rgs_calc[0]
|
|
2570
|
+
sec_rg_values = sec_rgs_calc.m_create(
|
|
2571
|
+
RadiusOfGyrationValuesCalculation
|
|
2572
|
+
)
|
|
2573
|
+
# TODO Fix this assignment fails with TypeError
|
|
2574
|
+
try:
|
|
2575
|
+
sec_rg_values.atomsgroup_ref = rg.get('atomsgroup_ref')
|
|
2576
|
+
except Exception:
|
|
2577
|
+
pass
|
|
2578
|
+
sec_rg_values.label = rg.get('label')
|
|
2579
|
+
sec_rg_values.value = rg.get('value')[sys_ind]
|
|
2580
|
+
|
|
2581
|
+
|
|
2582
|
+
class MolecularDynamics(SerialSimulation):
|
|
2583
|
+
method = SubSection(sub_section=MolecularDynamicsMethod)
|
|
2584
|
+
|
|
2585
|
+
results = SubSection(sub_section=MolecularDynamicsResults)
|
|
2586
|
+
|
|
2587
|
+
def normalize(self, archive, logger):
|
|
2588
|
+
super().normalize(archive, logger)
|
|
2589
|
+
|
|
2590
|
+
if not self.method:
|
|
2591
|
+
self.method = MolecularDynamicsMethod()
|
|
2592
|
+
self.inputs.append(Link(name=WORKFLOW_METHOD_NAME, section=self.method))
|
|
2593
|
+
|
|
2594
|
+
if not self.results:
|
|
2595
|
+
self.results = MolecularDynamicsResults()
|
|
2596
|
+
self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results))
|
|
2597
|
+
|
|
2598
|
+
if self.results.trajectory is None and self._systems:
|
|
2599
|
+
self.results.trajectory = self._systems
|
|
2600
|
+
|
|
2601
|
+
SimulationWorkflowResults.normalize(self.results, archive, logger)
|