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.
Files changed (80) hide show
  1. atomisticparsers/__init__.py +400 -0
  2. atomisticparsers/amber/__init__.py +19 -0
  3. atomisticparsers/amber/__main__.py +31 -0
  4. atomisticparsers/amber/metainfo/__init__.py +19 -0
  5. atomisticparsers/amber/metainfo/amber.py +495 -0
  6. atomisticparsers/amber/parser.py +42 -0
  7. atomisticparsers/asap/__init__.py +19 -0
  8. atomisticparsers/asap/__main__.py +31 -0
  9. atomisticparsers/asap/metainfo/__init__.py +19 -0
  10. atomisticparsers/asap/metainfo/asap.py +75 -0
  11. atomisticparsers/asap/parser.py +197 -0
  12. atomisticparsers/bopfox/__init__.py +19 -0
  13. atomisticparsers/bopfox/__main__.py +31 -0
  14. atomisticparsers/bopfox/metainfo/__init__.py +19 -0
  15. atomisticparsers/bopfox/metainfo/bopfox.py +225 -0
  16. atomisticparsers/bopfox/parser.py +808 -0
  17. atomisticparsers/dftbplus/__init__.py +19 -0
  18. atomisticparsers/dftbplus/__main__.py +31 -0
  19. atomisticparsers/dftbplus/metainfo/__init__.py +19 -0
  20. atomisticparsers/dftbplus/metainfo/dftbplus.py +217 -0
  21. atomisticparsers/dftbplus/parser.py +500 -0
  22. atomisticparsers/dlpoly/__init__.py +19 -0
  23. atomisticparsers/dlpoly/__main__.py +31 -0
  24. atomisticparsers/dlpoly/metainfo/__init__.py +19 -0
  25. atomisticparsers/dlpoly/metainfo/dl_poly.py +312 -0
  26. atomisticparsers/dlpoly/parser.py +798 -0
  27. atomisticparsers/gromacs/__init__.py +19 -0
  28. atomisticparsers/gromacs/__main__.py +31 -0
  29. atomisticparsers/gromacs/metainfo/__init__.py +19 -0
  30. atomisticparsers/gromacs/metainfo/gromacs.py +2388 -0
  31. atomisticparsers/gromacs/parser.py +1581 -0
  32. atomisticparsers/gromos/__init__.py +19 -0
  33. atomisticparsers/gromos/__main__.py +31 -0
  34. atomisticparsers/gromos/metainfo/__init__.py +19 -0
  35. atomisticparsers/gromos/metainfo/gromos.py +1995 -0
  36. atomisticparsers/gromos/parser.py +58 -0
  37. atomisticparsers/gulp/__init__.py +19 -0
  38. atomisticparsers/gulp/__main__.py +31 -0
  39. atomisticparsers/gulp/metainfo/__init__.py +19 -0
  40. atomisticparsers/gulp/metainfo/gulp.py +1117 -0
  41. atomisticparsers/gulp/parser.py +1316 -0
  42. atomisticparsers/h5md/__init__.py +19 -0
  43. atomisticparsers/h5md/__main__.py +31 -0
  44. atomisticparsers/h5md/metainfo/__init__.py +19 -0
  45. atomisticparsers/h5md/metainfo/h5md.py +239 -0
  46. atomisticparsers/h5md/parser.py +901 -0
  47. atomisticparsers/lammps/__init__.py +19 -0
  48. atomisticparsers/lammps/__main__.py +31 -0
  49. atomisticparsers/lammps/metainfo/__init__.py +19 -0
  50. atomisticparsers/lammps/metainfo/lammps.py +1417 -0
  51. atomisticparsers/lammps/parser.py +1753 -0
  52. atomisticparsers/libatoms/__init__.py +19 -0
  53. atomisticparsers/libatoms/__main__.py +31 -0
  54. atomisticparsers/libatoms/metainfo/__init__.py +19 -0
  55. atomisticparsers/libatoms/metainfo/lib_atoms.py +251 -0
  56. atomisticparsers/libatoms/parser.py +38 -0
  57. atomisticparsers/namd/__init__.py +19 -0
  58. atomisticparsers/namd/__main__.py +31 -0
  59. atomisticparsers/namd/metainfo/__init__.py +19 -0
  60. atomisticparsers/namd/metainfo/namd.py +1605 -0
  61. atomisticparsers/namd/parser.py +312 -0
  62. atomisticparsers/tinker/__init__.py +19 -0
  63. atomisticparsers/tinker/__main__.py +31 -0
  64. atomisticparsers/tinker/metainfo/__init__.py +18 -0
  65. atomisticparsers/tinker/metainfo/tinker.py +1363 -0
  66. atomisticparsers/tinker/parser.py +685 -0
  67. atomisticparsers/utils/__init__.py +22 -0
  68. atomisticparsers/utils/mdanalysis.py +662 -0
  69. atomisticparsers/utils/parsers.py +226 -0
  70. atomisticparsers/xtb/__init__.py +19 -0
  71. atomisticparsers/xtb/__main__.py +32 -0
  72. atomisticparsers/xtb/metainfo/__init__.py +19 -0
  73. atomisticparsers/xtb/metainfo/xtb.py +256 -0
  74. atomisticparsers/xtb/parser.py +979 -0
  75. nomad_parser_plugins_atomistic-1.0.dist-info/LICENSE +202 -0
  76. nomad_parser_plugins_atomistic-1.0.dist-info/METADATA +327 -0
  77. nomad_parser_plugins_atomistic-1.0.dist-info/RECORD +80 -0
  78. nomad_parser_plugins_atomistic-1.0.dist-info/WHEEL +5 -0
  79. nomad_parser_plugins_atomistic-1.0.dist-info/entry_points.txt +15 -0
  80. nomad_parser_plugins_atomistic-1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1581 @@
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
+ import os
20
+ import numpy as np
21
+ import logging
22
+ import re
23
+ import datetime
24
+
25
+ import panedr
26
+
27
+ try:
28
+ import MDAnalysis
29
+ from MDAnalysis.topology.tpr import utils as tpr_utils, setting as tpr_setting
30
+ except Exception:
31
+ logging.warning('Required module MDAnalysis not found.')
32
+ MDAnalysis = False
33
+ from ase.symbols import symbols2numbers
34
+ from nomad.units import ureg
35
+ from nomad.parsing.file_parser import TextParser, Quantity, FileParser
36
+ from runschema.run import Run, Program, TimeRun
37
+ from runschema.method import (
38
+ NeighborSearching,
39
+ ForceCalculations,
40
+ Method,
41
+ ForceField,
42
+ Model,
43
+ AtomParameters,
44
+ )
45
+ from runschema.system import AtomsGroup
46
+ from simulationworkflowschema import (
47
+ GeometryOptimization,
48
+ GeometryOptimizationMethod,
49
+ GeometryOptimizationResults,
50
+ )
51
+ from .metainfo.gromacs import (
52
+ x_gromacs_section_control_parameters,
53
+ x_gromacs_section_input_output_files,
54
+ )
55
+ from atomisticparsers.utils import MDAnalysisParser, MDParser
56
+ from simulationworkflowschema.molecular_dynamics import get_bond_list_from_model_contributions
57
+
58
+ re_float = r'[-+]?\d+\.*\d*(?:[Ee][-+]\d+)?'
59
+ re_n = r'[\n\r]'
60
+
61
+ MOL = 6.022140857e23
62
+
63
+
64
+ def to_float(string):
65
+ try:
66
+ value = float(string)
67
+ except ValueError:
68
+ value = None
69
+ return value
70
+
71
+
72
+ class GromacsLogParser(TextParser):
73
+ def __init__(self):
74
+ super().__init__(None)
75
+
76
+ def init_quantities(self):
77
+ def str_to_header(val_in):
78
+ val = [v.split(':', 1) for v in val_in.strip().splitlines()]
79
+ return {v[0].strip(): v[1].strip() for v in val if len(v) == 2}
80
+
81
+ def str_to_input_parameters(val_in):
82
+ re_array = re.compile(r'\s*([\w\-]+)\[[\d ]+\]\s*=\s*\{*(.+)')
83
+ re_scalar = re.compile(r'\s*([\w\-]+)\s*[=:]\s*(.+)')
84
+ parameters = dict()
85
+ val = val_in.strip().splitlines()
86
+ for val_n in val:
87
+ val_scalar = re_scalar.match(val_n)
88
+ if val_scalar:
89
+ parameters[val_scalar.group(1)] = val_scalar.group(2)
90
+ continue
91
+ val_array = re_array.match(val_n)
92
+ if val_array:
93
+ parameters.setdefault(val_array.group(1), [])
94
+ value = [
95
+ float(v) for v in val_array.group(2).rstrip('}').split(',')
96
+ ]
97
+ parameters[val_array.group(1)].append(
98
+ value[0] if len(value) == 1 else value
99
+ )
100
+ return parameters
101
+
102
+ def str_to_energies(val_in):
103
+ thermo_common = [
104
+ r'Total Energy',
105
+ r'Potential',
106
+ r'Kinetic En.',
107
+ r'Temperature',
108
+ r'Pressure \(bar\)',
109
+ r'LJ \(SR\)',
110
+ r'Coulomb \(SR\)',
111
+ r'Proper Dih.',
112
+ ]
113
+ n_chars_val = re.search(rf'( +{"| +".join(thermo_common)})', val_in)
114
+ n_chars_val = len(n_chars_val.group(1)) if n_chars_val is not None else None
115
+ if n_chars_val is None:
116
+ n_chars_val = 15
117
+ energies = {}
118
+ rows = [v for v in val_in.splitlines() if v]
119
+ for n in range(0, len(rows), 2):
120
+ pointer = 0
121
+ while pointer < len(rows[n]):
122
+ key = rows[n][pointer : pointer + n_chars_val].strip()
123
+ value = rows[n + 1][pointer : pointer + n_chars_val]
124
+ energies[key] = to_float(value)
125
+ pointer += n_chars_val
126
+ return energies
127
+
128
+ def str_to_step_info(val_in):
129
+ val = val_in.strip().splitlines()
130
+ keys = val[0].split()
131
+ values = [to_float(v) for v in val[1].split()]
132
+ return {key: values[n] for n, key in enumerate(keys)}
133
+
134
+ thermo_quantities = [
135
+ Quantity(
136
+ 'energies',
137
+ r'Energies \(kJ/mol\).*\n(\s*[\s\S]+?)(?:\n.*step.* load imb.*|\n\n)',
138
+ str_operation=str_to_energies,
139
+ convert=False,
140
+ ),
141
+ Quantity(
142
+ 'step_info',
143
+ rf'{re_n}\s*(Step.+\n[\d\.\- ]+)',
144
+ str_operation=str_to_step_info,
145
+ convert=False,
146
+ ),
147
+ ]
148
+
149
+ self._quantities = [
150
+ Quantity('time_start', r'Log file opened on (.+)', flatten=False),
151
+ Quantity(
152
+ 'host_info',
153
+ r'Host:\s*(\S+)\s*pid:\s*(\d+)\s*rank ID:\s*(\d+)\s*number of ranks:\s*(\d*)',
154
+ ),
155
+ Quantity(
156
+ 'module_version', r'GROMACS:\s*(.+?),\s*VERSION\s*(\S+)', flatten=False
157
+ ),
158
+ Quantity('execution_path', r'Executable:\s*(.+)'),
159
+ Quantity('working_path', r'Data prefix:\s*(.+)'),
160
+ # TODO cannot understand treatment of the command line in the old parser
161
+ Quantity(
162
+ 'header',
163
+ r'(?:GROMACS|Gromacs) (20[\s\S]+?)\n\n',
164
+ str_operation=str_to_header,
165
+ ),
166
+ Quantity(
167
+ 'header',
168
+ r'(?:GROMACS|Gromacs) (version:[\s\S]+?)\n\n',
169
+ str_operation=str_to_header,
170
+ ),
171
+ Quantity(
172
+ 'input_parameters',
173
+ r'Input Parameters:\s*([\s\S]+?)\n\n',
174
+ str_operation=str_to_input_parameters,
175
+ ),
176
+ Quantity('maximum_force', r'Norm of force\s*([\s\S]+?)\n\n', flatten=False),
177
+ Quantity(
178
+ 'step',
179
+ r'(Step\s*Time[\s\S]+?Energies[\s\S]+?\n\n)',
180
+ repeats=True,
181
+ sub_parser=TextParser(quantities=thermo_quantities),
182
+ ),
183
+ Quantity(
184
+ 'averages',
185
+ r'A V E R A G E S ====>([\s\S]+?\n\n\n)',
186
+ sub_parser=TextParser(quantities=thermo_quantities),
187
+ ),
188
+ Quantity('time_end', r'Finished \S+ on rank \d+ (.+)', flatten=False),
189
+ ]
190
+
191
+
192
+ class GromacsMdpParser(TextParser):
193
+ def __init__(self):
194
+ super().__init__(None)
195
+
196
+ def init_quantities(self):
197
+ def str_to_input_parameters(val_in):
198
+ re_array = re.compile(r'\s*([\w\-]+)\[[\d ]+\]\s*=\s*\{*(.+)')
199
+ re_scalar = re.compile(r'\s*([\w\-]+)\s*[=:]\s*(.+)')
200
+ parameters = dict()
201
+ val = [line.strip() for line in val_in.splitlines()]
202
+ for val_n in val:
203
+ val_scalar = re_scalar.match(val_n)
204
+ if val_scalar:
205
+ parameters[val_scalar.group(1)] = val_scalar.group(2)
206
+ continue
207
+ val_array = re_array.match(val_n)
208
+ if val_array:
209
+ parameters.setdefault(val_array.group(1), [])
210
+ value = [
211
+ to_float(v) for v in val_array.group(2).rstrip('}').split(',')
212
+ ]
213
+ parameters[val_array.group(1)].append(
214
+ value[0] if len(value) == 1 else value
215
+ )
216
+ return parameters
217
+
218
+ self._quantities = [
219
+ Quantity(
220
+ 'input_parameters',
221
+ r'([\s\S]+)',
222
+ str_operation=str_to_input_parameters,
223
+ ),
224
+ ]
225
+
226
+
227
+ class GromacsXvgParser(TextParser):
228
+ def __init__(self):
229
+ super().__init__(None)
230
+ self.re_columns = re.compile(r'@\s*s\d{1,2}\s*legend\s*\".*\"')
231
+ self.re_comment = re.compile(r'^[@#]')
232
+ self.re_quotes = re.compile(r'\"(.*)\"')
233
+ self.re_label = re.compile(r'@\s*(title|xaxis|yaxis)\s*(?: label)?\s*"(.*)"')
234
+
235
+ def str_to_results(val_in):
236
+ results = {
237
+ 'column_vals': None,
238
+ 'title': '',
239
+ 'xaxis': '',
240
+ 'yaxis': '',
241
+ 'column_headers': [],
242
+ }
243
+
244
+ val = val_in.strip().splitlines()
245
+ val = [line.strip() for line in val]
246
+ for val_n in val:
247
+ val_label = self.re_label.match(val_n)
248
+ val_legend = self.re_columns.match(val_n)
249
+ val_comment = self.re_comment.match(val_n)
250
+ if val_label:
251
+ key, label = val_label.groups()
252
+ results[key] = label
253
+ elif val_legend: # TODO convert out of xmgrace notation
254
+ column = val_legend.group()
255
+ column = self.re_quotes.findall(column)
256
+ column = column[0] if column else None
257
+ results['column_headers'].append(column)
258
+ elif not val_comment:
259
+ results['column_vals'] = (
260
+ np.vstack((results['column_vals'], [val_n.split()]))
261
+ if results['column_vals'] is not None
262
+ else [val_n.split()]
263
+ )
264
+ return results
265
+
266
+ self._quantities = [
267
+ Quantity(
268
+ 'results',
269
+ r'([\s\S]+)',
270
+ str_operation=str_to_results,
271
+ ),
272
+ ]
273
+
274
+
275
+ class GromacsEDRParser(FileParser):
276
+ def __init__(self):
277
+ super().__init__(None)
278
+
279
+ @property
280
+ def fileedr(self):
281
+ if self._file_handler is None:
282
+ try:
283
+ self._file_handler = panedr.edr_to_df(self.mainfile)
284
+ except Exception:
285
+ self.logger.error('Error reading edr file.')
286
+
287
+ return self._file_handler
288
+
289
+ def parse(self, key):
290
+ if self.fileedr is None:
291
+ return
292
+
293
+ val = self.fileedr.get(key, None)
294
+ if self._results is None:
295
+ self._results = dict()
296
+
297
+ if val is not None:
298
+ val = np.asarray(val)
299
+
300
+ self._results[key] = val
301
+
302
+ def keys(self):
303
+ return list(self.fileedr.keys())
304
+
305
+ @property
306
+ def length(self):
307
+ return self.fileedr.shape[0]
308
+
309
+
310
+ class GromacsMDAnalysisParser(MDAnalysisParser):
311
+ def __init__(self):
312
+ super().__init__(None)
313
+
314
+ def get_interactions(self):
315
+ interactions = super().get_interactions()
316
+
317
+ # add force field parameters
318
+ try:
319
+ interactions.extend(self.get_force_field_parameters())
320
+ except Exception:
321
+ self.logger.error('Error parsing force field parameters.')
322
+
323
+ self._results['interactions'] = interactions
324
+
325
+ return interactions
326
+
327
+ def get_force_field_parameters(self):
328
+ # read force field parameters not saved by MDAnalysis
329
+ # copied from MDAnalysis.topology.tpr.utils
330
+ # TODO maybe a better implementation exists
331
+ if MDAnalysis.__version__ != '2.0.0':
332
+ self.logger.warning('Incompatible version of MDAnalysis.')
333
+
334
+ with open(self.mainfile, 'rb') as f:
335
+ data = tpr_utils.TPXUnpacker(f.read())
336
+
337
+ interactions = []
338
+
339
+ # read header
340
+ header = tpr_utils.read_tpxheader(data)
341
+ # address compatibility issue
342
+ if header.fver >= tpr_setting.tpxv_AddSizeField and header.fgen >= 27:
343
+ actual_body_size = len(data.get_buffer()) - data.get_position()
344
+ if actual_body_size == 4 * header.sizeOfTprBody:
345
+ self.logger.error('Unsupported tpr format.')
346
+ return interactions
347
+ data = tpr_utils.TPXUnpacker2020.from_unpacker(data)
348
+
349
+ # read other unimportant parts
350
+ if header.bBox:
351
+ tpr_utils.extract_box_info(data, header.fver)
352
+ if header.ngtc > 0:
353
+ if header.fver < 69:
354
+ tpr_utils.ndo_real(data, header.ngtc)
355
+ tpr_utils.ndo_real(data, header.ngtc)
356
+ if not header.bTop:
357
+ return interactions
358
+
359
+ tpr_utils.do_symstr(data, tpr_utils.do_symtab(data))
360
+ data.unpack_int()
361
+ ntypes = data.unpack_int()
362
+ # functional types
363
+ functypes = tpr_utils.ndo_int(data, ntypes)
364
+ data.unpack_double() if header.fver >= 66 else 12.0
365
+ data.unpack_real()
366
+ # read the ffparams
367
+ for i in functypes:
368
+ parameters = []
369
+ if i in [
370
+ tpr_setting.F_ANGLES,
371
+ tpr_setting.F_G96ANGLES,
372
+ tpr_setting.F_BONDS,
373
+ tpr_setting.F_G96BONDS,
374
+ tpr_setting.F_HARMONIC,
375
+ tpr_setting.F_IDIHS,
376
+ ]:
377
+ parameters.append(data.unpack_real()) # rA
378
+ parameters.append(data.unpack_real()) # krA
379
+ parameters.append(data.unpack_real()) # rB
380
+ parameters.append(data.unpack_real()) # krB
381
+
382
+ elif i in [tpr_setting.F_RESTRANGLES]:
383
+ parameters.append(data.unpack_real()) # harmonic.rA
384
+ parameters.append(data.unpack_real()) # harmonic.krA
385
+ elif i in [tpr_setting.F_LINEAR_ANGLES]:
386
+ parameters.append(data.unpack_real()) # linangle.klinA
387
+ parameters.append(data.unpack_real()) # linangle.aA
388
+ parameters.append(data.unpack_real()) # linangle.klinB
389
+ parameters.append(data.unpack_real()) # linangle.aB);
390
+ elif i in [tpr_setting.F_FENEBONDS]:
391
+ parameters.append(data.unpack_real()) # fene.bm
392
+ parameters.append(data.unpack_real()) # fene.kb
393
+ elif i in [tpr_setting.F_RESTRBONDS]:
394
+ parameters.append(data.unpack_real()) # restraint.lowA
395
+ parameters.append(data.unpack_real()) # restraint.up1A
396
+ parameters.append(data.unpack_real()) # restraint.up2A
397
+ parameters.append(data.unpack_real()) # restraint.kA
398
+ parameters.append(data.unpack_real()) # restraint.lowB
399
+ parameters.append(data.unpack_real()) # restraint.up1B
400
+ parameters.append(data.unpack_real()) # restraint.up2B
401
+ parameters.append(data.unpack_real()) # restraint.kB
402
+ elif i in [
403
+ tpr_setting.F_TABBONDS,
404
+ tpr_setting.F_TABBONDSNC,
405
+ tpr_setting.F_TABANGLES,
406
+ tpr_setting.F_TABDIHS,
407
+ ]:
408
+ parameters.append(data.unpack_real()) # tab.kA
409
+ parameters.append(data.unpack_int()) # tab.table
410
+ parameters.append(data.unpack_real()) # tab.kB
411
+ elif i in [tpr_setting.F_CROSS_BOND_BONDS]:
412
+ parameters.append(data.unpack_real()) # cross_bb.r1e
413
+ parameters.append(data.unpack_real()) # cross_bb.r2e
414
+ parameters.append(data.unpack_real()) # cross_bb.krr
415
+ elif i in [tpr_setting.F_CROSS_BOND_ANGLES]:
416
+ parameters.append(data.unpack_real()) # cross_ba.r1e
417
+ parameters.append(data.unpack_real()) # cross_ba.r2e
418
+ parameters.append(data.unpack_real()) # cross_ba.r3e
419
+ parameters.append(data.unpack_real()) # cross_ba.krt
420
+ elif i in [tpr_setting.F_UREY_BRADLEY]:
421
+ parameters.append(data.unpack_real()) # u_b.theta
422
+ parameters.append(data.unpack_real()) # u_b.ktheta
423
+ parameters.append(data.unpack_real()) # u_b.r13
424
+ parameters.append(data.unpack_real()) # u_b.kUB
425
+ if header.fver >= 79:
426
+ parameters.append(data.unpack_real()) # u_b.thetaB
427
+ parameters.append(data.unpack_real()) # u_b.kthetaB
428
+ parameters.append(data.unpack_real()) # u_b.r13B
429
+ parameters.append(data.unpack_real()) # u_b.kUBB
430
+ elif i in [tpr_setting.F_QUARTIC_ANGLES]:
431
+ parameters.append(data.unpack_real()) # qangle.theta
432
+ parameters.append(tpr_utils.ndo_real(data, 5)) # qangle.c
433
+ elif i in [tpr_setting.F_BHAM]:
434
+ parameters.append(data.unpack_real()) # bham.a
435
+ parameters.append(data.unpack_real()) # bham.b
436
+ parameters.append(data.unpack_real()) # bham.c
437
+ elif i in [tpr_setting.F_MORSE]:
438
+ parameters.append(data.unpack_real()) # morse.b0
439
+ parameters.append(data.unpack_real()) # morse.cb
440
+ parameters.append(data.unpack_real()) # morse.beta
441
+ if header.fver >= 79:
442
+ parameters.append(data.unpack_real()) # morse.b0B
443
+ parameters.append(data.unpack_real()) # morse.cbB
444
+ parameters.append(data.unpack_real()) # morse.betaB
445
+ elif i in [tpr_setting.F_CUBICBONDS]:
446
+ parameters.append(data.unpack_real()) # cubic.b0g
447
+ parameters.append(data.unpack_real()) # cubic.kb
448
+ parameters.append(data.unpack_real()) # cubic.kcub
449
+ elif i in [tpr_setting.F_CONNBONDS]:
450
+ pass
451
+ elif i in [tpr_setting.F_POLARIZATION]:
452
+ parameters.append(data.unpack_real()) # polarize.alpha
453
+ elif i in [tpr_setting.F_ANHARM_POL]:
454
+ parameters.append(data.unpack_real()) # anharm_polarize.alpha
455
+ parameters.append(data.unpack_real()) # anharm_polarize.drcut
456
+ parameters.append(data.unpack_real()) # anharm_polarize.khyp
457
+ elif i in [tpr_setting.F_WATER_POL]:
458
+ parameters.append(data.unpack_real()) # wpol.al_x
459
+ parameters.append(data.unpack_real()) # wpol.al_y
460
+ parameters.append(data.unpack_real()) # wpol.al_z
461
+ parameters.append(data.unpack_real()) # wpol.rOH
462
+ parameters.append(data.unpack_real()) # wpol.rHH
463
+ parameters.append(data.unpack_real()) # wpol.rOD
464
+ elif i in [tpr_setting.F_THOLE_POL]:
465
+ parameters.append(data.unpack_real()) # thole.a
466
+ parameters.append(data.unpack_real()) # thole.alpha1
467
+ parameters.append(data.unpack_real()) # thole.alpha2
468
+ parameters.append(data.unpack_real()) # thole.rfac
469
+
470
+ elif i in [tpr_setting.F_LJ]:
471
+ parameters.append(data.unpack_real()) # lj_c6
472
+ parameters.append(data.unpack_real()) # lj_c9
473
+ elif i in [tpr_setting.F_LJ14]:
474
+ parameters.append(data.unpack_real()) # lj14_c6A
475
+ parameters.append(data.unpack_real()) # lj14_c12A
476
+ parameters.append(data.unpack_real()) # lj14_c6B
477
+ parameters.append(data.unpack_real()) # lj14_c12B
478
+ elif i in [tpr_setting.F_LJC14_Q]:
479
+ parameters.append(data.unpack_real()) # ljc14.fqq
480
+ parameters.append(data.unpack_real()) # ljc14.qi
481
+ parameters.append(data.unpack_real()) # ljc14.qj
482
+ parameters.append(data.unpack_real()) # ljc14.c6
483
+ parameters.append(data.unpack_real()) # ljc14.c12
484
+ elif i in [tpr_setting.F_LJC_PAIRS_NB]:
485
+ parameters.append(data.unpack_real()) # ljcnb.qi
486
+ parameters.append(data.unpack_real()) # ljcnb.qj
487
+ parameters.append(data.unpack_real()) # ljcnb.c6
488
+ parameters.append(data.unpack_real()) # ljcnb.c12
489
+
490
+ elif i in [
491
+ tpr_setting.F_PIDIHS,
492
+ tpr_setting.F_ANGRES,
493
+ tpr_setting.F_ANGRESZ,
494
+ tpr_setting.F_PDIHS,
495
+ ]:
496
+ parameters.append(data.unpack_real()) # pdihs_phiA
497
+ parameters.append(data.unpack_real()) # pdihs_cpA
498
+ parameters.append(data.unpack_real()) # pdihs_phiB
499
+ parameters.append(data.unpack_real()) # pdihs_cpB
500
+ parameters.append(data.unpack_int()) # pdihs_mult
501
+
502
+ elif i in [tpr_setting.F_RESTRDIHS]:
503
+ parameters.append(data.unpack_real()) # pdihs.phiA
504
+ parameters.append(data.unpack_real()) # pdihs.cpA
505
+ elif i in [tpr_setting.F_DISRES]:
506
+ parameters.append(data.unpack_int()) # disres.label
507
+ parameters.append(data.unpack_int()) # disres.type
508
+ parameters.append(data.unpack_real()) # disres.low
509
+ parameters.append(data.unpack_real()) # disres.up1
510
+ parameters.append(data.unpack_real()) # disres.up2
511
+ parameters.append(data.unpack_real()) # disres.kfac
512
+
513
+ elif i in [tpr_setting.F_ORIRES]:
514
+ parameters.append(data.unpack_int()) # orires.ex
515
+ parameters.append(data.unpack_int()) # orires.label
516
+ parameters.append(data.unpack_int()) # orires.power
517
+ parameters.append(data.unpack_real()) # orires.c
518
+ parameters.append(data.unpack_real()) # orires.obs
519
+ parameters.append(data.unpack_real()) # orires.kfac
520
+
521
+ elif i in [tpr_setting.F_DIHRES]:
522
+ if header.fver < 72:
523
+ parameters.append(data.unpack_int()) # idum
524
+ parameters.append(data.unpack_int()) # idum
525
+ parameters.append(data.unpack_real()) # dihres.phiA
526
+ parameters.append(data.unpack_real()) # dihres.dphiA
527
+ parameters.append(data.unpack_real()) # dihres.kfacA
528
+ if header.fver >= 72:
529
+ parameters.append(data.unpack_real()) # dihres.phiB
530
+ parameters.append(data.unpack_real()) # dihres.dphiB
531
+ parameters.append(data.unpack_real()) # dihres.kfacB
532
+
533
+ elif i in [tpr_setting.F_POSRES]:
534
+ parameters.append(tpr_utils.do_rvec(data)) # posres.pos0A
535
+ parameters.append(tpr_utils.do_rvec(data)) # posres.fcA
536
+ parameters.append(tpr_utils.do_rvec(data)) # posres.pos0B
537
+ parameters.append(tpr_utils.do_rvec(data)) # posres.fcB
538
+
539
+ elif i in [tpr_setting.F_FBPOSRES]:
540
+ parameters.append(data.unpack_int()) # fbposres.geom
541
+ parameters.append(tpr_utils.do_rvec(data)) # fbposres.pos0
542
+ parameters.append(data.unpack_real()) # fbposres.r
543
+ parameters.append(data.unpack_real()) # fbposres.k
544
+
545
+ elif i in [tpr_setting.F_CBTDIHS]:
546
+ parameters.append(
547
+ tpr_utils.ndo_real(data, tpr_setting.NR_CBTDIHS)
548
+ ) # cbtdihs.cbtcA
549
+
550
+ elif i in [tpr_setting.F_RBDIHS]:
551
+ parameters.append(
552
+ tpr_utils.ndo_real(data, tpr_setting.NR_RBDIHS)
553
+ ) # iparams_rbdihs_rbcA
554
+ parameters.append(
555
+ tpr_utils.ndo_real(data, tpr_setting.NR_RBDIHS)
556
+ ) # iparams_rbdihs_rbcB
557
+
558
+ elif i in [tpr_setting.F_FOURDIHS]:
559
+ # Fourier dihedrals
560
+ parameters.append(
561
+ tpr_utils.ndo_real(data, tpr_setting.NR_RBDIHS)
562
+ ) # rbdihs.rbcA
563
+ parameters.append(
564
+ tpr_utils.ndo_real(data, tpr_setting.NR_RBDIHS)
565
+ ) # rbdihs.rbcB
566
+
567
+ elif i in [tpr_setting.F_CONSTR, tpr_setting.F_CONSTRNC]:
568
+ parameters.append(data.unpack_real()) # dA
569
+ parameters.append(data.unpack_real()) # dB
570
+
571
+ elif i in [tpr_setting.F_SETTLE]:
572
+ parameters.append(data.unpack_real()) # settle.doh
573
+ parameters.append(data.unpack_real()) # settle.dhh
574
+
575
+ elif i in [tpr_setting.F_VSITE1]:
576
+ pass
577
+
578
+ elif i in [tpr_setting.F_VSITE2, tpr_setting.F_VSITE2FD]:
579
+ parameters.append(data.unpack_real()) # vsite.a
580
+
581
+ elif i in [
582
+ tpr_setting.F_VSITE3,
583
+ tpr_setting.F_VSITE3FD,
584
+ tpr_setting.F_VSITE3FAD,
585
+ ]:
586
+ parameters.append(data.unpack_real()) # vsite.a
587
+
588
+ elif i in [
589
+ tpr_setting.F_VSITE3OUT,
590
+ tpr_setting.F_VSITE4FD,
591
+ tpr_setting.F_VSITE4FDN,
592
+ ]:
593
+ parameters.append(data.unpack_real()) # vsite.a
594
+ parameters.append(data.unpack_real()) # vsite.b
595
+ parameters.append(data.unpack_real()) # vsite.c
596
+
597
+ elif i in [tpr_setting.F_VSITEN]:
598
+ parameters.append(data.unpack_int()) # vsiten.n
599
+ parameters.append(data.unpack_real()) # vsiten.a
600
+
601
+ elif i in [tpr_setting.F_GB12, tpr_setting.F_GB13, tpr_setting.F_GB14]:
602
+ # /* We got rid of some parameters in version 68 */
603
+ if header.fver < 68:
604
+ parameters.append(data.unpack_real()) # rdum
605
+ parameters.append(data.unpack_real()) # rdum
606
+ parameters.append(data.unpack_real()) # rdum
607
+ parameters.append(data.unpack_real()) # rdum
608
+ parameters.append(data.unpack_real()) # gb.sar
609
+ parameters.append(data.unpack_real()) # gb.st
610
+ parameters.append(data.unpack_real()) # gb.pi
611
+ parameters.append(data.unpack_real()) # gb.gbr
612
+ parameters.append(data.unpack_real()) # gb.bmlt
613
+
614
+ elif i in [tpr_setting.F_CMAP]:
615
+ parameters.append(data.unpack_int()) # cmap.cmapA
616
+ parameters.append(data.unpack_int()) # cmap.cmapB
617
+ else:
618
+ raise NotImplementedError(f'unknown functype: {i}')
619
+ interactions.append(
620
+ dict(type=tpr_setting.interaction_types[i][1], parameters=parameters)
621
+ )
622
+
623
+ return interactions
624
+
625
+
626
+ class GromacsParser(MDParser):
627
+ def __init__(self):
628
+ self.log_parser = GromacsLogParser()
629
+ self.traj_parser = GromacsMDAnalysisParser()
630
+ self.energy_parser = GromacsEDRParser()
631
+ self.mdp_parser = GromacsMdpParser()
632
+ self.mdp_ext = 'mdp'
633
+ self.mdp_std_filename = 'mdout'
634
+ self.xvg_parser = GromacsXvgParser()
635
+ self.input_parameters = {}
636
+ self._gro_energy_units = ureg.kilojoule / MOL
637
+ self._thermo_ignore_list = ['Time', 'Box-X', 'Box-Y', 'Box-Z']
638
+ self._base_calc_map = {
639
+ 'Temperature': ('temperature', ureg.kelvin),
640
+ 'Volume': ('volume', ureg.nm**3),
641
+ 'Density': ('density', ureg.kilogram / ureg.m**3),
642
+ 'Pressure (bar)': ('pressure', ureg.bar),
643
+ 'Pressure': ('pressure', ureg.bar),
644
+ 'Enthalpy': ('enthalpy', self._gro_energy_units),
645
+ }
646
+ self._energy_map = {
647
+ 'Potential': 'potential',
648
+ 'Kinetic En.': 'kinetic',
649
+ 'Total Energy': 'total',
650
+ 'pV': 'pressure_volume_work',
651
+ }
652
+ self._vdw_map = {
653
+ 'LJ (SR)': 'short_range',
654
+ 'LJ (LR)': 'long_range',
655
+ 'Disper. corr.': 'correction',
656
+ }
657
+ self._electrostatic_map = {
658
+ 'Coulomb (SR)': 'short_range',
659
+ 'Coul. recip.': 'long_range',
660
+ }
661
+ self._energy_keys_contain = [
662
+ 'bond',
663
+ 'angle',
664
+ 'dih.',
665
+ 'coul-',
666
+ 'coulomb-',
667
+ 'lj-',
668
+ 'en.',
669
+ ]
670
+ super().__init__()
671
+
672
+ def get_pbc(self):
673
+ pbc = self.input_parameters.get('pbc', 'xyz')
674
+ return ['x' in pbc, 'y' in pbc, 'z' in pbc]
675
+
676
+ def get_mdp_file(self):
677
+ """
678
+ Tries to find the mdp input parameters (ext = mdp) that match the mainfile calculation.
679
+ Priority is as follows:
680
+ 1. output mdp file containing both the matching mainfile name and the standard
681
+ gromacs name `mdout`
682
+ 2. file containing the standard gromacs name `mdout`
683
+ 3. input mdp file matching the mainfile name (as usual)
684
+ 4. any `.mdp` file within the directory (as usual)
685
+ """
686
+ files = [d for d in self._gromacs_files if d.endswith(self.mdp_ext)]
687
+
688
+ if len(files) == 0:
689
+ return ''
690
+
691
+ if len(files) == 1:
692
+ return os.path.join(self._maindir, files[0])
693
+
694
+ for f in files:
695
+ filename = f.rsplit('.', 1)[0]
696
+ if self._basename in filename and self.mdp_std_filename in filename:
697
+ return os.path.join(self._maindir, f)
698
+
699
+ for f in files:
700
+ filename = f.rsplit('.', 1)[0]
701
+ if self.mdp_std_filename in filename:
702
+ return os.path.join(self._maindir, f)
703
+
704
+ return self.get_gromacs_file(self.mdp_ext)
705
+
706
+ def get_gromacs_file(self, ext):
707
+ files = [d for d in self._gromacs_files if d.endswith(ext)]
708
+
709
+ if len(files) == 0:
710
+ return ''
711
+
712
+ if len(files) == 1:
713
+ return os.path.join(self._maindir, files[0])
714
+
715
+ # we assume that the file has the same basename as the log file e.g.
716
+ # out.log would correspond to out.tpr and out.trr and out.edr
717
+ for f in files:
718
+ if f.rsplit('.', 1)[0] == self._basename:
719
+ return os.path.join(self._maindir, f)
720
+
721
+ for f in files:
722
+ if f.rsplit('.', 1)[0].startswith(self._basename):
723
+ return os.path.join(self._maindir, f)
724
+
725
+ # if the files are all named differently, we guess that the one that does not
726
+ # share the same basename would be file we are interested in
727
+ # e.g. in a list of files out.log someout.log out.tpr out.trr another.tpr file.trr
728
+ # we guess that the out.* files belong together and the rest that does not share
729
+ # a basename would be grouped together
730
+ counts = []
731
+ for f in files:
732
+ count = 0
733
+ for reff in self._gromacs_files:
734
+ if f.rsplit('.', 1)[0] == reff.rsplit('.', 1)[0]:
735
+ count += 1
736
+ if count == 1:
737
+ return os.path.join(self._maindir, f)
738
+ counts.append(count)
739
+
740
+ return os.path.join(self._maindir, files[counts.index(min(counts))])
741
+
742
+ def parse_thermodynamic_data(self):
743
+ sec_run = self.archive.run[-1]
744
+
745
+ n_frames = self.traj_parser.get('n_frames')
746
+
747
+ # # TODO read also from ene
748
+ edr_file = self.get_gromacs_file('edr')
749
+ self.energy_parser.mainfile = edr_file
750
+
751
+ # get it from edr file
752
+ if self.energy_parser.keys():
753
+ thermo_data = self.energy_parser
754
+ else:
755
+ # try to get it from log file
756
+ steps = self.input_parameters.get('step', [])
757
+ thermo_data = dict()
758
+ for n, step in enumerate(steps):
759
+ n = int(step.get('step_info', {}).get('Step', n))
760
+ if step.energies is None:
761
+ continue
762
+ keys = step.energies.keys()
763
+ for key in keys:
764
+ thermo_data.setdefault(key, [None] * n_frames)
765
+ thermo_data[key][n] = step.energies.get(key)
766
+ info = step.get('step_info', {})
767
+ thermo_data.setdefault('Time', [None] * n_frames)
768
+ thermo_data['Time'][n] = info.get('Time', None)
769
+
770
+ if not thermo_data:
771
+ # get it from edr file
772
+ thermo_data = self.energy_parser
773
+
774
+ calculation_times = thermo_data.get('Time', [])
775
+ time_step = self.input_parameters.get('dt')
776
+ if time_step is None and len(calculation_times) > 1:
777
+ time_step = calculation_times[1] - calculation_times[0]
778
+ self.thermodynamics_steps = [
779
+ int(time / time_step if time_step else 1) for time in calculation_times
780
+ ]
781
+
782
+ for n, step in enumerate(self.thermodynamics_steps):
783
+ data = {
784
+ 'step': step,
785
+ 'time': calculation_times[n] * ureg.picosecond,
786
+ 'method_ref': sec_run.method[-1] if sec_run.method else None,
787
+ 'energy': {},
788
+ }
789
+ if step in self._trajectory_steps:
790
+ data['forces'] = dict(
791
+ total=dict(
792
+ value=self.traj_parser.get_forces(
793
+ self._trajectory_steps.index(step)
794
+ )
795
+ )
796
+ )
797
+
798
+ pressure_tensor, virial_tensor = None, None
799
+ for key in thermo_data.keys():
800
+ if (
801
+ key in self._thermo_ignore_list
802
+ or (val := thermo_data.get(key)[n]) is None
803
+ ):
804
+ continue
805
+
806
+ # Attributes of BaseCalculation
807
+ if key in self._base_calc_map:
808
+ data[self._base_calc_map[key][0]] = (
809
+ val * self._base_calc_map[key][1]
810
+ )
811
+
812
+ # pressure tensor
813
+ elif match := re.match(r'Pres-([XYZ]{2})', key):
814
+ if pressure_tensor is None:
815
+ pressure_tensor = np.zeros(shape=(3, 3))
816
+ pressure_tensor[tuple('XYZ'.index(n) for n in match.group(1))] = val
817
+
818
+ # virial tensor
819
+ elif match := re.match(r'Vir-([XYZ]{2})', key):
820
+ if virial_tensor is None:
821
+ virial_tensor = np.zeros(shape=(3, 3))
822
+ virial_tensor[tuple('XYZ'.index(n) for n in match.group(1))] = val
823
+
824
+ # well-defined, single Energy quantities
825
+ elif (nomad_key := self._energy_map.get(key)) is not None:
826
+ data['energy'][nomad_key] = dict(value=val * self._gro_energy_units)
827
+ # well-defined, piecewise energy quantities
828
+ elif (nomad_key := self._vdw_map.get(key)) is not None:
829
+ data['energy'].setdefault(
830
+ 'van_der_waals', {'value': 0.0 * self._gro_energy_units}
831
+ )
832
+ data['energy']['van_der_waals'][nomad_key] = (
833
+ val * self._gro_energy_units
834
+ )
835
+ data['energy']['van_der_waals']['value'] += (
836
+ val * self._gro_energy_units
837
+ )
838
+ elif (nomad_key := self._electrostatic_map.get(key)) is not None:
839
+ data['energy'].setdefault(
840
+ 'electrostatic', {'value': 0.0 * self._gro_energy_units}
841
+ )
842
+ data['energy']['electrostatic'][nomad_key] = (
843
+ val * self._gro_energy_units
844
+ )
845
+ data['energy']['electrostatic']['value'] += (
846
+ val * self._gro_energy_units
847
+ )
848
+ # try to identify other known energy keys to be stored as gromacs-specific
849
+ elif any(
850
+ keyword in key.lower() for keyword in self._energy_keys_contain
851
+ ):
852
+ data['energy'].setdefault('x_gromacs_energy_contributions', [])
853
+ data['energy']['x_gromacs_energy_contributions'].append(
854
+ dict(kind=key, value=val * self._gro_energy_units)
855
+ )
856
+ else: # store all other quantities as gromacs-specific under BaseCalculation
857
+ data.setdefault('x_gromacs_thermodynamics_contributions', [])
858
+ data['x_gromacs_thermodynamics_contributions'].append(
859
+ dict(kind=key, value=val)
860
+ )
861
+
862
+ if pressure_tensor is not None:
863
+ data['pressure_tensor'] = pressure_tensor * ureg.bar
864
+
865
+ if virial_tensor is not None:
866
+ data['virial_tensor'] = virial_tensor * (ureg.bar * ureg.nm**3)
867
+
868
+ self.parse_thermodynamics_step(data)
869
+
870
+ def parse_system(self):
871
+ sec_run = self.archive.run[-1]
872
+
873
+ def get_composition(children_names):
874
+ children_count_tup = np.unique(children_names, return_counts=True)
875
+ formula = ''.join(
876
+ [f'{name}({count})' for name, count in zip(*children_count_tup)]
877
+ )
878
+ return formula
879
+
880
+ n_frames = self.traj_parser.get('n_frames', 0)
881
+ traj_sampling_rate = self.input_parameters.get('nstxout', 1)
882
+ self.n_atoms = [self.traj_parser.get_n_atoms(n) for n in range(n_frames)]
883
+ traj_steps = [n * traj_sampling_rate for n in range(n_frames)]
884
+ self.trajectory_steps = traj_steps
885
+
886
+ pbc = self.get_pbc()
887
+ self._system_time_map = {}
888
+ for step in self.trajectory_steps:
889
+ n = traj_steps.index(step)
890
+ positions = self.traj_parser.get_positions(n)
891
+ if positions is None:
892
+ continue
893
+
894
+ bond_list = []
895
+ if n == 0: # TODO add references to the bond list for other steps
896
+ bond_list = get_bond_list_from_model_contributions(
897
+ sec_run, method_index=-1, model_index=-1
898
+ )
899
+
900
+ atom_labels = self.traj_parser.get_atom_labels(n)
901
+ if atom_labels is not None:
902
+ try:
903
+ symbols2numbers(atom_labels)
904
+ except KeyError:
905
+ atom_labels = ['X'] * len(atom_labels)
906
+
907
+ self.parse_trajectory_step(
908
+ {
909
+ 'atoms': {
910
+ 'n_atoms': self.traj_parser.get_n_atoms(n),
911
+ 'periodic': pbc,
912
+ 'lattice_vectors': self.traj_parser.get_lattice_vectors(n),
913
+ 'labels': atom_labels,
914
+ 'positions': positions,
915
+ 'velocities': self.traj_parser.get_velocities(n),
916
+ 'bond_list': bond_list if bond_list else None,
917
+ }
918
+ }
919
+ )
920
+
921
+ if not sec_run.system:
922
+ return
923
+
924
+ # parse atomsgroup (segments --> molecules --> residues)
925
+ atoms_info = self.traj_parser._results['atoms_info']
926
+ atoms_moltypes = np.array(atoms_info['moltypes'])
927
+ atoms_molnums = np.array(atoms_info['molnums'])
928
+ atoms_resids = np.array(atoms_info['resids'])
929
+ atoms_elements = np.array(atoms_info['elements'])
930
+ atoms_resnames = np.array(atoms_info['resnames'])
931
+ for segment in self.traj_parser.universe.segments:
932
+ # we only create atomsgroup in the initial system
933
+ sec_segment = AtomsGroup()
934
+ sec_run.system[0].atoms_group.append(sec_segment)
935
+ sec_segment.type = 'molecule_group'
936
+ sec_segment.index = int(segment.segindex)
937
+ sec_segment.atom_indices = segment.atoms.ix
938
+ sec_segment.n_atoms = len(sec_segment.atom_indices)
939
+ sec_segment.is_molecule = False
940
+
941
+ moltypes = np.unique(atoms_moltypes[sec_segment.atom_indices])
942
+ moltypes_count = {}
943
+ for moltype in moltypes:
944
+ atom_indices = np.where(atoms_moltypes == moltype)[0]
945
+ # mol_nums is the molecule identifier for each atom
946
+ mol_nums = atoms_molnums[atom_indices]
947
+ moltypes_count[moltype] = np.unique(mol_nums).shape[0]
948
+ formula = ''.join(
949
+ [f'{moltype}({moltypes_count[moltype]})' for moltype in moltypes_count]
950
+ )
951
+ sec_segment.composition_formula = formula
952
+ sec_segment.label = f'group_{moltypes[0]}'
953
+
954
+ for i_molecule, molecule in enumerate(
955
+ np.unique(atoms_molnums[sec_segment.atom_indices])
956
+ ):
957
+ sec_molecule = AtomsGroup()
958
+ sec_segment.atoms_group.append(sec_molecule)
959
+ sec_molecule.index = i_molecule
960
+ sec_molecule.atom_indices = np.where(atoms_molnums == molecule)[0]
961
+ sec_molecule.n_atoms = len(sec_molecule.atom_indices)
962
+ # use first particle to get the moltype
963
+ # not sure why but this value is being cast to int, cast back to str
964
+ sec_molecule.label = str(atoms_moltypes[sec_molecule.atom_indices[0]])
965
+ sec_molecule.type = 'molecule'
966
+ sec_molecule.is_molecule = True
967
+
968
+ mol_resids = np.unique(atoms_resids[sec_molecule.atom_indices])
969
+ n_res = mol_resids.shape[0]
970
+ if n_res == 1:
971
+ elements = atoms_elements[sec_molecule.atom_indices]
972
+ sec_molecule.composition_formula = get_composition(elements)
973
+ else:
974
+ mol_resnames = atoms_resnames[sec_molecule.atom_indices]
975
+ restypes = np.unique(mol_resnames)
976
+ for i_restype, restype in enumerate(restypes):
977
+ sec_monomer_group = AtomsGroup()
978
+ sec_molecule.atoms_group.append(sec_monomer_group)
979
+ restype_indices = np.where(atoms_resnames == restype)[0]
980
+ sec_monomer_group.label = f'group_{restype}'
981
+ sec_monomer_group.type = 'monomer_group'
982
+ sec_monomer_group.index = i_restype
983
+ sec_monomer_group.atom_indices = np.intersect1d(
984
+ restype_indices, sec_molecule.atom_indices
985
+ )
986
+ sec_monomer_group.n_atoms = len(sec_monomer_group.atom_indices)
987
+ sec_monomer_group.is_molecule = False
988
+
989
+ restype_resids = np.unique(
990
+ atoms_resids[sec_monomer_group.atom_indices]
991
+ )
992
+ restype_count = restype_resids.shape[0]
993
+ sec_monomer_group.composition_formula = (
994
+ f'{restype}({restype_count})'
995
+ )
996
+ for i_res, res_id in enumerate(restype_resids):
997
+ sec_residue = AtomsGroup()
998
+ sec_monomer_group.atoms_group.append(sec_residue)
999
+ sec_residue.index = i_res
1000
+ atom_indices = np.where(atoms_resids == res_id)[0]
1001
+ sec_residue.atom_indices = np.intersect1d(
1002
+ atom_indices, sec_monomer_group.atom_indices
1003
+ )
1004
+ sec_residue.n_atoms = len(sec_residue.atom_indices)
1005
+ sec_residue.label = str(restype)
1006
+ sec_residue.type = 'monomer'
1007
+ sec_residue.is_molecule = False
1008
+ elements = atoms_elements[sec_residue.atom_indices]
1009
+ sec_residue.composition_formula = get_composition(elements)
1010
+
1011
+ names = atoms_resnames[sec_molecule.atom_indices]
1012
+ ids = atoms_resids[sec_molecule.atom_indices]
1013
+ # filter for the first instance of each residue, as to not overcount
1014
+ __, ids_count = np.unique(ids, return_counts=True)
1015
+ # get the index of the first atom of each residue
1016
+ ids_firstatom = np.cumsum(ids_count)[:-1]
1017
+ # add the 0th index manually
1018
+ ids_firstatom = np.insert(ids_firstatom, 0, 0)
1019
+ names_firstatom = names[ids_firstatom]
1020
+ sec_molecule.composition_formula = get_composition(names_firstatom)
1021
+
1022
+ def parse_method(self):
1023
+ sec_method = Method()
1024
+ self.archive.run[-1].method.append(sec_method)
1025
+ sec_force_field = ForceField()
1026
+ sec_method.force_field = sec_force_field
1027
+ sec_model = Model()
1028
+ sec_force_field.model.append(sec_model)
1029
+ try:
1030
+ n_atoms = self.traj_parser.get('n_atoms', 0)
1031
+ except Exception:
1032
+ gro_file = self.get_gromacs_file('gro')
1033
+ self.traj_parser.mainfile = gro_file
1034
+ n_atoms = self.traj_parser.get('n_atoms', 0)
1035
+
1036
+ atoms_info = self.traj_parser.get('atoms_info', {})
1037
+ for n in range(n_atoms):
1038
+ sec_atom = AtomParameters()
1039
+ sec_method.atom_parameters.append(sec_atom)
1040
+ sec_atom.charge = atoms_info.get('charges', [None] * (n + 1))[n]
1041
+ sec_atom.mass = atoms_info.get('masses', [None] * (n + 1))[n]
1042
+ sec_atom.label = atoms_info.get('names', [None] * (n + 1))[n]
1043
+ sec_atom.x_gromacs_atom_name = atoms_info.get(
1044
+ 'atom_names', [None] * (n + 1)
1045
+ )[n]
1046
+ sec_atom.x_gromacs_atom_resid = atoms_info.get('resids', [None] * (n + 1))[
1047
+ n
1048
+ ]
1049
+ sec_atom.x_gromacs_atom_resname = atoms_info.get(
1050
+ 'resnames', [None] * (n + 1)
1051
+ )[n]
1052
+ sec_atom.x_gromacs_atom_molnum = atoms_info.get(
1053
+ 'molnums', [None] * (n + 1)
1054
+ )[n]
1055
+ sec_atom.x_gromacs_atom_moltype = atoms_info.get(
1056
+ 'moltypes', [None] * (n + 1)
1057
+ )[n]
1058
+
1059
+ if n_atoms == 0:
1060
+ self.logger.error('Error parsing interactions.')
1061
+
1062
+ interactions = self.traj_parser.get_interactions()
1063
+ self.parse_interactions(interactions, sec_model)
1064
+
1065
+ input_parameters = self.input_parameters
1066
+ sec_force_calculations = ForceCalculations()
1067
+ sec_force_field.force_calculations = sec_force_calculations
1068
+ sec_neighbor_searching = NeighborSearching()
1069
+ sec_force_calculations.neighbor_searching = sec_neighbor_searching
1070
+
1071
+ nstlist = input_parameters.get('nstlist', None)
1072
+ sec_neighbor_searching.neighbor_update_frequency = (
1073
+ int(nstlist) if nstlist else None
1074
+ )
1075
+ rlist = to_float(input_parameters.get('rlist', None))
1076
+ sec_neighbor_searching.neighbor_update_cutoff = (
1077
+ rlist * ureg.nanometer if rlist else None
1078
+ )
1079
+ rvdw = to_float(input_parameters.get('rvdw', None))
1080
+ sec_force_calculations.vdw_cutoff = rvdw * ureg.nanometer if rvdw else None
1081
+ coulombtype = input_parameters.get('coulombtype', 'no').lower()
1082
+ coulombtype_map = {
1083
+ 'cut-off': 'cutoff',
1084
+ 'ewald': 'ewald',
1085
+ 'pme': 'particle_mesh_ewald',
1086
+ 'p3m-ad': 'particle_particle_particle_mesh',
1087
+ 'reaction-field': 'reaction_field',
1088
+ 'shift': 'cutoff',
1089
+ 'switch': 'cutoff',
1090
+ 'user': 'cutoff',
1091
+ }
1092
+ value = coulombtype_map.get(
1093
+ coulombtype,
1094
+ [val for key, val in coulombtype_map.items() if key in coulombtype],
1095
+ )
1096
+ value = (
1097
+ value
1098
+ if not isinstance(value, list)
1099
+ else value[0]
1100
+ if len(value) != 0
1101
+ else None
1102
+ )
1103
+ sec_force_calculations.coulomb_type = value
1104
+ rcoulomb = input_parameters.get('rcoulomb', None)
1105
+ sec_force_calculations.coulomb_cutoff = to_float(rcoulomb)
1106
+
1107
+ def get_thermostat_parameters(self, integrator: str = ''):
1108
+ thermostat = self.input_parameters.get('tcoupl', 'no').lower()
1109
+ thermostat_map = {
1110
+ 'berendsen': 'berendsen',
1111
+ 'v-rescale': 'velocity_rescaling',
1112
+ 'nose-hoover': 'nose_hoover',
1113
+ 'andersen': 'andersen',
1114
+ }
1115
+ value = thermostat_map.get(
1116
+ thermostat,
1117
+ [val for key, val in thermostat_map.items() if key in thermostat],
1118
+ )
1119
+ value = (
1120
+ value
1121
+ if not isinstance(value, list)
1122
+ else value[0]
1123
+ if len(value) != 0
1124
+ else None
1125
+ )
1126
+ thermostat_parameters = {}
1127
+ thermostat_parameters['thermostat_type'] = value
1128
+ if 'sd' in integrator:
1129
+ thermostat_parameters['thermostat_type'] = 'langevin_goga'
1130
+ if thermostat_parameters['thermostat_type']:
1131
+ reference_temperature = self.input_parameters.get('ref-t', None)
1132
+ if isinstance(reference_temperature, str):
1133
+ reference_temperature = to_float(
1134
+ reference_temperature.split()[0]
1135
+ ) # ! simulated annealing protocols not supported
1136
+ reference_temperature *= ureg.kelvin if reference_temperature else None
1137
+ thermostat_parameters['reference_temperature'] = reference_temperature
1138
+ coupling_constant = self.input_parameters.get('tau-t', None)
1139
+ if isinstance(coupling_constant, str):
1140
+ coupling_constant = to_float(
1141
+ coupling_constant.split()[0]
1142
+ ) # ! simulated annealing protocols not supported
1143
+ coupling_constant *= ureg.picosecond if coupling_constant else None
1144
+ thermostat_parameters['coupling_constant'] = coupling_constant
1145
+
1146
+ return thermostat_parameters
1147
+
1148
+ def get_barostat_parameters(self):
1149
+ barostat_parameters = {}
1150
+ barostat_map = {
1151
+ 'berendsen': 'berendsen',
1152
+ 'parrinello-rahman': 'parrinello_rahman',
1153
+ 'mttk': 'martyna_tuckerman_tobias_klein',
1154
+ 'c-rescale': 'stochastic_cell_rescaling',
1155
+ }
1156
+ barostat = self.input_parameters.get('pcoupl', 'no').lower()
1157
+ value = barostat_map.get(
1158
+ barostat, [val for key, val in barostat_map.items() if key in barostat]
1159
+ )
1160
+ value = (
1161
+ value
1162
+ if not isinstance(value, list)
1163
+ else value[0]
1164
+ if len(value) != 0
1165
+ else None
1166
+ )
1167
+ barostat_parameters['barostat_type'] = value
1168
+ if barostat_parameters['barostat_type']:
1169
+ couplingtype = self.input_parameters.get('pcoupltype', None).lower()
1170
+ couplingtype_map = {
1171
+ 'isotropic': 'isotropic',
1172
+ 'semiisotropic': 'semi_isotropic',
1173
+ 'anisotropic': 'anisotropic',
1174
+ }
1175
+ value = couplingtype_map.get(
1176
+ couplingtype,
1177
+ [val for key, val in couplingtype_map.items() if key in couplingtype],
1178
+ )
1179
+ barostat_parameters['coupling_type'] = (
1180
+ value[0] if isinstance(value, list) else value
1181
+ )
1182
+ taup = to_float(self.input_parameters.get('tau-p', None))
1183
+ barostat_parameters['coupling_constant'] = (
1184
+ np.ones(shape=(3, 3)) * taup * ureg.picosecond if taup else None
1185
+ )
1186
+ refp = self.input_parameters.get('ref-p', None)
1187
+ barostat_parameters['reference_pressure'] = (
1188
+ refp * ureg.bar if refp is not None else None
1189
+ )
1190
+ compressibility = self.input_parameters.get('compressibility', None)
1191
+ barostat_parameters['compressibility'] = (
1192
+ compressibility * (1.0 / ureg.bar)
1193
+ if compressibility is not None
1194
+ else None
1195
+ )
1196
+ return barostat_parameters
1197
+
1198
+ def get_free_energy_calculation_parameters(self):
1199
+ free_energy_parameters = {}
1200
+ free_energy = self.input_parameters.get('free-energy', '')
1201
+ free_energy = free_energy.lower() if free_energy else ''
1202
+ expanded = self.input_parameters.get('expanded', '')
1203
+ expanded = expanded.lower() if expanded else ''
1204
+ delta_lambda = int(self.input_parameters.get('delta-lamda', -1))
1205
+ if free_energy == 'yes' and expanded == 'yes':
1206
+ self.logger.warning(
1207
+ 'storage of expanded ensemble simulation data not supported, skipping storage of free energy calculation parameters'
1208
+ )
1209
+ elif free_energy == 'yes' and delta_lambda == 'no':
1210
+ self.logger.warning(
1211
+ 'Only fixed state free energy calculation calculations are explicitly supported, skipping storage of free energy calculation parameters.'
1212
+ )
1213
+ elif free_energy == 'yes':
1214
+ free_energy_parameters['type'] = 'alchemical'
1215
+ lambda_key_map = {
1216
+ 'fep': 'output',
1217
+ 'coul': 'coulomb',
1218
+ 'vdw': 'vdw',
1219
+ 'bonded': 'bonded',
1220
+ 'restraint': 'restraint',
1221
+ 'mass': 'mass',
1222
+ 'temperature': 'temperature',
1223
+ }
1224
+ lambdas = {
1225
+ key: self.input_parameters.get(f'{key}-lambdas', '')
1226
+ for key in lambda_key_map.keys()
1227
+ }
1228
+ lambdas = {
1229
+ key: [to_float(i) for i in val.split()] for key, val in lambdas.items()
1230
+ }
1231
+ free_energy_parameters['lambdas'] = [
1232
+ {'type': nomad_key, 'value': lambdas[gromacs_key]}
1233
+ for gromacs_key, nomad_key in lambda_key_map.items()
1234
+ if lambdas[gromacs_key]
1235
+ ]
1236
+ free_energy_parameters['lambda_index'] = self.input_parameters.get(
1237
+ 'init-lambda-state', ''
1238
+ )
1239
+
1240
+ atoms_info = self.traj_parser._results['atoms_info']
1241
+ atoms_moltypes = np.array(atoms_info['moltypes'])
1242
+ couple_moltype = self.input_parameters.get('couple-moltype', '').split()
1243
+ n_atoms = len(atoms_moltypes)
1244
+ indices = []
1245
+ if len(couple_moltype) == 1 and couple_moltype[0].lower() == 'system':
1246
+ indices.extend(range(n_atoms))
1247
+ else:
1248
+ for moltype in couple_moltype:
1249
+ indices.extend(
1250
+ [
1251
+ index
1252
+ for index in range(n_atoms)
1253
+ if atoms_moltypes[index].lower() == moltype
1254
+ ]
1255
+ )
1256
+ free_energy_parameters['atom_indices'] = indices
1257
+
1258
+ couple_vdw_map = {'vdw-q': True, 'vdw': True, 'q': False, 'none': False}
1259
+ couple_coloumb_map = {
1260
+ 'vdw-q': True,
1261
+ 'vdw': False,
1262
+ 'q': True,
1263
+ 'none': False,
1264
+ }
1265
+ couple_initial = self.input_parameters.get('couple-lambda0', 'none').lower()
1266
+ couple_final = self.input_parameters.get('couple-lambda1', 'vdw-q').lower()
1267
+
1268
+ free_energy_parameters['initial_state_vdw'] = couple_vdw_map[couple_initial]
1269
+ free_energy_parameters['final_state_vdw'] = couple_vdw_map[couple_final]
1270
+ free_energy_parameters['initial_state_coloumb'] = couple_coloumb_map[
1271
+ couple_initial
1272
+ ]
1273
+ free_energy_parameters['final_state_coloumb'] = couple_coloumb_map[
1274
+ couple_final
1275
+ ]
1276
+
1277
+ couple_intramolecular = self.input_parameters.get(
1278
+ 'couple-intramol', 'on'
1279
+ ).lower()
1280
+ free_energy_parameters['final_state_bonded'] = True
1281
+ free_energy_parameters['initial_state_bonded'] = (
1282
+ couple_intramolecular != 'yes'
1283
+ )
1284
+ return free_energy_parameters
1285
+
1286
+ def parse_workflow(self):
1287
+ sec_run = self.archive.run[-1]
1288
+ sec_calc = sec_run.get('calculation')
1289
+ input_parameters = self.input_parameters
1290
+
1291
+ workflow = None
1292
+ integrator = input_parameters.get('integrator', 'md').lower()
1293
+ if integrator in ['l-bfgs', 'cg', 'steep']:
1294
+ workflow = GeometryOptimization(
1295
+ method=GeometryOptimizationMethod(),
1296
+ results=GeometryOptimizationResults(),
1297
+ )
1298
+ workflow.method.type = 'atomic'
1299
+ integrator_map = {
1300
+ 'steep': 'steepest_descent',
1301
+ 'cg': 'conjugant_gradient',
1302
+ 'l-bfgs': 'low_memory_broyden_fletcher_goldfarb_shanno',
1303
+ }
1304
+ value = integrator_map.get(
1305
+ integrator,
1306
+ [val for key, val in integrator_map.items() if key in integrator],
1307
+ )
1308
+ value = (
1309
+ value
1310
+ if not isinstance(value, list)
1311
+ else value[0]
1312
+ if len(value) != 0
1313
+ else None
1314
+ )
1315
+ workflow.method.method = value
1316
+ nsteps = input_parameters.get('nsteps', None)
1317
+ workflow.method.optimization_steps_maximum = int(nsteps) if nsteps else None
1318
+ nstenergy = input_parameters.get('nstenergy', None)
1319
+ workflow.method.save_frequency = int(nstenergy) if nstenergy else None
1320
+
1321
+ force_maximum = to_float(input_parameters.get('emtol', None))
1322
+ force_conversion = ureg.convert(
1323
+ 1.0, ureg.kilojoule * ureg.avogadro_number / ureg.nanometer, ureg.newton
1324
+ )
1325
+ workflow.method.convergence_tolerance_force_maximum = (
1326
+ force_maximum * force_conversion if force_maximum else None
1327
+ )
1328
+
1329
+ energies = []
1330
+ steps = []
1331
+ for calc in sec_calc:
1332
+ val = calc.get('energy')
1333
+ energy = val.get('potential') if val else None
1334
+ if energy:
1335
+ energies.append(energy.value.magnitude)
1336
+ step = calc.get('step')
1337
+ steps.append(step)
1338
+ workflow.results.energies = energies
1339
+ workflow.results.steps = steps
1340
+ workflow.results.optimization_steps = len(energies) + 1
1341
+
1342
+ final_force_maximum = self.log_parser.get('maximum_force')
1343
+ final_force_maximum = to_float(
1344
+ re.split('=|\n', final_force_maximum)[1]
1345
+ if final_force_maximum
1346
+ else None
1347
+ )
1348
+ workflow.results.final_force_maximum = (
1349
+ final_force_maximum * force_conversion if final_force_maximum else None
1350
+ )
1351
+ self.archive.workflow2 = workflow
1352
+ else:
1353
+ method, results = {}, {}
1354
+ nsteps = input_parameters.get('nsteps', None)
1355
+ method['n_steps'] = int(nsteps) if nsteps else None
1356
+ nstxout = input_parameters.get('nstxout', None)
1357
+ method['coordinate_save_frequency'] = int(nstxout) if nstxout else None
1358
+ nstvout = input_parameters.get('nstvout', None)
1359
+ method['velocity_save_frequency'] = int(nstvout) if nstvout else None
1360
+ nstfout = input_parameters.get('nstfout', None)
1361
+ method['force_save_frequency'] = int(nstfout) if nstfout else None
1362
+ nstenergy = input_parameters.get('nstenergy', None)
1363
+ method['thermodynamics_save_frequency'] = (
1364
+ int(nstenergy) if nstenergy else None
1365
+ )
1366
+
1367
+ integrator_map = {
1368
+ 'md': 'leap_frog',
1369
+ 'md-vv': 'velocity_verlet',
1370
+ 'sd': 'langevin_goga',
1371
+ 'bd': 'brownian',
1372
+ }
1373
+ value = integrator_map.get(
1374
+ integrator,
1375
+ [val for key, val in integrator_map.items() if key in integrator],
1376
+ )
1377
+ value = (
1378
+ value
1379
+ if not isinstance(value, list)
1380
+ else value[0]
1381
+ if len(value) != 0
1382
+ else None
1383
+ )
1384
+ method['integrator_type'] = value
1385
+ timestep = to_float(input_parameters.get('dt', None))
1386
+ method['integration_timestep'] = (
1387
+ timestep * ureg.picosecond if timestep else None
1388
+ )
1389
+
1390
+ thermostat_parameters = self.get_thermostat_parameters(integrator)
1391
+ method['thermostat_parameters'] = [thermostat_parameters]
1392
+ barostat_parameters = self.get_barostat_parameters()
1393
+ method['barostat_parameters'] = [barostat_parameters]
1394
+
1395
+ if thermostat_parameters.get('thermostat_type'):
1396
+ method['thermodynamic_ensemble'] = (
1397
+ 'NPT' if barostat_parameters.get('barostat_type') else 'NVT'
1398
+ )
1399
+ elif barostat_parameters.get('barostat_type'):
1400
+ method['thermodynamic_ensemble'] = 'NPH'
1401
+ else:
1402
+ method['thermodynamic_ensemble'] = 'NVE'
1403
+
1404
+ params_key = 'free_energy_calculation_parameters'
1405
+ method[params_key] = self.get_free_energy_calculation_parameters()
1406
+
1407
+ self.xvg_parser.mainfile = self.get_gromacs_file('xvg')
1408
+ free_energies = self.xvg_parser.get('results')
1409
+
1410
+ title = free_energies.get('title', '') if free_energies is not None else ''
1411
+ flag_fe = False
1412
+ if (
1413
+ r'dH/d\xl\f{}' in title and r'\xD\f{}H' in title
1414
+ ): # TODO incorporate x and y axis labels into the checks
1415
+ flag_fe = True
1416
+ results_key = 'free_energy_calculations'
1417
+ results[results_key] = {}
1418
+ columns = free_energies.get('column_vals')
1419
+ results[results_key]['n_frames'] = len(columns)
1420
+ lambdas = method[params_key].get('lambdas', None)
1421
+ results[results_key]['n_states'] = (
1422
+ len(lambdas[0].get('value', [])) if lambdas is not None else None
1423
+ )
1424
+ results[results_key]['lambda_index'] = method[params_key].get(
1425
+ 'lambda_index', None
1426
+ )
1427
+ results[results_key]['value_unit'] = str(self._gro_energy_units.units)
1428
+ xaxis = free_energies.get('xaxis', '').lower()
1429
+ # The expected columns of the xvg file are:
1430
+ # Total Energy
1431
+ # dH/dlambda current lambda
1432
+ # Delta H between each lambda and current lambda (n_lambda columns)
1433
+ # PV Energy
1434
+ if (
1435
+ 'time' in xaxis
1436
+ and columns[:, 3:-1].shape[1] == results[results_key]['n_states']
1437
+ ):
1438
+ results[results_key]['times'] = columns[:, 0] * ureg.ps
1439
+ columns = columns[:, 1:] * self._gro_energy_units.magnitude
1440
+ else:
1441
+ self.logger.warning(
1442
+ 'Unexpected format of xvg file. Not storing free energy calculation results.'
1443
+ )
1444
+ flag_fe = False
1445
+
1446
+ self.parse_md_workflow(dict(method=method, results=results))
1447
+
1448
+ if flag_fe and self.archive.m_context:
1449
+ sec_fe_parameters = (
1450
+ self.archive.workflow2.method.free_energy_calculation_parameters[0]
1451
+ )
1452
+ sec_fe = self.archive.workflow2.results.free_energy_calculations[0]
1453
+ sec_fe.method_ref = sec_fe_parameters
1454
+ sec_fe.value_total_energy_magnitude = columns[:, 0]
1455
+ sec_fe.value_total_energy_derivative_magnitude = columns[:, 1]
1456
+ sec_fe.value_total_energy_differences_magnitude = columns[:, 2:-1]
1457
+ sec_fe.value_PV_energy_magnitude = columns[:, -1]
1458
+
1459
+ def parse_input(self):
1460
+ sec_run = self.archive.run[-1]
1461
+ sec_input_output_files = x_gromacs_section_input_output_files()
1462
+ sec_run.x_gromacs_section_input_output_files = sec_input_output_files
1463
+
1464
+ topology_file = os.path.basename(self.traj_parser.mainfile)
1465
+ if topology_file.endswith('tpr'):
1466
+ sec_input_output_files.x_gromacs_inout_file_topoltpr = topology_file
1467
+ elif topology_file.endswith('gro'):
1468
+ sec_input_output_files.x_gromacs_inout_file_confoutgro = topology_file
1469
+
1470
+ trajectory_file = os.path.basename(self.traj_parser.auxilliary_files[0])
1471
+ sec_input_output_files.x_gromacs_inout_file_trajtrr = trajectory_file
1472
+
1473
+ edr_file = os.path.basename(self.energy_parser.mainfile)
1474
+ sec_input_output_files.x_gromacs_inout_file_eneredr = edr_file
1475
+
1476
+ sec_control_parameters = x_gromacs_section_control_parameters()
1477
+ sec_run.x_gromacs_section_control_parameters = sec_control_parameters
1478
+ input_parameters = self.input_parameters
1479
+ input_parameters.update(self.info.get('header', {}))
1480
+ for key, val in input_parameters.items():
1481
+ key = (
1482
+ 'x_gromacs_inout_control_%s'
1483
+ % key.replace('-', '').replace(' ', '_').lower()
1484
+ )
1485
+ quantity_def = sec_control_parameters.m_def.all_quantities.get(key)
1486
+ if quantity_def:
1487
+ try:
1488
+ val = str(val) if not isinstance(val, np.ndarray) else val
1489
+ sec_control_parameters.m_set(quantity_def, val)
1490
+ except Exception:
1491
+ self.logger.error('Error setting metainfo.', data={'key': key})
1492
+
1493
+ def write_to_archive(self):
1494
+ self._maindir = os.path.dirname(self.mainfile)
1495
+ self._gromacs_files = os.listdir(self._maindir)
1496
+ self._basename = os.path.basename(self.mainfile).rsplit('.', 1)[0]
1497
+ self.log_parser.mainfile = self.mainfile
1498
+ self.log_parser.logger = self.logger
1499
+ self.traj_parser.logger = self.logger
1500
+ self.energy_parser.logger = self.logger
1501
+ self._frame_rate = None
1502
+
1503
+ sec_run = Run()
1504
+ self.archive.run.append(sec_run)
1505
+
1506
+ header = self.log_parser.get('header', {})
1507
+
1508
+ sec_run.program = Program(
1509
+ name='GROMACS',
1510
+ version=str(header.get('version', 'unknown')).lstrip('VERSION '),
1511
+ )
1512
+
1513
+ sec_time_run = TimeRun()
1514
+ sec_run.time_run = sec_time_run
1515
+ for key in ['start', 'end']:
1516
+ time = self.log_parser.get('time_%s' % key)
1517
+ if time is None:
1518
+ continue
1519
+ setattr(
1520
+ sec_time_run,
1521
+ 'date_%s' % key,
1522
+ datetime.datetime.strptime(time, '%a %b %d %H:%M:%S %Y').timestamp(),
1523
+ )
1524
+
1525
+ host_info = self.log_parser.get('host_info')
1526
+ if host_info is not None:
1527
+ sec_run.x_gromacs_program_execution_host = host_info[0]
1528
+ sec_run.x_gromacs_parallel_task_nr = host_info[1]
1529
+ sec_run.x_gromacs_number_of_tasks = host_info[2]
1530
+
1531
+ # parse the input parameters using log file as default and mdp output or input as supplementary
1532
+ self.input_parameters = {
1533
+ key.replace('_', '-'): val.lower() if isinstance(val, str) else val
1534
+ for key, val in self.log_parser.get('input_parameters', {}).items()
1535
+ }
1536
+ self.mdp_parser.mainfile = self.get_mdp_file()
1537
+ for key, param in self.mdp_parser.get('input_parameters', {}).items():
1538
+ new_key = key.replace('_', '-')
1539
+ if new_key not in self.input_parameters:
1540
+ self.input_parameters[new_key] = (
1541
+ param.lower() if isinstance(param, str) else param
1542
+ )
1543
+
1544
+ topology_file = self.get_gromacs_file('tpr')
1545
+ # I have no idea if output trajectory file can be specified in input
1546
+ trr_file = self.get_gromacs_file('trr')
1547
+ trr_file_nopath = trr_file.rsplit('.', 1)[0]
1548
+ trr_file_nopath = trr_file_nopath.rsplit('/')[-1]
1549
+ xtc_file = self.get_gromacs_file('xtc')
1550
+ xtc_file_nopath = xtc_file.rsplit('.', 1)[0]
1551
+ xtc_file_nopath = xtc_file_nopath.rsplit('/')[-1]
1552
+ if not trr_file_nopath.startswith(self._basename):
1553
+ trajectory_file = (
1554
+ xtc_file if xtc_file_nopath.startswith(self._basename) else trr_file
1555
+ )
1556
+ else:
1557
+ trajectory_file = trr_file
1558
+
1559
+ self.traj_parser.mainfile = topology_file
1560
+ self.traj_parser.auxilliary_files = [trajectory_file]
1561
+ # check to see if the trr file can be read properly (and has positions), otherwise try xtc file instead
1562
+ positions = None
1563
+ if (universe := self.traj_parser.universe) is not None:
1564
+ atoms = getattr(universe, 'atoms', None)
1565
+ positions = getattr(atoms, 'positions', None)
1566
+ if positions is None:
1567
+ self.traj_parser.auxilliary_files = [xtc_file] if xtc_file else [trr_file]
1568
+
1569
+ self.parse_method()
1570
+
1571
+ self.parse_system()
1572
+
1573
+ self.parse_thermodynamic_data()
1574
+
1575
+ self.parse_input()
1576
+
1577
+ self.parse_workflow()
1578
+
1579
+ self.traj_parser.clean()
1580
+ self.traj_parser.close()
1581
+ self.energy_parser.close()