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,685 @@
|
|
|
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 os
|
|
21
|
+
import numpy as np
|
|
22
|
+
import fnmatch
|
|
23
|
+
from typing import List
|
|
24
|
+
import re
|
|
25
|
+
|
|
26
|
+
from nomad.parsing.file_parser.text_parser import Quantity, TextParser
|
|
27
|
+
from nomad.units import ureg
|
|
28
|
+
from runschema.run import Run, Program
|
|
29
|
+
from runschema.system import System, Atoms
|
|
30
|
+
from runschema.method import Method, ForceField, Model, AtomParameters
|
|
31
|
+
from runschema.calculation import (
|
|
32
|
+
Calculation,
|
|
33
|
+
Energy,
|
|
34
|
+
EnergyEntry,
|
|
35
|
+
VibrationalFrequencies,
|
|
36
|
+
)
|
|
37
|
+
from simulationworkflowschema import GeometryOptimization, GeometryOptimizationMethod
|
|
38
|
+
from atomisticparsers.utils import MDAnalysisParser, MDParser
|
|
39
|
+
from .metainfo.tinker import x_tinker_section_control_parameters
|
|
40
|
+
|
|
41
|
+
re_n = r'[\n\r]'
|
|
42
|
+
re_f = r'[-+]?\d+\.\d*(?:[DdEe][-+]\d+)?'
|
|
43
|
+
mol = (1 * ureg.mol).to('particle').magnitude
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class KeyParser(TextParser):
|
|
47
|
+
def __init__(self):
|
|
48
|
+
super().__init__()
|
|
49
|
+
|
|
50
|
+
def init_quantities(self):
|
|
51
|
+
def to_key_val(val_in):
|
|
52
|
+
val = val_in.strip().split()
|
|
53
|
+
if len(val) == 1:
|
|
54
|
+
return [val[0], True]
|
|
55
|
+
elif len(val) == 2:
|
|
56
|
+
val[1] = float(val[1]) if re.match(re_f, val[1]) else val[1]
|
|
57
|
+
return val
|
|
58
|
+
else:
|
|
59
|
+
return [val[0], val[1:]]
|
|
60
|
+
|
|
61
|
+
self._quantities = [
|
|
62
|
+
Quantity(
|
|
63
|
+
'key_val',
|
|
64
|
+
rf'([a-z\-]+) *([^#]*?) *{re_n}',
|
|
65
|
+
str_operation=to_key_val,
|
|
66
|
+
repeats=True,
|
|
67
|
+
convert=False,
|
|
68
|
+
)
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RunParser(TextParser):
|
|
73
|
+
def __init__(self):
|
|
74
|
+
super().__init__()
|
|
75
|
+
|
|
76
|
+
def init_quantities(self):
|
|
77
|
+
def to_argument(val_in):
|
|
78
|
+
val_in = val_in.strip().split()
|
|
79
|
+
argument = dict()
|
|
80
|
+
if '-k' in val_in:
|
|
81
|
+
argument['key'] = val_in.pop(val_in.index('-k') + 1)
|
|
82
|
+
val_in.remove('-k')
|
|
83
|
+
argument['name'] = val_in.pop(0)
|
|
84
|
+
argument['parameters'] = val_in
|
|
85
|
+
return argument
|
|
86
|
+
|
|
87
|
+
self._quantities = [
|
|
88
|
+
Quantity(
|
|
89
|
+
'molecular_dynamics',
|
|
90
|
+
r'dynamic +(\S+ +\-*k* *\S+.+)',
|
|
91
|
+
repeats=True,
|
|
92
|
+
str_operation=to_argument,
|
|
93
|
+
),
|
|
94
|
+
Quantity(
|
|
95
|
+
'geometry_optimization',
|
|
96
|
+
r'minimize +(\S+ +\-*k* *\S+.+)',
|
|
97
|
+
repeats=True,
|
|
98
|
+
str_operation=to_argument,
|
|
99
|
+
),
|
|
100
|
+
Quantity(
|
|
101
|
+
'single_point',
|
|
102
|
+
r'vibrate +(\S+ +\-*k* *\S+.+)',
|
|
103
|
+
repeats=True,
|
|
104
|
+
str_operation=to_argument,
|
|
105
|
+
),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class OutParser(TextParser):
|
|
110
|
+
def __init__(self):
|
|
111
|
+
super().__init__()
|
|
112
|
+
|
|
113
|
+
def init_quantities(self):
|
|
114
|
+
iteration_quantity = Quantity(
|
|
115
|
+
'iteration',
|
|
116
|
+
rf'Iter.+([\s\S]+?){re_n} *{re_n}',
|
|
117
|
+
sub_parser=TextParser(
|
|
118
|
+
quantities=[
|
|
119
|
+
Quantity(
|
|
120
|
+
'step',
|
|
121
|
+
rf' +\d+ +({re_f} +{re_f} +[\d\.DE\+\- ]+)',
|
|
122
|
+
str_operation=lambda x: [
|
|
123
|
+
float(v)
|
|
124
|
+
for v in x.replace('D+', 'E+').replace('d+', 'e+').split()
|
|
125
|
+
],
|
|
126
|
+
repeats=True,
|
|
127
|
+
dtype=np.dtype(np.float64),
|
|
128
|
+
)
|
|
129
|
+
]
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
calculation_quantities = [
|
|
134
|
+
Quantity('program_version', r'Version ([\d\.]+)', dtype=str),
|
|
135
|
+
Quantity(
|
|
136
|
+
'vibrate',
|
|
137
|
+
r'(Eigenvalues of the Hessian Matrix[\s\S]+?)\Z',
|
|
138
|
+
sub_parser=TextParser(
|
|
139
|
+
quantities=[
|
|
140
|
+
Quantity(
|
|
141
|
+
'eigenvalues',
|
|
142
|
+
r'Eigenvalues of the Hessian Matrix :\s+([\d\-\s\.]+)',
|
|
143
|
+
dtype=np.dtype(np.float64),
|
|
144
|
+
),
|
|
145
|
+
Quantity(
|
|
146
|
+
'frequencies',
|
|
147
|
+
r'Vibrational Frequencies \(cm\-1\) :([\d\-\s\.]+)',
|
|
148
|
+
dtype=np.dtype(np.float64),
|
|
149
|
+
),
|
|
150
|
+
]
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
Quantity(
|
|
154
|
+
'minimize',
|
|
155
|
+
r'(.+?Optimization :[\s\S]+?Final Gradient Norm.+)',
|
|
156
|
+
sub_parser=TextParser(
|
|
157
|
+
quantities=[
|
|
158
|
+
iteration_quantity,
|
|
159
|
+
Quantity(
|
|
160
|
+
'method', r' +(.+) Optimization', flatten=False, dtype=str
|
|
161
|
+
),
|
|
162
|
+
Quantity(
|
|
163
|
+
'x_tiner_final_function_value',
|
|
164
|
+
rf'Final Function Value : +({re_f})',
|
|
165
|
+
dtype=np.float64,
|
|
166
|
+
),
|
|
167
|
+
Quantity(
|
|
168
|
+
'x_tinker_final_rms_gradient',
|
|
169
|
+
rf'Final RMS Gradient : +({re_f})',
|
|
170
|
+
dtype=np.float64,
|
|
171
|
+
),
|
|
172
|
+
Quantity(
|
|
173
|
+
'x_tinker_final_gradient_norm',
|
|
174
|
+
rf'Final Gradient Norm : +({re_f})',
|
|
175
|
+
dtype=np.float64,
|
|
176
|
+
),
|
|
177
|
+
]
|
|
178
|
+
),
|
|
179
|
+
),
|
|
180
|
+
Quantity(
|
|
181
|
+
'dynamic',
|
|
182
|
+
r'(Molecular Dynamics[\s\S]+?)(?:\#\#\#\#\#|\Z)',
|
|
183
|
+
sub_parser=TextParser(
|
|
184
|
+
quantities=[
|
|
185
|
+
iteration_quantity,
|
|
186
|
+
Quantity(
|
|
187
|
+
'instantaneous_values',
|
|
188
|
+
r'(Instantaneous Values for Frame Saved at[\s\S]+?Coordinate File.+)',
|
|
189
|
+
repeats=True,
|
|
190
|
+
sub_parser=TextParser(
|
|
191
|
+
quantities=[
|
|
192
|
+
Quantity(
|
|
193
|
+
'step', r'(\d+) Dynamics Steps', dtype=np.int32
|
|
194
|
+
),
|
|
195
|
+
Quantity(
|
|
196
|
+
'time',
|
|
197
|
+
rf'Current Time +({re_f}) Picosecond',
|
|
198
|
+
dtype=np.float64,
|
|
199
|
+
unit=ureg.ps,
|
|
200
|
+
),
|
|
201
|
+
Quantity(
|
|
202
|
+
'potential',
|
|
203
|
+
rf'Current Potential +({re_f}) Kcal/mole',
|
|
204
|
+
dtype=np.float64,
|
|
205
|
+
unit=ureg.J * 4184.0 / mol,
|
|
206
|
+
),
|
|
207
|
+
Quantity(
|
|
208
|
+
'kinetic',
|
|
209
|
+
rf'Current Kinetic +({re_f}) Kcal/mole',
|
|
210
|
+
dtype=np.float64,
|
|
211
|
+
unit=ureg.J * 4184.0 / mol,
|
|
212
|
+
),
|
|
213
|
+
Quantity(
|
|
214
|
+
'lattice_lengths',
|
|
215
|
+
rf'Lattice Lengths +({re_f} +{re_f} +{re_f})',
|
|
216
|
+
dtype=np.dtype(np.float64),
|
|
217
|
+
),
|
|
218
|
+
Quantity(
|
|
219
|
+
'lattice_angles',
|
|
220
|
+
rf'Lattice Angles +({re_f} +{re_f} +{re_f})',
|
|
221
|
+
dtype=np.dtype(np.float64),
|
|
222
|
+
),
|
|
223
|
+
Quantity(
|
|
224
|
+
'frame', r'Frame Number +(\d+)', dtype=np.int32
|
|
225
|
+
),
|
|
226
|
+
Quantity(
|
|
227
|
+
'coordinate_file',
|
|
228
|
+
r'Coordinate File +(\S+)',
|
|
229
|
+
dtype=str,
|
|
230
|
+
),
|
|
231
|
+
]
|
|
232
|
+
),
|
|
233
|
+
),
|
|
234
|
+
Quantity(
|
|
235
|
+
'average_values',
|
|
236
|
+
r'(Average Values for the Last[\s\S]+?Density.+)',
|
|
237
|
+
repeats=True,
|
|
238
|
+
sub_parser=TextParser(
|
|
239
|
+
quantities=[
|
|
240
|
+
Quantity(
|
|
241
|
+
'step', r'(\d+) Dynamics Steps', dtype=np.int32
|
|
242
|
+
),
|
|
243
|
+
Quantity(
|
|
244
|
+
'time',
|
|
245
|
+
rf'Simulation Time +({re_f}) Picosecond',
|
|
246
|
+
dtype=np.float64,
|
|
247
|
+
unit=ureg.ps,
|
|
248
|
+
),
|
|
249
|
+
Quantity(
|
|
250
|
+
'energy_total',
|
|
251
|
+
rf'Total Energy +({re_f}) Kcal/mole',
|
|
252
|
+
dtype=np.float64,
|
|
253
|
+
unit=ureg.J * 4184.0 / mol,
|
|
254
|
+
),
|
|
255
|
+
Quantity(
|
|
256
|
+
'potential',
|
|
257
|
+
rf'Potential Energy +({re_f}) Kcal/mole',
|
|
258
|
+
dtype=np.float64,
|
|
259
|
+
unit=ureg.J * 4184.0 / mol,
|
|
260
|
+
),
|
|
261
|
+
Quantity(
|
|
262
|
+
'kinetic',
|
|
263
|
+
rf'Kinetic Energy +({re_f}) Kcal/mole',
|
|
264
|
+
dtype=np.float64,
|
|
265
|
+
unit=ureg.J * 4184.0 / mol,
|
|
266
|
+
),
|
|
267
|
+
Quantity(
|
|
268
|
+
'temperature',
|
|
269
|
+
rf'Temperature +({re_f}) Kelvin',
|
|
270
|
+
dtype=np.float64,
|
|
271
|
+
unit=ureg.kelvin,
|
|
272
|
+
),
|
|
273
|
+
Quantity(
|
|
274
|
+
'pressure',
|
|
275
|
+
rf'Pressure +({re_f}) Atmosphere',
|
|
276
|
+
dtype=np.float64,
|
|
277
|
+
unit=ureg.atm,
|
|
278
|
+
),
|
|
279
|
+
Quantity(
|
|
280
|
+
'density',
|
|
281
|
+
rf'Density +({re_f}) Grams/cc',
|
|
282
|
+
dtype=np.float64,
|
|
283
|
+
unit=ureg.g / ureg.cm**3,
|
|
284
|
+
),
|
|
285
|
+
]
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
]
|
|
289
|
+
),
|
|
290
|
+
),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
self._quantities = [
|
|
294
|
+
Quantity(
|
|
295
|
+
'run',
|
|
296
|
+
r'Software Tools for Molecular Design([\s\S]+?)(?:TINKER \-\-\-|\Z)',
|
|
297
|
+
repeats=True,
|
|
298
|
+
sub_parser=TextParser(quantities=calculation_quantities),
|
|
299
|
+
)
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class TinkerParser(MDParser):
|
|
304
|
+
def __init__(self):
|
|
305
|
+
self.out_parser = OutParser()
|
|
306
|
+
self.traj_parser = MDAnalysisParser()
|
|
307
|
+
self.key_parser = KeyParser()
|
|
308
|
+
self.run_parser = RunParser()
|
|
309
|
+
self._run_types = {
|
|
310
|
+
'vibrate': 'single_point',
|
|
311
|
+
'minimize': 'geometry_optimization',
|
|
312
|
+
'dynamic': 'molecular_dynamics',
|
|
313
|
+
}
|
|
314
|
+
super().__init__()
|
|
315
|
+
|
|
316
|
+
def _get_tinker_file(self, ext):
|
|
317
|
+
if ext not in self._files:
|
|
318
|
+
return
|
|
319
|
+
current = self._files[ext]['current']
|
|
320
|
+
# advance to the next file
|
|
321
|
+
files = self._files[ext]['files']
|
|
322
|
+
try:
|
|
323
|
+
self._files[ext]['current'] = files[files.index(current) + 1]
|
|
324
|
+
except Exception:
|
|
325
|
+
# the last file has been reached in this case
|
|
326
|
+
pass
|
|
327
|
+
return os.path.join(self.maindir, current)
|
|
328
|
+
|
|
329
|
+
def parse_system(self, index, filename):
|
|
330
|
+
self.traj_parser.mainfile = filename
|
|
331
|
+
self.traj_parser.options = dict(
|
|
332
|
+
topology_format='ARC' if filename.endswith('.arc') else 'TXYZ'
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if self.traj_parser.universe is None:
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
sec_system = System()
|
|
339
|
+
self.archive.run[-1].system.append(sec_system)
|
|
340
|
+
trajectory = self.traj_parser.universe.trajectory[index]
|
|
341
|
+
sec_system.atoms = Atoms(
|
|
342
|
+
positions=trajectory.positions * ureg.angstrom,
|
|
343
|
+
labels=[atom.name for atom in list(self.traj_parser.universe.atoms)],
|
|
344
|
+
)
|
|
345
|
+
if trajectory.triclinic_dimensions is not None:
|
|
346
|
+
sec_system.atoms.lattice_vectors = (
|
|
347
|
+
trajectory.triclinic_dimensions * ureg.angstrom
|
|
348
|
+
)
|
|
349
|
+
sec_system.atoms.periodic = [True, True, True]
|
|
350
|
+
if trajectory.has_velocities:
|
|
351
|
+
sec_system.atoms.velocities = trajectory.velocities * (
|
|
352
|
+
ureg.angstrom / ureg.ps
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
return sec_system
|
|
356
|
+
|
|
357
|
+
def parse_method(self):
|
|
358
|
+
sec_method = Method()
|
|
359
|
+
self.archive.run[-1].method.append(sec_method)
|
|
360
|
+
|
|
361
|
+
parameters = self.archive.run[-1].x_tinker_control_parameters
|
|
362
|
+
if parameters.get('parameters') is not None:
|
|
363
|
+
sec_method.force_field = ForceField(
|
|
364
|
+
model=[Model(name=parameters['parameters'])]
|
|
365
|
+
)
|
|
366
|
+
# TODO read the prm file
|
|
367
|
+
|
|
368
|
+
property_map = {
|
|
369
|
+
'name': 'label',
|
|
370
|
+
'type': 'x_tinker_atom_type',
|
|
371
|
+
'resid': 'x_tinker_atom_resid',
|
|
372
|
+
}
|
|
373
|
+
if self.traj_parser.universe is not None:
|
|
374
|
+
for atom in list(self.traj_parser.universe.atoms):
|
|
375
|
+
sec_atom = AtomParameters()
|
|
376
|
+
sec_method.atom_parameters.append(sec_atom)
|
|
377
|
+
for key in ['charge', 'mass', 'name', 'type', 'resid']:
|
|
378
|
+
if hasattr(atom, key):
|
|
379
|
+
setattr(
|
|
380
|
+
sec_atom, property_map.get(key, key), getattr(atom, key)
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# TODO add interaction parameters
|
|
384
|
+
|
|
385
|
+
def resolve_workflow_type(self, run):
|
|
386
|
+
for program in run.keys():
|
|
387
|
+
if program in self._run_types and run.get(program) is not None:
|
|
388
|
+
return self._run_types[program]
|
|
389
|
+
|
|
390
|
+
def parse_workflow(self, program, run):
|
|
391
|
+
def resolve_ensemble_type():
|
|
392
|
+
parameters = self.archive.run[-1].x_tinker_control_parameters
|
|
393
|
+
if parameters is None:
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
thermostat, barostat = (
|
|
397
|
+
parameters.get('thermostat', ''),
|
|
398
|
+
parameters.get('barostat', ''),
|
|
399
|
+
)
|
|
400
|
+
thermostats = ['berendsen', 'andersen', 'bussi']
|
|
401
|
+
# TODO verify this
|
|
402
|
+
if barostat.lower() in ['berendsen', 'montecarlo']:
|
|
403
|
+
ensemble_type = 'NPT'
|
|
404
|
+
elif not barostat and thermostat in thermostats:
|
|
405
|
+
ensemble_type = 'NVT'
|
|
406
|
+
elif not barostat and not thermostat:
|
|
407
|
+
ensemble_type = 'NVE'
|
|
408
|
+
else:
|
|
409
|
+
ensemble_type = None
|
|
410
|
+
|
|
411
|
+
return ensemble_type
|
|
412
|
+
|
|
413
|
+
# TODO handle multiple workflow sections
|
|
414
|
+
parameters = list(program.get('parameters', []))
|
|
415
|
+
# so we cover the case when optional parameters are missing
|
|
416
|
+
parameters.extend([None] * 6)
|
|
417
|
+
workflow_type = self.resolve_workflow_type(run)
|
|
418
|
+
if workflow_type == 'molecular_dynamics':
|
|
419
|
+
control_parameters = self.archive.run[-1].x_tinker_control_parameters
|
|
420
|
+
# TODO verify this! I am sure it is wrong but tinker documentation does not specify clearly
|
|
421
|
+
ensemble_types = ['NVE', 'NVT', 'NPT', None, None]
|
|
422
|
+
ensemble = (
|
|
423
|
+
ensemble_types[int(parameters[3]) - 1]
|
|
424
|
+
if parameters[3] is not None
|
|
425
|
+
else resolve_ensemble_type()
|
|
426
|
+
)
|
|
427
|
+
self.parse_md_workflow(
|
|
428
|
+
dict(
|
|
429
|
+
method=dict(
|
|
430
|
+
thermodynamic_ensemble=ensemble,
|
|
431
|
+
integration_timestep=parameters[1] * ureg.fs
|
|
432
|
+
if parameters[1]
|
|
433
|
+
else parameters[1],
|
|
434
|
+
),
|
|
435
|
+
x_tinker_barostat_tau=control_parameters.get('tau-pressure'),
|
|
436
|
+
x_tinker_barostat_type=control_parameters.get('barostat'),
|
|
437
|
+
x_tinker_integrator_type=control_parameters.get('integrator'),
|
|
438
|
+
x_tinker_number_of_steps_requested=parameters[0],
|
|
439
|
+
x_tinker_integrator_dt=parameters[1] * ureg.ps
|
|
440
|
+
if parameters[1]
|
|
441
|
+
else parameters,
|
|
442
|
+
x_tinker_thermostat_target_temperature=parameters[4] * ureg.kelvin
|
|
443
|
+
if parameters[4]
|
|
444
|
+
else parameters[4],
|
|
445
|
+
x_tinker_barostat_target_pressure=parameters[5] * ureg.atmosphere
|
|
446
|
+
if parameters[5]
|
|
447
|
+
else parameters[5],
|
|
448
|
+
x_tinker_thermostat_tau=control_parameters.get('tau-temperature'),
|
|
449
|
+
x_tinker_thermostat_type=control_parameters.get('thermostat'),
|
|
450
|
+
)
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
elif workflow_type == 'geometry_optimization':
|
|
454
|
+
workflow = GeometryOptimization(method=GeometryOptimizationMethod())
|
|
455
|
+
workflow.method.method = run.minimize.get('method')
|
|
456
|
+
workflow.x_tinker_convergence_tolerance_rms_gradient = parameters[0]
|
|
457
|
+
for key, val in run.minimize.items():
|
|
458
|
+
if key.startswith('x_tinker'):
|
|
459
|
+
setattr(workflow, key, val)
|
|
460
|
+
|
|
461
|
+
self.archive.workflow2 = workflow
|
|
462
|
+
|
|
463
|
+
def write_to_archive(self) -> None:
|
|
464
|
+
self.out_parser.mainfile = self.mainfile
|
|
465
|
+
self.out_parser.logger = self.logger
|
|
466
|
+
self.maindir = os.path.dirname(self.mainfile)
|
|
467
|
+
self._base_name = os.path.basename(self.mainfile).rsplit('.', 1)[0]
|
|
468
|
+
files = os.listdir(self.maindir)
|
|
469
|
+
# required to track the current files
|
|
470
|
+
self._files = dict()
|
|
471
|
+
for ext in ['xyz', 'arc', 'key']:
|
|
472
|
+
matches = fnmatch.filter(files, f'{self._base_name}.{ext}*')
|
|
473
|
+
if not matches:
|
|
474
|
+
matches = fnmatch.filter(files, f'tinker.{ext}*')
|
|
475
|
+
matches.sort(
|
|
476
|
+
key=lambda x: int(x.rsplit('_', 1)[-1]) if x[-1].isdecimal() else 0
|
|
477
|
+
)
|
|
478
|
+
self._files[ext] = dict(
|
|
479
|
+
files=matches, current=matches[0] if matches else ''
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
self.maindir = os.path.dirname(self.mainfile)
|
|
483
|
+
|
|
484
|
+
workflows: List[str] = []
|
|
485
|
+
|
|
486
|
+
def get_reference_filename(program):
|
|
487
|
+
# resolves the filename as provided in the cli command for the program
|
|
488
|
+
filename = program.get('name', '')
|
|
489
|
+
if '.xyz' not in filename:
|
|
490
|
+
filename = self._files['xyz']['current']
|
|
491
|
+
# succeding files start from the index of the file for geometry_opt and md
|
|
492
|
+
filename = filename if '.xyz' in filename else f'{filename}.xyz'
|
|
493
|
+
if workflows[-1] in ['geometry_optimization', 'molecular_dynamics']:
|
|
494
|
+
files = self._files['xyz']['files']
|
|
495
|
+
try:
|
|
496
|
+
self._files['xyz']['current'] = files[files.index(filename) + 1]
|
|
497
|
+
except Exception:
|
|
498
|
+
pass
|
|
499
|
+
return os.path.join(self.maindir, filename)
|
|
500
|
+
|
|
501
|
+
# necesarry to extract program parameters from the cli command in basename.run
|
|
502
|
+
self.run_parser.mainfile = os.path.join(self.maindir, f'{self._base_name}.run')
|
|
503
|
+
|
|
504
|
+
# TODO put runs in separate archives
|
|
505
|
+
for run in self.out_parser.get('run', []):
|
|
506
|
+
sec_run = Run()
|
|
507
|
+
self.archive.run.append(sec_run)
|
|
508
|
+
sec_run.program = Program(name='tinker', version=run.get('program_version'))
|
|
509
|
+
|
|
510
|
+
workflow_type = self.resolve_workflow_type(run)
|
|
511
|
+
workflows.append(workflow_type)
|
|
512
|
+
# get parameters of the program from the cli command, the key file and the
|
|
513
|
+
# initial structure file can also be specied as an argument so we need to
|
|
514
|
+
# extract the information here
|
|
515
|
+
# program can be executed for an arbitrary number of times so we need to
|
|
516
|
+
# resolve the index of the appropriate command
|
|
517
|
+
n_workflow = (
|
|
518
|
+
len([workflow for workflow in workflows if workflow == workflow_type])
|
|
519
|
+
- 1
|
|
520
|
+
)
|
|
521
|
+
program = self.run_parser.get(workflow_type, [])
|
|
522
|
+
program = program[n_workflow] if len(program) > n_workflow else dict()
|
|
523
|
+
if run.vibrate is not None:
|
|
524
|
+
# reference structure
|
|
525
|
+
sec_system = self.parse_system(0, get_reference_filename(program))
|
|
526
|
+
sec_scc = Calculation()
|
|
527
|
+
sec_run.calculation.append(sec_scc)
|
|
528
|
+
sec_vibrations = VibrationalFrequencies()
|
|
529
|
+
sec_scc.vibrational_frequencies.append(sec_vibrations)
|
|
530
|
+
sec_vibrations.value = [
|
|
531
|
+
run.vibrate.frequencies[n]
|
|
532
|
+
for n in range(len(run.vibrate.get('frequencies', [])))
|
|
533
|
+
if n % 2 == 1
|
|
534
|
+
] * (1 / ureg.cm)
|
|
535
|
+
sec_vibrations.x_tinker_eigenvalues = [
|
|
536
|
+
run.vibrate.eigenvalues[n]
|
|
537
|
+
for n in range(len(run.vibrate.get('eigenvalues', [])))
|
|
538
|
+
if n % 2 == 1
|
|
539
|
+
]
|
|
540
|
+
sec_scc.system_ref = sec_system
|
|
541
|
+
|
|
542
|
+
if run.minimize is not None:
|
|
543
|
+
# initial structure
|
|
544
|
+
initial_system = self.parse_system(0, get_reference_filename(program))
|
|
545
|
+
|
|
546
|
+
# optimized structure
|
|
547
|
+
sec_system = self.parse_system(0, self._get_tinker_file('xyz'))
|
|
548
|
+
|
|
549
|
+
for n, step in enumerate(
|
|
550
|
+
run.minimize.get('iteration', {}).get('step', [])
|
|
551
|
+
):
|
|
552
|
+
sec_scc = Calculation()
|
|
553
|
+
sec_run.calculation.append(sec_scc)
|
|
554
|
+
sec_scc.energy = Energy(
|
|
555
|
+
total=EnergyEntry(
|
|
556
|
+
value=step[0]
|
|
557
|
+
* len(sec_system.atoms.positions)
|
|
558
|
+
* ureg.J
|
|
559
|
+
* 4184.0
|
|
560
|
+
/ mol
|
|
561
|
+
)
|
|
562
|
+
)
|
|
563
|
+
if n == 0:
|
|
564
|
+
sec_scc.system_ref = initial_system
|
|
565
|
+
# only the optimized structure is printed, corresponding to the last scc
|
|
566
|
+
sec_scc.system_ref = sec_system
|
|
567
|
+
|
|
568
|
+
if run.dynamic is not None:
|
|
569
|
+
self.traj_parser.mainfile = self._get_tinker_file('arc')
|
|
570
|
+
self.traj_parser.options = dict(topology_format='ARC')
|
|
571
|
+
average_values = {
|
|
572
|
+
value.step: value for value in run.dynamic.get('average_values', [])
|
|
573
|
+
}
|
|
574
|
+
# set up md parser
|
|
575
|
+
# TODO this is not efficient as we need to load traj file to know if it exists
|
|
576
|
+
traj_steps, thermo_steps, n_atoms, filenames = [], [], [], []
|
|
577
|
+
for nframe, value in enumerate(
|
|
578
|
+
run.dynamic.get('instantaneous_values', [])
|
|
579
|
+
):
|
|
580
|
+
filename = os.path.join(
|
|
581
|
+
self.maindir, value.get('coordinate_file', '')
|
|
582
|
+
)
|
|
583
|
+
index = nframe if filename.endswith('.arc') else 0
|
|
584
|
+
filenames.append((filename, index))
|
|
585
|
+
thermo_steps.append(value.step)
|
|
586
|
+
self.traj_parser.mainfile = filename
|
|
587
|
+
self.traj_parser.options = dict(
|
|
588
|
+
topology_format='ARC' if filename.endswith('.arc') else 'TXYZ'
|
|
589
|
+
)
|
|
590
|
+
trajectory = self.traj_parser.universe.trajectory[index]
|
|
591
|
+
if trajectory is not None:
|
|
592
|
+
traj_steps.append(value.step)
|
|
593
|
+
n_atoms.append(len(trajectory))
|
|
594
|
+
self.n_atoms = max(n_atoms)
|
|
595
|
+
self.trajectory_steps = traj_steps
|
|
596
|
+
self.thermodynamics_steps = thermo_steps
|
|
597
|
+
|
|
598
|
+
for n_frame, step in enumerate(self.thermodynamics_steps):
|
|
599
|
+
self.traj_parser.mainfile = filenames[n_frame][0]
|
|
600
|
+
trajectory = self.traj_parser.universe.trajectory[
|
|
601
|
+
filenames[n_frame][1]
|
|
602
|
+
]
|
|
603
|
+
if step in self.trajectory_steps:
|
|
604
|
+
# sampled trajectory
|
|
605
|
+
positions = trajectory.positions * ureg.angstrom
|
|
606
|
+
labels = [
|
|
607
|
+
atom.name for atom in list(self.traj_parser.universe.atoms)
|
|
608
|
+
]
|
|
609
|
+
lattice_vectors, periodic, velocities = None, None, None
|
|
610
|
+
if trajectory.triclinic_dimensions is not None:
|
|
611
|
+
lattice_vectors = (
|
|
612
|
+
trajectory.triclinic_dimensions * ureg.angstrom
|
|
613
|
+
)
|
|
614
|
+
periodic = [True, True, True]
|
|
615
|
+
if trajectory.has_velocities:
|
|
616
|
+
velocities = trajectory.velocities * (
|
|
617
|
+
ureg.angstrom / ureg.ps
|
|
618
|
+
)
|
|
619
|
+
self.parse_trajectory_step(
|
|
620
|
+
dict(
|
|
621
|
+
atoms=dict(
|
|
622
|
+
positions=positions,
|
|
623
|
+
labels=labels,
|
|
624
|
+
lattice_vectors=lattice_vectors,
|
|
625
|
+
periodic=periodic,
|
|
626
|
+
velocities=velocities,
|
|
627
|
+
)
|
|
628
|
+
)
|
|
629
|
+
)
|
|
630
|
+
# thermo properties
|
|
631
|
+
instantaneous = run.dynamic.instantaneous_values[n_frame]
|
|
632
|
+
n_atoms_step = n_atoms[traj_steps.index(step)]
|
|
633
|
+
energy = dict(
|
|
634
|
+
total=dict(
|
|
635
|
+
value=(instantaneous.potential + instantaneous.kinetic)
|
|
636
|
+
* n_atoms_step,
|
|
637
|
+
kinetic=instantaneous.kinetic * n_atoms_step,
|
|
638
|
+
potential=instantaneous.potential * n_atoms_step,
|
|
639
|
+
)
|
|
640
|
+
)
|
|
641
|
+
data = dict(energy=energy, step=instantaneous.step)
|
|
642
|
+
average = average_values.get(instantaneous.step)
|
|
643
|
+
if average:
|
|
644
|
+
data['temperature'] = average.temperature
|
|
645
|
+
data['pressure'] = average.pressure
|
|
646
|
+
if trajectory is not None and trajectory.has_forces:
|
|
647
|
+
data['forces'] = dict(
|
|
648
|
+
total=dict(
|
|
649
|
+
value=trajectory.forces * (ureg.kJ / ureg.angstrom)
|
|
650
|
+
)
|
|
651
|
+
)
|
|
652
|
+
self.parse_thermodynamics_step(data)
|
|
653
|
+
|
|
654
|
+
# TODO add support for other tinker programs
|
|
655
|
+
|
|
656
|
+
# control parameters
|
|
657
|
+
# a key file can be specified optionally via the cli we assign this instead to get the parameters
|
|
658
|
+
self.key_parser.mainfile = os.path.join(
|
|
659
|
+
self.maindir, program.get('key', '')
|
|
660
|
+
)
|
|
661
|
+
if self.key_parser.mainfile is None:
|
|
662
|
+
# if the key file is not specified in cli, read from either basename.key
|
|
663
|
+
# or tinker.key
|
|
664
|
+
self.key_parser.mainfile = self._get_tinker_file('key')
|
|
665
|
+
|
|
666
|
+
parameters = {
|
|
667
|
+
key.lower(): val for key, val in self.key_parser.get('key_val', [])
|
|
668
|
+
}
|
|
669
|
+
sec_run.x_tinker_control_parameters = parameters
|
|
670
|
+
# TODO should this be removed and only have a dictionary of control parameters
|
|
671
|
+
sec_control = x_tinker_section_control_parameters()
|
|
672
|
+
sec_run.x_tinker_section_control_parameters.append(sec_control)
|
|
673
|
+
for key, val in parameters.items():
|
|
674
|
+
key = key.replace('-', '_')
|
|
675
|
+
setattr(
|
|
676
|
+
sec_control,
|
|
677
|
+
f'x_tinker_inout_control_{key}',
|
|
678
|
+
val if isinstance(val, bool) else str(val),
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
self.parse_method()
|
|
682
|
+
|
|
683
|
+
self.parse_workflow(program, run)
|
|
684
|
+
|
|
685
|
+
self.traj_parser.close()
|