nomad-parser-plugins-atomistic 1.0__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.
- atomisticparsers/__init__.py +400 -0
- atomisticparsers/amber/__init__.py +19 -0
- atomisticparsers/amber/__main__.py +31 -0
- atomisticparsers/amber/metainfo/__init__.py +19 -0
- atomisticparsers/amber/metainfo/amber.py +495 -0
- atomisticparsers/amber/parser.py +42 -0
- atomisticparsers/asap/__init__.py +19 -0
- atomisticparsers/asap/__main__.py +31 -0
- atomisticparsers/asap/metainfo/__init__.py +19 -0
- atomisticparsers/asap/metainfo/asap.py +75 -0
- atomisticparsers/asap/parser.py +197 -0
- atomisticparsers/bopfox/__init__.py +19 -0
- atomisticparsers/bopfox/__main__.py +31 -0
- atomisticparsers/bopfox/metainfo/__init__.py +19 -0
- atomisticparsers/bopfox/metainfo/bopfox.py +225 -0
- atomisticparsers/bopfox/parser.py +808 -0
- atomisticparsers/dftbplus/__init__.py +19 -0
- atomisticparsers/dftbplus/__main__.py +31 -0
- atomisticparsers/dftbplus/metainfo/__init__.py +19 -0
- atomisticparsers/dftbplus/metainfo/dftbplus.py +217 -0
- atomisticparsers/dftbplus/parser.py +500 -0
- atomisticparsers/dlpoly/__init__.py +19 -0
- atomisticparsers/dlpoly/__main__.py +31 -0
- atomisticparsers/dlpoly/metainfo/__init__.py +19 -0
- atomisticparsers/dlpoly/metainfo/dl_poly.py +312 -0
- atomisticparsers/dlpoly/parser.py +798 -0
- atomisticparsers/gromacs/__init__.py +19 -0
- atomisticparsers/gromacs/__main__.py +31 -0
- atomisticparsers/gromacs/metainfo/__init__.py +19 -0
- atomisticparsers/gromacs/metainfo/gromacs.py +2388 -0
- atomisticparsers/gromacs/parser.py +1581 -0
- atomisticparsers/gromos/__init__.py +19 -0
- atomisticparsers/gromos/__main__.py +31 -0
- atomisticparsers/gromos/metainfo/__init__.py +19 -0
- atomisticparsers/gromos/metainfo/gromos.py +1995 -0
- atomisticparsers/gromos/parser.py +58 -0
- atomisticparsers/gulp/__init__.py +19 -0
- atomisticparsers/gulp/__main__.py +31 -0
- atomisticparsers/gulp/metainfo/__init__.py +19 -0
- atomisticparsers/gulp/metainfo/gulp.py +1117 -0
- atomisticparsers/gulp/parser.py +1316 -0
- atomisticparsers/h5md/__init__.py +19 -0
- atomisticparsers/h5md/__main__.py +31 -0
- atomisticparsers/h5md/metainfo/__init__.py +19 -0
- atomisticparsers/h5md/metainfo/h5md.py +239 -0
- atomisticparsers/h5md/parser.py +901 -0
- atomisticparsers/lammps/__init__.py +19 -0
- atomisticparsers/lammps/__main__.py +31 -0
- atomisticparsers/lammps/metainfo/__init__.py +19 -0
- atomisticparsers/lammps/metainfo/lammps.py +1417 -0
- atomisticparsers/lammps/parser.py +1753 -0
- atomisticparsers/libatoms/__init__.py +19 -0
- atomisticparsers/libatoms/__main__.py +31 -0
- atomisticparsers/libatoms/metainfo/__init__.py +19 -0
- atomisticparsers/libatoms/metainfo/lib_atoms.py +251 -0
- atomisticparsers/libatoms/parser.py +38 -0
- atomisticparsers/namd/__init__.py +19 -0
- atomisticparsers/namd/__main__.py +31 -0
- atomisticparsers/namd/metainfo/__init__.py +19 -0
- atomisticparsers/namd/metainfo/namd.py +1605 -0
- atomisticparsers/namd/parser.py +312 -0
- atomisticparsers/tinker/__init__.py +19 -0
- atomisticparsers/tinker/__main__.py +31 -0
- atomisticparsers/tinker/metainfo/__init__.py +18 -0
- atomisticparsers/tinker/metainfo/tinker.py +1363 -0
- atomisticparsers/tinker/parser.py +685 -0
- atomisticparsers/utils/__init__.py +22 -0
- atomisticparsers/utils/mdanalysis.py +662 -0
- atomisticparsers/utils/parsers.py +226 -0
- atomisticparsers/xtb/__init__.py +19 -0
- atomisticparsers/xtb/__main__.py +32 -0
- atomisticparsers/xtb/metainfo/__init__.py +19 -0
- atomisticparsers/xtb/metainfo/xtb.py +256 -0
- atomisticparsers/xtb/parser.py +979 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/LICENSE +202 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/METADATA +327 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/RECORD +80 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/WHEEL +5 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/entry_points.txt +15 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD.
|
|
5
|
+
# See https://nomad-lab.eu for further info.
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
from .mdanalysis import MDAnalysisParser
|
|
20
|
+
from .parsers import MDParser
|
|
21
|
+
|
|
22
|
+
MOL = 6.022140857e23
|
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD.
|
|
5
|
+
# See https://nomad-lab.eu for further info.
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import MDAnalysis
|
|
25
|
+
import MDAnalysis.analysis.rdf as MDA_RDF
|
|
26
|
+
from MDAnalysis.topology.guessers import guess_atom_element
|
|
27
|
+
except Exception:
|
|
28
|
+
MDAnalysis = None
|
|
29
|
+
from typing import Any, Dict
|
|
30
|
+
from nptyping import NDArray
|
|
31
|
+
from collections import namedtuple
|
|
32
|
+
from array import array
|
|
33
|
+
from scipy import sparse
|
|
34
|
+
from scipy.stats import linregress
|
|
35
|
+
|
|
36
|
+
from nomad.units import ureg
|
|
37
|
+
from nomad.parsing.file_parser import FileParser
|
|
38
|
+
from simulationworkflowschema.molecular_dynamics import BeadGroup, shifted_correlation_average
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
MOL = 6.022140857e23
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MDAnalysisParser(FileParser):
|
|
45
|
+
def __init__(self, *args, **kwargs):
|
|
46
|
+
super().__init__()
|
|
47
|
+
self._args = args
|
|
48
|
+
self._kwargs = kwargs
|
|
49
|
+
self._atomsgroup_info = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def auxilliary_files(self):
|
|
53
|
+
return self._args
|
|
54
|
+
|
|
55
|
+
@auxilliary_files.setter
|
|
56
|
+
def auxilliary_files(self, value):
|
|
57
|
+
self._file_handler = None
|
|
58
|
+
self._args = [value] if isinstance(value, str) else value
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def options(self):
|
|
62
|
+
return self._kwargs
|
|
63
|
+
|
|
64
|
+
@options.setter
|
|
65
|
+
def options(self, value):
|
|
66
|
+
self._file_handler = None
|
|
67
|
+
self._kwargs = value
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def universe(self):
|
|
71
|
+
if self._file_handler is None:
|
|
72
|
+
try:
|
|
73
|
+
self._file_handler = MDAnalysis.Universe(
|
|
74
|
+
self.mainfile, *self.auxilliary_files, **self.options
|
|
75
|
+
)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self.logger.error('Error creating MDAnalysis universe.', exc_info=e)
|
|
78
|
+
return self._file_handler
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def bead_groups(self):
|
|
82
|
+
atoms_moltypes = self.get('atoms_info', {}).get('moltypes', [])
|
|
83
|
+
moltypes = np.unique(atoms_moltypes)
|
|
84
|
+
bead_groups = {}
|
|
85
|
+
compound = 'fragments'
|
|
86
|
+
for moltype in moltypes:
|
|
87
|
+
if hasattr(self.universe.atoms, 'moltypes'):
|
|
88
|
+
ags_by_moltype = self.universe.select_atoms('moltype ' + moltype)
|
|
89
|
+
else: # this is easier than adding something to the universe
|
|
90
|
+
selection = ' '.join(
|
|
91
|
+
[str(i) for i in np.where(atoms_moltypes == moltype)[0]]
|
|
92
|
+
)
|
|
93
|
+
selection = f'index {selection}'
|
|
94
|
+
ags_by_moltype = self.universe.select_atoms(selection)
|
|
95
|
+
ags_by_moltype = ags_by_moltype[
|
|
96
|
+
ags_by_moltype.masses > abs(1e-2)
|
|
97
|
+
] # remove any virtual/massless sites (needed for, e.g., 4-bead water models)
|
|
98
|
+
bead_groups[moltype] = BeadGroup(ags_by_moltype, compound=compound)
|
|
99
|
+
|
|
100
|
+
return bead_groups
|
|
101
|
+
|
|
102
|
+
def parse(self, quantity_key: str = None, **kwargs):
|
|
103
|
+
if self._results is None:
|
|
104
|
+
self._results: Dict[str, Any] = dict()
|
|
105
|
+
|
|
106
|
+
if not self.universe:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
atoms = list(self.universe.atoms)
|
|
110
|
+
|
|
111
|
+
name_map = {'mass': 'masses'}
|
|
112
|
+
unit_map = {'mass': ureg.amu, 'charge': ureg.elementary_charge}
|
|
113
|
+
self._results['atoms_info'] = dict()
|
|
114
|
+
for key in [
|
|
115
|
+
'name',
|
|
116
|
+
'charge',
|
|
117
|
+
'mass',
|
|
118
|
+
'resid',
|
|
119
|
+
'resname',
|
|
120
|
+
'molnum',
|
|
121
|
+
'moltype',
|
|
122
|
+
'type',
|
|
123
|
+
'segid',
|
|
124
|
+
'element',
|
|
125
|
+
]:
|
|
126
|
+
try:
|
|
127
|
+
value = [getattr(atom, key) for atom in atoms]
|
|
128
|
+
except Exception:
|
|
129
|
+
continue
|
|
130
|
+
value = value * unit_map.get(key, 1) if value is not None else value
|
|
131
|
+
self._results['atoms_info'][name_map.get(key, f'{key}s')] = value
|
|
132
|
+
|
|
133
|
+
# if atom name is not identified, set it to 'X'
|
|
134
|
+
if self._results['atoms_info'].get('names') is None:
|
|
135
|
+
self._results['atoms_info']['names'] = ['X'] * self.universe.atoms.n_atoms
|
|
136
|
+
self._results['n_atoms'] = self.universe.atoms.n_atoms
|
|
137
|
+
self._results['n_frames'] = len(self.universe.trajectory)
|
|
138
|
+
|
|
139
|
+
# make substitutions based on available atom info
|
|
140
|
+
if self._results['atoms_info'].get('moltypes') is None:
|
|
141
|
+
if hasattr(self.universe.atoms, 'fragments'):
|
|
142
|
+
self._results['atoms_info']['moltypes'] = self.get_fragtypes()
|
|
143
|
+
|
|
144
|
+
if self._results['atoms_info'].get('molnums') is None:
|
|
145
|
+
try:
|
|
146
|
+
value = getattr(self.universe.atoms, 'fragindices')
|
|
147
|
+
self._results['atoms_info']['molnums'] = value
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
if self._results['atoms_info'].get('resnames') is None:
|
|
152
|
+
try:
|
|
153
|
+
self._results['atoms_info']['resnames'] = self._results['atoms_info'][
|
|
154
|
+
'resids'
|
|
155
|
+
]
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
if self._results['atoms_info'].get('names') is None:
|
|
160
|
+
try:
|
|
161
|
+
self._results['atoms_info']['names'] = self._results['atoms_info'][
|
|
162
|
+
'types'
|
|
163
|
+
]
|
|
164
|
+
except Exception:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
if self._results['atoms_info'].get('elements') is None:
|
|
168
|
+
try:
|
|
169
|
+
self._results['atoms_info']['elements'] = self._results['atoms_info'][
|
|
170
|
+
'names'
|
|
171
|
+
]
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
def get_fragtypes(self):
|
|
176
|
+
# TODO put description otherwise, make private or put under parse method
|
|
177
|
+
""" """
|
|
178
|
+
atoms_fragtypes = np.empty(self.universe.atoms.types.shape, dtype=str)
|
|
179
|
+
ctr_fragtype = 0
|
|
180
|
+
atoms_fragtypes[self.universe.atoms.fragments[0].ix] = ctr_fragtype
|
|
181
|
+
frag_unique_atomtypes = [
|
|
182
|
+
self.universe.atoms.types[self.universe.atoms.fragments[0].ix]
|
|
183
|
+
]
|
|
184
|
+
ctr_fragtype += 1
|
|
185
|
+
for i_frag in range(1, self.universe.atoms.n_fragments):
|
|
186
|
+
types_i_frag = self.universe.atoms.types[
|
|
187
|
+
self.universe.atoms.fragments[i_frag].ix
|
|
188
|
+
]
|
|
189
|
+
flag_fragtype_exists = False
|
|
190
|
+
for j_frag in range(len(frag_unique_atomtypes) - 1, -1, -1):
|
|
191
|
+
types_j_frag = frag_unique_atomtypes[j_frag]
|
|
192
|
+
if len(types_i_frag) != len(types_j_frag):
|
|
193
|
+
continue
|
|
194
|
+
elif np.all(types_i_frag == types_j_frag):
|
|
195
|
+
atoms_fragtypes[self.universe.atoms.fragments[i_frag].ix] = j_frag
|
|
196
|
+
flag_fragtype_exists = True
|
|
197
|
+
if not flag_fragtype_exists:
|
|
198
|
+
atoms_fragtypes[self.universe.atoms.fragments[i_frag].ix] = ctr_fragtype
|
|
199
|
+
frag_unique_atomtypes.append(
|
|
200
|
+
self.universe.atoms.types[self.universe.atoms.fragments[i_frag].ix]
|
|
201
|
+
)
|
|
202
|
+
ctr_fragtype += 1
|
|
203
|
+
return atoms_fragtypes
|
|
204
|
+
|
|
205
|
+
def calc_molecular_rdf(
|
|
206
|
+
self,
|
|
207
|
+
n_traj_split=10,
|
|
208
|
+
n_prune=1,
|
|
209
|
+
interval_indices=None,
|
|
210
|
+
n_bins=200,
|
|
211
|
+
n_smooth=2,
|
|
212
|
+
max_mols=5000,
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Calculates the radial distribution functions between for each unique pair of
|
|
216
|
+
molecule types as a function of their center of mass distance.
|
|
217
|
+
|
|
218
|
+
interval_indices: 2D array specifying the groups of the n_traj_split intervals to be averaged
|
|
219
|
+
max_mols: the maximum number of molecules per bead group for calculating the rdf, for efficiency purposes.
|
|
220
|
+
5k was set after > 50k was giving problems. Should do further testing to see where the appropriate limit should be set.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def get_rdf_avg(rdf_results_tmp, rdf_results, interval_indices, n_frames_split):
|
|
224
|
+
split_weights = n_frames_split[np.array(interval_indices)] / np.sum(
|
|
225
|
+
n_frames_split[np.array(interval_indices)]
|
|
226
|
+
)
|
|
227
|
+
assert abs(np.sum(split_weights) - 1.0) < 1e-6
|
|
228
|
+
rdf_values_avg = (
|
|
229
|
+
split_weights[0] * rdf_results_tmp['value'][interval_indices[0]]
|
|
230
|
+
)
|
|
231
|
+
for i_interval, interval in enumerate(interval_indices[1:]):
|
|
232
|
+
assert (
|
|
233
|
+
rdf_results_tmp['types'][interval]
|
|
234
|
+
== rdf_results_tmp['types'][interval - 1]
|
|
235
|
+
)
|
|
236
|
+
assert (
|
|
237
|
+
rdf_results_tmp['variables_name'][interval]
|
|
238
|
+
== rdf_results_tmp['variables_name'][interval - 1]
|
|
239
|
+
)
|
|
240
|
+
assert (
|
|
241
|
+
rdf_results_tmp['bins'][interval]
|
|
242
|
+
== rdf_results_tmp['bins'][interval - 1]
|
|
243
|
+
).all()
|
|
244
|
+
rdf_values_avg += (
|
|
245
|
+
split_weights[i_interval + 1] * rdf_results_tmp['value'][interval]
|
|
246
|
+
)
|
|
247
|
+
rdf_results['types'].append(rdf_results_tmp['types'][interval_indices[0]])
|
|
248
|
+
rdf_results['variables_name'].append(
|
|
249
|
+
rdf_results_tmp['variables_name'][interval_indices[0]]
|
|
250
|
+
)
|
|
251
|
+
rdf_results['bins'].append(rdf_results_tmp['bins'][interval_indices[0]])
|
|
252
|
+
rdf_results['value'].append(rdf_values_avg)
|
|
253
|
+
rdf_results['frame_start'].append(
|
|
254
|
+
int(rdf_results_tmp['frame_start'][interval_indices[0]])
|
|
255
|
+
)
|
|
256
|
+
rdf_results['frame_end'].append(
|
|
257
|
+
int(rdf_results_tmp['frame_end'][interval_indices[-1]])
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if self.universe is None:
|
|
261
|
+
return
|
|
262
|
+
trajectory = self.universe.trajectory[0] if self.universe.trajectory else None
|
|
263
|
+
dimensions = getattr(trajectory, 'dimensions', None) if trajectory else None
|
|
264
|
+
if dimensions is None:
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
n_frames = self.universe.trajectory.n_frames
|
|
268
|
+
if n_frames < n_traj_split:
|
|
269
|
+
n_traj_split = 1
|
|
270
|
+
frames_start = np.array([0])
|
|
271
|
+
frames_end = np.array([n_frames])
|
|
272
|
+
n_frames_split = np.array([n_frames])
|
|
273
|
+
interval_indices = [[0]]
|
|
274
|
+
else:
|
|
275
|
+
run_len = int(n_frames / n_traj_split)
|
|
276
|
+
frames_start = np.arange(n_traj_split) * run_len
|
|
277
|
+
frames_end = frames_start + run_len
|
|
278
|
+
frames_end[-1] = n_frames
|
|
279
|
+
n_frames_split = frames_end - frames_start
|
|
280
|
+
assert np.sum(n_frames_split) == n_frames
|
|
281
|
+
if not interval_indices:
|
|
282
|
+
interval_indices = [[i] for i in range(n_traj_split)]
|
|
283
|
+
|
|
284
|
+
bead_groups = self.bead_groups
|
|
285
|
+
if bead_groups is {}:
|
|
286
|
+
return bead_groups
|
|
287
|
+
moltypes = [moltype for moltype in bead_groups.keys()]
|
|
288
|
+
del_list = []
|
|
289
|
+
for i_moltype, moltype in enumerate(moltypes):
|
|
290
|
+
if bead_groups[moltype]._nbeads > max_mols:
|
|
291
|
+
del_list.append(i_moltype)
|
|
292
|
+
self.logger.warning(
|
|
293
|
+
'The number of molecules of exceeds the maximum of for calculating the rdf. Skipping this molecule type.'
|
|
294
|
+
)
|
|
295
|
+
moltypes = np.delete(moltypes, del_list)
|
|
296
|
+
|
|
297
|
+
min_box_dimension = np.min(self.universe.trajectory[0].dimensions[:3])
|
|
298
|
+
max_rdf_dist = min_box_dimension / 2
|
|
299
|
+
|
|
300
|
+
rdf_results = {}
|
|
301
|
+
rdf_results['n_smooth'] = n_smooth
|
|
302
|
+
rdf_results['types'] = []
|
|
303
|
+
rdf_results['variables_name'] = []
|
|
304
|
+
rdf_results['bins'] = []
|
|
305
|
+
rdf_results['value'] = []
|
|
306
|
+
rdf_results['frame_start'] = []
|
|
307
|
+
rdf_results['frame_end'] = []
|
|
308
|
+
for i, moltype_i in enumerate(moltypes):
|
|
309
|
+
for j, moltype_j in enumerate(moltypes):
|
|
310
|
+
if j > i:
|
|
311
|
+
continue
|
|
312
|
+
elif (
|
|
313
|
+
i == j and bead_groups[moltype_i].positions.shape[0] == 1
|
|
314
|
+
): # skip if only 1 mol in group
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
if i == j:
|
|
318
|
+
exclusion_block = (1, 1) # remove self-distance
|
|
319
|
+
else:
|
|
320
|
+
exclusion_block = None
|
|
321
|
+
pair_type = moltype_i + '-' + moltype_j
|
|
322
|
+
rdf_results_tmp = {}
|
|
323
|
+
rdf_results_tmp['types'] = []
|
|
324
|
+
rdf_results_tmp['variables_name'] = []
|
|
325
|
+
rdf_results_tmp['bins'] = []
|
|
326
|
+
rdf_results_tmp['value'] = []
|
|
327
|
+
rdf_results_tmp['frame_start'] = []
|
|
328
|
+
rdf_results_tmp['frame_end'] = []
|
|
329
|
+
for i_interval in range(n_traj_split):
|
|
330
|
+
rdf_results_tmp['types'].append(pair_type)
|
|
331
|
+
rdf_results_tmp['variables_name'].append(['distance'])
|
|
332
|
+
rdf = MDA_RDF.InterRDF(
|
|
333
|
+
bead_groups[moltype_i],
|
|
334
|
+
bead_groups[moltype_j],
|
|
335
|
+
range=(0, max_rdf_dist),
|
|
336
|
+
exclusion_block=exclusion_block,
|
|
337
|
+
nbins=n_bins,
|
|
338
|
+
).run(frames_start[i_interval], frames_end[i_interval], n_prune)
|
|
339
|
+
rdf_results_tmp['frame_start'].append(frames_start[i_interval])
|
|
340
|
+
rdf_results_tmp['frame_end'].append(frames_end[i_interval])
|
|
341
|
+
|
|
342
|
+
rdf_results_tmp['bins'].append(
|
|
343
|
+
rdf.results.bins[int(n_smooth / 2) : -int(n_smooth / 2)]
|
|
344
|
+
* ureg.angstrom
|
|
345
|
+
)
|
|
346
|
+
rdf_results_tmp['value'].append(
|
|
347
|
+
np.convolve(
|
|
348
|
+
rdf.results.rdf,
|
|
349
|
+
np.ones((n_smooth,)) / n_smooth,
|
|
350
|
+
mode='same',
|
|
351
|
+
)[int(n_smooth / 2) : -int(n_smooth / 2)]
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
for interval_group in interval_indices:
|
|
355
|
+
get_rdf_avg(
|
|
356
|
+
rdf_results_tmp, rdf_results, interval_group, n_frames_split
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
return rdf_results
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def with_trajectory(self):
|
|
363
|
+
"""
|
|
364
|
+
True if trajectory is present.
|
|
365
|
+
"""
|
|
366
|
+
return (
|
|
367
|
+
self.universe.trajectory is not None and len(self.universe.trajectory) > 0
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def get_frame(self, frame_index):
|
|
371
|
+
"""
|
|
372
|
+
Returns the frame in the trajectory with index frame_index.
|
|
373
|
+
"""
|
|
374
|
+
try:
|
|
375
|
+
return self.universe.trajectory[frame_index]
|
|
376
|
+
except Exception as e:
|
|
377
|
+
self.logger.warning('Error accessing frame.', exc_info=e)
|
|
378
|
+
|
|
379
|
+
def get_n_atoms(self, frame_index):
|
|
380
|
+
"""
|
|
381
|
+
Returns the number of atoms of the frame with index frame_index.
|
|
382
|
+
"""
|
|
383
|
+
frame = self.get_frame(frame_index)
|
|
384
|
+
return len(frame) if frame is not None else None
|
|
385
|
+
|
|
386
|
+
def get_atom_labels(self, frame_index):
|
|
387
|
+
"""
|
|
388
|
+
Returns the number of atoms of the frame with index frame_index.
|
|
389
|
+
"""
|
|
390
|
+
# MDAnalysis assumes no change in atom configuration
|
|
391
|
+
return [
|
|
392
|
+
guess_atom_element(name).title()
|
|
393
|
+
for name in self.get('atoms_info', {}).get('names', [])
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
def get_time(self, frame_index):
|
|
397
|
+
"""
|
|
398
|
+
Returns the elapsed simulated physical time since the start of the simulation for index frame_index.
|
|
399
|
+
"""
|
|
400
|
+
frame = self.get_frame(frame_index)
|
|
401
|
+
return frame.time * ureg.picosecond if frame is not None else None
|
|
402
|
+
|
|
403
|
+
def get_step(self, frame_index):
|
|
404
|
+
"""
|
|
405
|
+
Returns the step of the frame with index frame_index.
|
|
406
|
+
"""
|
|
407
|
+
frame = self.get_frame(frame_index)
|
|
408
|
+
dt = frame.dt if frame.dt else getattr(self.universe.trajectory, 'dt')
|
|
409
|
+
if not dt:
|
|
410
|
+
return
|
|
411
|
+
if frame:
|
|
412
|
+
return round(frame.time / dt)
|
|
413
|
+
|
|
414
|
+
def get_lattice_vectors(self, frame_index):
|
|
415
|
+
"""
|
|
416
|
+
Returns the lattice vectors of the frame with index frame_index.
|
|
417
|
+
"""
|
|
418
|
+
lattice_vectors = self.get_frame(frame_index).triclinic_dimensions
|
|
419
|
+
return lattice_vectors * ureg.angstrom if lattice_vectors is not None else None
|
|
420
|
+
|
|
421
|
+
def get_pbc(self, frame_index):
|
|
422
|
+
"""
|
|
423
|
+
Returns the lattice periodicity of the frame with index frame_index.
|
|
424
|
+
"""
|
|
425
|
+
lattice_vectors = self.get_lattice_vectors(frame_index)
|
|
426
|
+
return [True] * 3 if lattice_vectors is not None else [False] * 3
|
|
427
|
+
|
|
428
|
+
def get_positions(self, frame_index):
|
|
429
|
+
"""
|
|
430
|
+
Returns the positions of the atoms of the frame with index frame_index.
|
|
431
|
+
"""
|
|
432
|
+
frame = self.get_frame(frame_index)
|
|
433
|
+
return frame.positions * ureg.angstrom if frame.has_positions else None
|
|
434
|
+
|
|
435
|
+
def get_velocities(self, frame_index):
|
|
436
|
+
"""
|
|
437
|
+
Returns the velocities of the atoms of the frame with index frame_index.
|
|
438
|
+
"""
|
|
439
|
+
frame = self.get_frame(frame_index)
|
|
440
|
+
return (
|
|
441
|
+
frame.velocities * ureg.angstrom / ureg.ps if frame.has_velocities else None
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def get_forces(self, frame_index):
|
|
445
|
+
"""
|
|
446
|
+
Returns the forces on the atoms of the frame with index frame_index.
|
|
447
|
+
"""
|
|
448
|
+
frame = self.get_frame(frame_index)
|
|
449
|
+
return (
|
|
450
|
+
frame.forces * ureg.kJ / (MOL * ureg.angstrom) if frame.has_forces else None
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
def get_interactions(self):
|
|
454
|
+
interactions = self.get('interactions', None)
|
|
455
|
+
if interactions is not None:
|
|
456
|
+
return interactions
|
|
457
|
+
|
|
458
|
+
interaction_types = ['angles', 'bonds', 'dihedrals', 'impropers']
|
|
459
|
+
interactions = []
|
|
460
|
+
for interaction_type in interaction_types:
|
|
461
|
+
try:
|
|
462
|
+
interaction = getattr(self.universe, interaction_type)
|
|
463
|
+
except Exception:
|
|
464
|
+
continue
|
|
465
|
+
|
|
466
|
+
for inter in interaction:
|
|
467
|
+
atom_labels = None
|
|
468
|
+
try:
|
|
469
|
+
atom_labels = [
|
|
470
|
+
self.universe.atoms[ind].type for ind in inter.indices
|
|
471
|
+
]
|
|
472
|
+
except Exception:
|
|
473
|
+
self.logger.warning('Could not assign atom labels to interactions.')
|
|
474
|
+
interactions.append(
|
|
475
|
+
dict(
|
|
476
|
+
atom_labels=atom_labels,
|
|
477
|
+
# parameters=float(inter.value()), ## This is not the parameter but rather the value of the interaction order parameter for a single frame
|
|
478
|
+
# TODO implement functions to get parameters for individual parsers
|
|
479
|
+
atom_indices=inter.indices,
|
|
480
|
+
type=inter.btype,
|
|
481
|
+
)
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
self._results['interactions'] = interactions
|
|
485
|
+
|
|
486
|
+
return interactions
|
|
487
|
+
|
|
488
|
+
def __calc_diffusion_constant(self, times: NDArray, values: NDArray, dim: int = 3):
|
|
489
|
+
"""
|
|
490
|
+
Determines the diffusion constant from a fit of the mean squared displacement
|
|
491
|
+
vs. time according to the Einstein relation.
|
|
492
|
+
"""
|
|
493
|
+
linear_model = linregress(times, values)
|
|
494
|
+
slope = linear_model.slope
|
|
495
|
+
error = linear_model.rvalue
|
|
496
|
+
return slope * 1 / (2 * dim), error
|
|
497
|
+
|
|
498
|
+
def calc_molecular_mean_squared_displacements(self, max_mols=5000):
|
|
499
|
+
"""
|
|
500
|
+
Calculates the mean squared displacement for the center of mass of each
|
|
501
|
+
molecule type.
|
|
502
|
+
|
|
503
|
+
max_mols: the maximum number of molecules per bead group for calculating the msd, for efficiency purposes.
|
|
504
|
+
1M is arbitrary, 50k was tested and is very fast and does not seem to have any memory issues.
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
def mean_squared_displacement(start: np.ndarray, current: np.ndarray):
|
|
508
|
+
"""
|
|
509
|
+
Calculates mean square displacement between current and initial (start) coordinates.
|
|
510
|
+
"""
|
|
511
|
+
vec = start - current
|
|
512
|
+
return (vec**2).sum(axis=1).mean()
|
|
513
|
+
|
|
514
|
+
if self.universe is None:
|
|
515
|
+
return
|
|
516
|
+
trajectory = self.universe.trajectory[0] if self.universe.trajectory else None
|
|
517
|
+
dimensions = getattr(trajectory, 'dimensions', None) if trajectory else None
|
|
518
|
+
if dimensions is None:
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
n_frames = self.universe.trajectory.n_frames
|
|
522
|
+
if n_frames < 50:
|
|
523
|
+
self.logger.warning(
|
|
524
|
+
'At least 50 frames required to calculate molecular' # noqa: PLE1205
|
|
525
|
+
' mean squared displacements, skipping.',
|
|
526
|
+
)
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
dt = getattr(self.universe.trajectory, 'dt')
|
|
530
|
+
if dt is None:
|
|
531
|
+
return
|
|
532
|
+
times = np.arange(n_frames) * dt
|
|
533
|
+
|
|
534
|
+
bead_groups = self.bead_groups
|
|
535
|
+
if bead_groups is {}:
|
|
536
|
+
return bead_groups
|
|
537
|
+
|
|
538
|
+
moltypes = [moltype for moltype in bead_groups.keys()]
|
|
539
|
+
del_list = []
|
|
540
|
+
for i_moltype, moltype in enumerate(moltypes):
|
|
541
|
+
if bead_groups[moltype]._nbeads > max_mols:
|
|
542
|
+
try:
|
|
543
|
+
# select max_mols nr. of rnd molecules from this moltype
|
|
544
|
+
moltype_indices = np.array(
|
|
545
|
+
[atom._ix for atom in bead_groups[moltype]._atoms]
|
|
546
|
+
)
|
|
547
|
+
molnums = self.universe.atoms.molnums[moltype_indices]
|
|
548
|
+
molnum_types = np.unique(molnums)
|
|
549
|
+
molnum_types_rnd = np.sort(
|
|
550
|
+
np.random.choice(molnum_types, size=max_mols)
|
|
551
|
+
)
|
|
552
|
+
atom_indices_rnd = moltype_indices[
|
|
553
|
+
np.concatenate(
|
|
554
|
+
[
|
|
555
|
+
np.where(molnums == molnum)[0]
|
|
556
|
+
for molnum in molnum_types_rnd
|
|
557
|
+
]
|
|
558
|
+
)
|
|
559
|
+
]
|
|
560
|
+
selection = ' '.join([str(i) for i in atom_indices_rnd])
|
|
561
|
+
selection = f'index {selection}'
|
|
562
|
+
ags_moltype_rnd = self.universe.select_atoms(selection)
|
|
563
|
+
bead_groups[moltype] = BeadGroup(
|
|
564
|
+
ags_moltype_rnd, compound='fragments'
|
|
565
|
+
)
|
|
566
|
+
self.logger.warning(
|
|
567
|
+
'Maximum number of molecules for calculating the msd has been reached.'
|
|
568
|
+
' Will make a random selection for calculation.'
|
|
569
|
+
)
|
|
570
|
+
except Exception:
|
|
571
|
+
self.logger.warning(
|
|
572
|
+
'Tried to select random molecules for large group when calculating msd, but something went wrong. Skipping this molecule type.'
|
|
573
|
+
)
|
|
574
|
+
del_list.append(i_moltype)
|
|
575
|
+
moltypes = np.delete(moltypes, del_list)
|
|
576
|
+
|
|
577
|
+
msd_results = {}
|
|
578
|
+
msd_results['value'] = []
|
|
579
|
+
msd_results['times'] = []
|
|
580
|
+
msd_results['diffusion_constant'] = []
|
|
581
|
+
msd_results['error_diffusion_constant'] = []
|
|
582
|
+
for moltype in moltypes:
|
|
583
|
+
positions = self.get_nojump_positions(bead_groups[moltype])
|
|
584
|
+
results = shifted_correlation_average(
|
|
585
|
+
mean_squared_displacement, times, positions
|
|
586
|
+
)
|
|
587
|
+
msd_results['value'].append(results[1])
|
|
588
|
+
msd_results['times'].append(results[0])
|
|
589
|
+
diffusion_constant, error = self.__calc_diffusion_constant(*results)
|
|
590
|
+
msd_results['diffusion_constant'].append(diffusion_constant)
|
|
591
|
+
msd_results['error_diffusion_constant'].append(error)
|
|
592
|
+
|
|
593
|
+
msd_results['types'] = moltypes
|
|
594
|
+
msd_results['times'] = np.array(msd_results['times']) * ureg.picosecond
|
|
595
|
+
msd_results['value'] = np.array(msd_results['value']) * ureg.angstrom**2
|
|
596
|
+
msd_results['diffusion_constant'] = (
|
|
597
|
+
np.array(msd_results['diffusion_constant'])
|
|
598
|
+
* ureg.angstrom**2
|
|
599
|
+
/ ureg.picosecond
|
|
600
|
+
)
|
|
601
|
+
msd_results['error_diffusion_constant'] = np.array(
|
|
602
|
+
msd_results['error_diffusion_constant']
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
return msd_results
|
|
606
|
+
|
|
607
|
+
def parse_jumps(self, selection):
|
|
608
|
+
__ = self.universe.trajectory[0]
|
|
609
|
+
prev = np.array(selection.positions)
|
|
610
|
+
box = self.universe.trajectory[0].dimensions[:3]
|
|
611
|
+
sparse_data = namedtuple('SparseData', ['data', 'row', 'col'])
|
|
612
|
+
jump_data = (
|
|
613
|
+
sparse_data(data=array('b'), row=array('l'), col=array('l')),
|
|
614
|
+
sparse_data(data=array('b'), row=array('l'), col=array('l')),
|
|
615
|
+
sparse_data(data=array('b'), row=array('l'), col=array('l')),
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
for i_frame, _ in enumerate(self.universe.trajectory[1:]):
|
|
619
|
+
curr = np.array(selection.positions)
|
|
620
|
+
delta = ((curr - prev) / box).round().astype(np.int8)
|
|
621
|
+
prev = np.array(curr)
|
|
622
|
+
for d in range(3):
|
|
623
|
+
(col,) = np.where(delta[:, d] != 0)
|
|
624
|
+
jump_data[d].col.extend(col)
|
|
625
|
+
jump_data[d].row.extend([i_frame] * len(col))
|
|
626
|
+
jump_data[d].data.extend(delta[col, d])
|
|
627
|
+
|
|
628
|
+
return jump_data
|
|
629
|
+
|
|
630
|
+
def generate_nojump_matrices(self, selection):
|
|
631
|
+
jump_data = self.parse_jumps(selection)
|
|
632
|
+
N = len(self.universe.trajectory)
|
|
633
|
+
M = selection.positions.shape[0]
|
|
634
|
+
|
|
635
|
+
nojump_matrices = tuple(
|
|
636
|
+
sparse.csr_matrix((np.array(m.data), (m.row, m.col)), shape=(N, M))
|
|
637
|
+
for m in jump_data
|
|
638
|
+
)
|
|
639
|
+
return nojump_matrices
|
|
640
|
+
|
|
641
|
+
def get_nojump_positions(self, selection):
|
|
642
|
+
nojump_matrices = self.generate_nojump_matrices(selection)
|
|
643
|
+
box = self.universe.trajectory[0].dimensions[:3]
|
|
644
|
+
|
|
645
|
+
nojump_positions = []
|
|
646
|
+
for i_frame, __ in enumerate(self.universe.trajectory):
|
|
647
|
+
delta = (
|
|
648
|
+
np.array(
|
|
649
|
+
np.vstack([m[:i_frame, :].sum(axis=0) for m in nojump_matrices]).T
|
|
650
|
+
)
|
|
651
|
+
* box
|
|
652
|
+
)
|
|
653
|
+
nojump_positions.append(selection.positions - delta)
|
|
654
|
+
|
|
655
|
+
return np.array(nojump_positions)
|
|
656
|
+
|
|
657
|
+
def clean(self):
|
|
658
|
+
for name in os.listdir(self.maindir):
|
|
659
|
+
if name.startswith('.') and (
|
|
660
|
+
name.endswith('.lock') or name.endswith('.npz')
|
|
661
|
+
):
|
|
662
|
+
os.remove(os.path.join(self.maindir, name))
|