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,1753 @@
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 numpy as np
20
+ import os
21
+ from ase import data as asedata
22
+ import re
23
+
24
+ from nomad.units import ureg
25
+
26
+ from nomad.parsing.file_parser import Quantity, TextParser
27
+ from runschema.run import Run, Program
28
+ from runschema.method import (
29
+ NeighborSearching,
30
+ ForceCalculations,
31
+ ForceField,
32
+ Method,
33
+ Model,
34
+ AtomParameters,
35
+ )
36
+ from runschema.system import AtomsGroup
37
+ from simulationworkflowschema import (
38
+ GeometryOptimization,
39
+ GeometryOptimizationMethod,
40
+ GeometryOptimizationResults,
41
+ )
42
+ from .metainfo.lammps import (
43
+ x_lammps_section_input_output_files,
44
+ x_lammps_section_control_parameters,
45
+ )
46
+ from atomisticparsers.utils import MDAnalysisParser, MDParser
47
+ from simulationworkflowschema.molecular_dynamics import get_bond_list_from_model_contributions
48
+
49
+
50
+ re_float = r'[-+]?\d+\.*\d*(?:[Ee][-+]\d+)?'
51
+ re_n = r'[\n\r]'
52
+
53
+
54
+ def get_unit(units_type, property_type=None, dimension=3):
55
+ mole = 6.022140857e23
56
+
57
+ units_type = units_type.lower()
58
+ if units_type == 'real':
59
+ units = dict(
60
+ mass=ureg.g / mole,
61
+ distance=ureg.angstrom,
62
+ time=ureg.fs,
63
+ energy=ureg.J * 4184.0 / mole,
64
+ velocity=ureg.angstrom / ureg.fs,
65
+ force=ureg.J * 4184.0 / ureg.angstrom / mole,
66
+ torque=ureg.J * 4184.0 / mole,
67
+ temperature=ureg.K,
68
+ pressure=ureg.atm,
69
+ dynamic_viscosity=ureg.poise,
70
+ charge=ureg.elementary_charge,
71
+ dipole=ureg.elementary_charge * ureg.angstrom,
72
+ electric_field=ureg.V / ureg.angstrom,
73
+ density=ureg.g / ureg.cm**dimension,
74
+ )
75
+
76
+ elif units_type == 'metal':
77
+ units = dict(
78
+ mass=ureg.g / mole,
79
+ distance=ureg.angstrom,
80
+ time=ureg.ps,
81
+ energy=ureg.eV,
82
+ velocity=ureg.angstrom / ureg.ps,
83
+ force=ureg.eV / ureg.angstrom,
84
+ torque=ureg.eV,
85
+ temperature=ureg.K,
86
+ pressure=ureg.bar,
87
+ dynamic_viscosity=ureg.poise,
88
+ charge=ureg.elementary_charge,
89
+ dipole=ureg.elementary_charge * ureg.angstrom,
90
+ electric_field=ureg.V / ureg.angstrom,
91
+ density=ureg.g / ureg.cm**dimension,
92
+ )
93
+
94
+ elif units_type == 'si':
95
+ units = dict(
96
+ mass=ureg.kg,
97
+ distance=ureg.m,
98
+ time=ureg.s,
99
+ energy=ureg.J,
100
+ velocity=ureg.m / ureg.s,
101
+ force=ureg.N,
102
+ torque=ureg.N * ureg.m,
103
+ temperature=ureg.K,
104
+ pressure=ureg.Pa,
105
+ dynamic_viscosity=ureg.Pa * ureg.s,
106
+ charge=ureg.C,
107
+ dipole=ureg.C * ureg.m,
108
+ electric_field=ureg.V / ureg.m,
109
+ density=ureg.kg / ureg.m**dimension,
110
+ )
111
+
112
+ elif units_type == 'cgs':
113
+ units = dict(
114
+ mass=ureg.g,
115
+ distance=ureg.cm,
116
+ time=ureg.s,
117
+ energy=ureg.erg,
118
+ velocity=ureg.cm / ureg.s,
119
+ force=ureg.dyne,
120
+ torque=ureg.dyne * ureg.cm,
121
+ temperature=ureg.K,
122
+ pressure=ureg.dyne / ureg.cm**2,
123
+ dynamic_viscosity=ureg.poise,
124
+ charge=ureg.esu,
125
+ dipole=ureg.esu * ureg.cm,
126
+ electric_field=ureg.dyne / ureg.esu,
127
+ density=ureg.g / ureg.cm**dimension,
128
+ )
129
+
130
+ elif units_type == 'electron':
131
+ units = dict(
132
+ mass=ureg.amu,
133
+ distance=ureg.bohr,
134
+ time=ureg.fs,
135
+ energy=ureg.hartree,
136
+ velocity=ureg.bohr / ureg.atomic_unit_of_time,
137
+ force=ureg.hartree / ureg.bohr,
138
+ temperature=ureg.K,
139
+ pressure=ureg.Pa,
140
+ charge=ureg.elementary_charge,
141
+ dipole=ureg.debye,
142
+ electric_field=ureg.V / ureg.cm,
143
+ )
144
+
145
+ elif units_type == 'micro':
146
+ units = dict(
147
+ mass=ureg.pg,
148
+ distance=ureg.microm,
149
+ time=ureg.micros,
150
+ energy=ureg.pg * ureg.microm**2 / ureg.micros**2,
151
+ velocity=ureg.microm / ureg.micros,
152
+ force=ureg.pg * ureg.microm / ureg.micros**2,
153
+ torque=ureg.pg * ureg.microm**2 / ureg.micros**2,
154
+ temperature=ureg.K,
155
+ pressure=ureg.pg / (ureg.microm * ureg.micros**2),
156
+ dynamic_viscosity=ureg.pg / (ureg.microm * ureg.micros),
157
+ charge=ureg.pC,
158
+ dipole=ureg.pC * ureg.microm,
159
+ electric_field=ureg.V / ureg.microm,
160
+ density=ureg.pg / ureg.microm**dimension,
161
+ )
162
+
163
+ elif units_type == 'nano':
164
+ units = dict(
165
+ mass=ureg.ag,
166
+ distance=ureg.nm,
167
+ time=ureg.ns,
168
+ energy=ureg.ag * ureg.nm**2 / ureg.ns**2,
169
+ velocity=ureg.nm / ureg.ns,
170
+ force=ureg.ag * ureg.nm / ureg.ns**2,
171
+ torque=ureg.ag * ureg.nm**2 / ureg.ns**2,
172
+ temperature=ureg.K,
173
+ pressure=ureg.ag / (ureg.nm * ureg.ns**2),
174
+ dynamic_viscosity=ureg.ag / (ureg.nm * ureg.ns),
175
+ charge=ureg.elementary_charge,
176
+ dipole=ureg.elementary_charge * ureg.nm,
177
+ electric_field=ureg.V / ureg.nm,
178
+ density=ureg.ag / ureg.nm**dimension,
179
+ )
180
+
181
+ else:
182
+ # units = dict(
183
+ # mass=1, distance=1, time=1, energy=1, velocity=1, force=1,
184
+ # torque=1, temperature=1, pressure=1, dynamic_viscosity=1, charge=1,
185
+ # dipole=1, electric_field=1, density=1)
186
+ units = dict()
187
+
188
+ if property_type:
189
+ return units.get(property_type, None)
190
+ else:
191
+ return units
192
+
193
+
194
+ class DataParser(TextParser):
195
+ def __init__(self):
196
+ self._headers = [
197
+ 'atoms',
198
+ 'bonds',
199
+ 'angles',
200
+ 'dihedrals',
201
+ 'impropers',
202
+ 'atom types',
203
+ 'bond types',
204
+ 'angle types',
205
+ 'dihedral types',
206
+ 'improper types',
207
+ 'extra bond per atom',
208
+ 'extra/bond/per/atom',
209
+ 'extra angle per atom',
210
+ 'extra/angle/per/atom',
211
+ 'extra dihedral per atom',
212
+ 'extra/dihedral/per/atom',
213
+ 'extra improper per atom',
214
+ 'extra/improper/per/atom',
215
+ 'extra special per atom',
216
+ 'extra/special/per/atom',
217
+ 'ellipsoids',
218
+ 'lines',
219
+ 'triangles',
220
+ 'bodies',
221
+ ]
222
+ self._sections = [
223
+ 'Atoms',
224
+ 'Velocities',
225
+ 'Masses',
226
+ 'Ellipsoids',
227
+ 'Lines',
228
+ 'Triangles',
229
+ 'Bodies',
230
+ 'Bonds',
231
+ 'Angles',
232
+ 'Dihedrals',
233
+ 'Impropers',
234
+ 'Pair Coeffs',
235
+ 'PairIJ Coeffs',
236
+ 'Bond Coeffs',
237
+ 'Angle Coeffs',
238
+ 'Dihedral Coeffs',
239
+ 'Improper Coeffs',
240
+ 'BondBond Coeffs',
241
+ 'BondAngle Coeffs',
242
+ 'MiddleBondTorsion Coeffs',
243
+ 'EndBondTorsion Coeffs',
244
+ 'AngleTorsion Coeffs',
245
+ 'AngleAngleTorsion Coeffs',
246
+ 'BondBond13 Coeffs',
247
+ 'AngleAngle Coeffs',
248
+ ]
249
+ self._interactions = [
250
+ section for section in self._sections if section.endswith('Coeffs')
251
+ ]
252
+ super().__init__(None)
253
+
254
+ def init_quantities(self):
255
+ self._quantities = [
256
+ Quantity(header, rf'{re_n} *(\d+) +{header}', repeats=True, dtype=np.int32)
257
+ for header in self._headers
258
+ ]
259
+
260
+ def get_section_value(val):
261
+ val = val.strip().splitlines()
262
+ name = None
263
+
264
+ if val[0][0] == '#':
265
+ name = val[0][1:].strip()
266
+ val = val[1:]
267
+
268
+ value = []
269
+ for i in range(len(val)):
270
+ v = val[i].split('#')[0].split()
271
+ if not v:
272
+ continue
273
+
274
+ try:
275
+ value.append(np.array(v, dtype=float))
276
+ except Exception:
277
+ break
278
+
279
+ return name, np.array(value)
280
+
281
+ self._quantities.extend(
282
+ [
283
+ Quantity(
284
+ section,
285
+ rf'{section} *(#*.*{re_n}\s+(?:[\d ]+{re_float}.+\s+)+)',
286
+ str_operation=get_section_value,
287
+ repeats=True,
288
+ )
289
+ for section in self._sections
290
+ ]
291
+ )
292
+
293
+ def get_interactions(self):
294
+ styles_coeffs = []
295
+ for interaction in self._interactions:
296
+ coeffs = self.get(interaction, None)
297
+ if coeffs is None:
298
+ continue
299
+ if isinstance(coeffs, tuple):
300
+ coeffs = list(coeffs)
301
+
302
+ styles_coeffs += coeffs
303
+
304
+ return styles_coeffs
305
+
306
+
307
+ class TrajParser(TextParser):
308
+ def __init__(self):
309
+ self._masses = None
310
+ self._reference_masses = dict(
311
+ masses=np.array(asedata.atomic_masses), symbols=asedata.chemical_symbols
312
+ )
313
+ self._chemical_symbols = None
314
+ super().__init__(None)
315
+
316
+ def init_quantities(self):
317
+ def get_pbc_cell(val):
318
+ val = val.split()
319
+
320
+ pbc = [v == 'pp' for v in val[:3]]
321
+
322
+ cell = np.zeros((3, 3))
323
+ for i in range(3):
324
+ cell[i][i] = float(val[i * 2 + 4]) - float(val[i * 2 + 3])
325
+
326
+ return pbc, cell
327
+
328
+ def get_atoms_info(val):
329
+ val = val.split('\n')
330
+ keys = val[0].split()
331
+ values = np.array([v.split() for v in val[1:] if v], dtype=float)
332
+ values = values[values[:, 0].argsort()].T
333
+ return {keys[i]: values[i] for i in range(len(keys))}
334
+
335
+ self._quantities = [
336
+ Quantity(
337
+ 'time_step',
338
+ r'\s*ITEM:\s*TIMESTEP\s*\n\s*(\d+)\s*\n',
339
+ comment='#',
340
+ repeats=True,
341
+ ),
342
+ Quantity(
343
+ 'n_atoms',
344
+ r'\s*ITEM:\s*NUMBER OF ATOMS\s*\n\s*(\d+)\s*\n',
345
+ comment='#',
346
+ repeats=True,
347
+ ),
348
+ Quantity(
349
+ 'pbc_cell',
350
+ r'\s*ITEM: BOX BOUNDS\s*([\s\w]+)\n([\+\-\d\.eE\s]+)\n',
351
+ str_operation=get_pbc_cell,
352
+ comment='#',
353
+ repeats=True,
354
+ ),
355
+ Quantity(
356
+ 'atoms_info',
357
+ r'\s*ITEM:\s*ATOMS\s*([ \w]+\n)*?([\+\-eE\d\.\n ]+)',
358
+ str_operation=get_atoms_info,
359
+ comment='#',
360
+ repeats=True,
361
+ ),
362
+ ]
363
+
364
+ @property
365
+ def with_trajectory(self):
366
+ return self.get('atoms_info') is not None
367
+
368
+ @property
369
+ def n_frames(self):
370
+ return len(self.get('atoms_info', []))
371
+
372
+ @property
373
+ def masses(self):
374
+ return self._masses
375
+
376
+ @masses.setter
377
+ def masses(self, val):
378
+ self._masses = val
379
+ if self._masses is None:
380
+ return
381
+
382
+ self._masses = val
383
+ if self._chemical_symbols is None:
384
+ masses = self._masses[0][1]
385
+ self._chemical_symbols = {}
386
+ for i in range(len(masses)):
387
+ symbol_idx = np.argmin(
388
+ abs(self._reference_masses['masses'] - masses[i][1])
389
+ )
390
+ self._chemical_symbols[masses[i][0]] = self._reference_masses[
391
+ 'symbols'
392
+ ][symbol_idx]
393
+
394
+ def get_atom_labels(self, idx):
395
+ atoms_info = self.get('atoms_info')
396
+ if atoms_info is None:
397
+ return
398
+
399
+ atoms_id = atoms_info[idx].get('id')
400
+ default = ['X' for _ in atoms_id] if atoms_id is not None else None
401
+ atoms_type = atoms_info[idx].get('type')
402
+ if atoms_type is None:
403
+ return default
404
+ if self._chemical_symbols is None:
405
+ return default
406
+
407
+ try:
408
+ atom_labels = [self._chemical_symbols[atype] for atype in atoms_type]
409
+ except Exception:
410
+ self.logger.error('Error resolving atom labels.')
411
+ return
412
+
413
+ return atom_labels
414
+
415
+ def get_positions(self, idx):
416
+ atoms_info = self.get('atoms_info')
417
+ if atoms_info is None:
418
+ return
419
+
420
+ atoms_info = atoms_info[idx]
421
+
422
+ cell = self.get('pbc_cell')
423
+ cell = None if cell is None else cell[idx][1]
424
+ if 'xs' in atoms_info and 'ys' in atoms_info and 'zs' in atoms_info:
425
+ if cell is None:
426
+ return
427
+ positions = np.array(
428
+ [atoms_info['xs'], atoms_info['ys'], atoms_info['zs']]
429
+ ).T
430
+ positions = positions * np.linalg.norm(cell, axis=1) + np.amin(cell, axis=1)
431
+
432
+ elif 'xu' in atoms_info and 'yu' in atoms_info and 'zu' in atoms_info:
433
+ positions = np.array(
434
+ [atoms_info['xu'], atoms_info['yu'], atoms_info['zu']]
435
+ ).T
436
+
437
+ elif 'xsu' in atoms_info and 'ysu' in atoms_info and 'zsu' in atoms_info:
438
+ if cell is None:
439
+ return
440
+ positions = np.array(
441
+ [atoms_info['xsu'], atoms_info['ysu'], atoms_info['zsu']]
442
+ ).T
443
+ positions = positions * np.linalg.norm(cell, axis=1) + np.amin(cell, axis=1)
444
+
445
+ elif 'x' in atoms_info and 'y' in atoms_info and 'z' in atoms_info:
446
+ positions = np.array([atoms_info['x'], atoms_info['y'], atoms_info['z']]).T
447
+ if 'ix' in atoms_info and 'iy' in atoms_info and 'iz' in atoms_info:
448
+ if cell is None:
449
+ return
450
+ positions_img = np.array(
451
+ [atoms_info['ix'], atoms_info['iy'], atoms_info['iz']]
452
+ ).T
453
+
454
+ positions += positions_img * np.linalg.norm(cell, axis=1)
455
+ else:
456
+ positions = None
457
+
458
+ return positions
459
+
460
+ def get_velocities(self, idx):
461
+ atoms_info = self.get('atoms_info')
462
+ if atoms_info is None:
463
+ return
464
+ atoms_info = atoms_info[idx]
465
+ if 'vx' not in atoms_info or 'vy' not in atoms_info or 'vz' not in atoms_info:
466
+ return
467
+
468
+ return np.array([atoms_info['vx'], atoms_info['vy'], atoms_info['vz']]).T
469
+
470
+ def get_forces(self, idx):
471
+ atoms_info = self.get('atoms_info')
472
+ if atoms_info is None:
473
+ return
474
+ atoms_info = atoms_info[idx]
475
+ if 'fx' not in atoms_info or 'fy' not in atoms_info or 'fz' not in atoms_info:
476
+ return
477
+ return np.array([atoms_info['fx'], atoms_info['fy'], atoms_info['fz']]).T
478
+
479
+ def get_lattice_vectors(self, idx):
480
+ pbc_cell = self.get('pbc_cell')
481
+ if pbc_cell is None:
482
+ return
483
+ return pbc_cell[idx][1]
484
+
485
+ def get_pbc(self, idx):
486
+ pbc_cell = self.get('pbc_cell')
487
+ if pbc_cell is None:
488
+ return
489
+ return pbc_cell[idx][0]
490
+
491
+ def get_n_atoms(self, idx):
492
+ n_atoms = self.get('n_atoms')
493
+ if n_atoms is None:
494
+ return len(self.get_positions(idx))
495
+ return n_atoms[idx]
496
+
497
+ def get_step(self, idx):
498
+ step = self.get('time_step')
499
+ if step is None:
500
+ return
501
+ return step[idx]
502
+
503
+
504
+ class XYZTrajParser(TrajParser):
505
+ def __init__(self):
506
+ super().__init__()
507
+
508
+ def init_quantities(self):
509
+ def get_atoms_info(val_in):
510
+ val = [v.split('#')[0].split() for v in val_in.strip().split('\n')]
511
+ symbols = []
512
+ for v in val:
513
+ if v[0].isalpha():
514
+ if v[0] not in symbols:
515
+ symbols.append(v[0])
516
+ v[0] = symbols.index(v[0]) + 1
517
+ val = np.transpose(np.array([v for v in val if len(v) == 4], dtype=float))
518
+ # val[0] is the atomic number
519
+ val[0] = [list(set(val[0])).index(v) + 1 for v in val[0]]
520
+ return dict(type=val[0], x=val[1], y=val[2], z=val[3])
521
+
522
+ self.quantities = [
523
+ Quantity(
524
+ 'atoms_info',
525
+ r'((?:\d+|[A-Z][a-z]?) [\s\S]+?)(?:\s\d+\n|\Z)',
526
+ str_operation=get_atoms_info,
527
+ comment='#',
528
+ repeats=True,
529
+ )
530
+ ]
531
+
532
+
533
+ class LogParser(TextParser):
534
+ def __init__(self):
535
+ self._commands = [
536
+ 'angle_coeff',
537
+ 'angle_style',
538
+ 'atom_modify',
539
+ 'atom_style',
540
+ 'balance',
541
+ 'bond_coeff',
542
+ 'bond_style',
543
+ 'bond_write',
544
+ 'boundary',
545
+ 'change_box',
546
+ 'clear',
547
+ 'comm_modify',
548
+ 'comm_style',
549
+ 'compute',
550
+ 'compute_modify',
551
+ 'create_atoms',
552
+ 'create_bonds',
553
+ 'create_box',
554
+ 'delete_bonds',
555
+ 'dielectric',
556
+ 'dihedral_coeff',
557
+ 'dihedral_style',
558
+ 'dimension',
559
+ 'displace_atoms',
560
+ 'dump',
561
+ 'dump_modify',
562
+ 'dynamical_matrix',
563
+ 'echo',
564
+ 'fix',
565
+ 'fix_modify',
566
+ 'group',
567
+ 'group2ndx',
568
+ 'ndx2group',
569
+ 'hyper',
570
+ 'if',
571
+ 'improper_coeff',
572
+ 'improper_style',
573
+ 'include',
574
+ 'info',
575
+ 'jump',
576
+ 'kim_init',
577
+ 'kim_interactions',
578
+ 'kim_query',
579
+ 'kim_param',
580
+ 'kim_property',
581
+ 'kspace_modify',
582
+ 'kspace_style',
583
+ 'label',
584
+ 'lattice',
585
+ 'log',
586
+ 'mass',
587
+ 'message',
588
+ 'min_modify',
589
+ 'min_style',
590
+ 'minimize',
591
+ 'minimize/kk',
592
+ 'molecule',
593
+ 'neb',
594
+ 'neb/spin',
595
+ 'neigh_modify',
596
+ 'neighbor',
597
+ 'newton',
598
+ 'next',
599
+ 'package',
600
+ 'pair_coeff',
601
+ 'pair_modify',
602
+ 'pair_style',
603
+ 'pair_write',
604
+ 'partition',
605
+ 'prd',
606
+ 'print',
607
+ 'processors',
608
+ 'quit',
609
+ 'read_data',
610
+ 'read_dump',
611
+ 'read_restart',
612
+ 'region',
613
+ 'replicate',
614
+ 'rerun',
615
+ 'reset_atom_ids',
616
+ 'reset_mol_ids',
617
+ 'reset_timestep',
618
+ 'restart',
619
+ 'run',
620
+ 'run_style',
621
+ 'server',
622
+ 'set',
623
+ 'shell',
624
+ 'special_bonds',
625
+ 'suffix',
626
+ 'tad',
627
+ 'temper/grem',
628
+ 'temper/npt',
629
+ 'thermo',
630
+ 'thermo_modify',
631
+ 'thermo_style',
632
+ 'third_order',
633
+ 'timer',
634
+ 'timestep',
635
+ 'uncompute',
636
+ 'undump',
637
+ 'unfix',
638
+ 'units',
639
+ 'variable',
640
+ 'velocity',
641
+ 'write_coeff',
642
+ 'write_data',
643
+ 'write_dump',
644
+ 'write_restart',
645
+ ]
646
+ self._interactions = [
647
+ 'atom',
648
+ 'pair',
649
+ 'bond',
650
+ 'angle',
651
+ 'dihedral',
652
+ 'improper',
653
+ 'kspace',
654
+ ]
655
+ self._units = None
656
+ super().__init__(None)
657
+
658
+ def init_quantities(self):
659
+ def str_op(val):
660
+ val = val.split('#')[0]
661
+ val = val.replace('&\n', ' ').split()
662
+ val = val if len(val) > 1 else val[0]
663
+ return val
664
+
665
+ self._quantities = [
666
+ Quantity(
667
+ name,
668
+ r'\n\s*%s\s+(?!.*\$\{)([${}\w\. \/\#\-]+)(\&\n[\w\. \/\#\-]*)*' % name,
669
+ str_operation=str_op,
670
+ comment='#',
671
+ repeats=True,
672
+ )
673
+ for name in self._commands
674
+ ]
675
+
676
+ self._quantities.append(
677
+ Quantity(
678
+ 'program_version',
679
+ r'\s*LAMMPS\s*\(([\w ]+)\)\n',
680
+ dtype=str,
681
+ repeats=False,
682
+ flatten=False,
683
+ )
684
+ )
685
+
686
+ self._quantities.append(
687
+ Quantity('finished', r'\s*Dangerous builds\s*=\s*(\d+)', repeats=False)
688
+ )
689
+
690
+ self._quantities.append(
691
+ Quantity(
692
+ 'minimization_stats',
693
+ r'\s*Minimization stats:\s*([\s\S]+?)\n\n',
694
+ flatten=False,
695
+ )
696
+ )
697
+
698
+ def str_to_thermo(val):
699
+ res = {}
700
+ if val.count('Step') > 1:
701
+ val = (
702
+ val.replace('--', '').replace('=', '').replace('(sec)', '').split()
703
+ )
704
+ val = [v.strip() for v in val]
705
+
706
+ for i in range(len(val)):
707
+ if val[i][0].isalpha():
708
+ res.setdefault(val[i], [])
709
+ res[val[i]].append(float(val[i + 1]))
710
+
711
+ else:
712
+ val = val.split('\n')
713
+ keys = [v.strip() for v in val[0].split()]
714
+ val = np.array([v.split() for v in val[1:] if v], dtype=float).T
715
+
716
+ res = {key: [] for key in keys}
717
+ for i in range(len(keys)):
718
+ res[keys[i]] = val[i]
719
+
720
+ return res
721
+
722
+ self._quantities.append(
723
+ Quantity(
724
+ 'thermo_data',
725
+ r'\s*\-*(\s*Step\s*[\-\s\w\.\=\(\)]*[ \-\.\d\n]+)Loop',
726
+ str_operation=str_to_thermo,
727
+ repeats=False,
728
+ convert=False,
729
+ )
730
+ )
731
+
732
+ @property
733
+ def units(self):
734
+ if self._units is None:
735
+ units_type = self.get('units', ['lj'])[0]
736
+ self._units = get_unit(units_type)
737
+ return self._units
738
+
739
+ def get_thermodynamic_data(self):
740
+ thermo_data = self.get('thermo_data')
741
+
742
+ if thermo_data is None:
743
+ return
744
+
745
+ data = {}
746
+ for key, val in thermo_data.items():
747
+ low_key = key.lower()
748
+ if low_key.startswith('e_') or low_key.endswith('eng'):
749
+ data[key] = val * self.units.get('energy', 1)
750
+ elif low_key == 'press':
751
+ data[key] = val * self.units.get('pressure', 1)
752
+ elif low_key == 'temp':
753
+ data[key] = val * self.units.get('temperature', 1)
754
+ else:
755
+ data[key] = val
756
+ return data
757
+
758
+ def get_traj_files(self):
759
+ dump = self.get('dump')
760
+ if dump is None:
761
+ self.logger.warning('Trajectory not specified in directory, will scan.')
762
+ # TODO improve matching of traj file
763
+ traj_files = os.listdir(self.maindir)
764
+ traj_files = [
765
+ f for f in traj_files if f.endswith('trj') or f.endswith('xyz')
766
+ ]
767
+ # further eliminate
768
+ if len(traj_files) > 1:
769
+ prefix = os.path.basename(self.mainfile).rsplit('.', 1)[0]
770
+ traj_files = [f for f in traj_files if prefix in f]
771
+ else:
772
+ traj_files = []
773
+ if type(dump[0]) in [str, int]:
774
+ dump = [dump]
775
+ traj_files = [d[4] for d in dump]
776
+ traj_files = [
777
+ i for n, i in enumerate(traj_files) if i not in traj_files[:n]
778
+ ] # remove duplicates
779
+
780
+ return [os.path.join(self.maindir, f) for f in traj_files]
781
+
782
+ def get_data_files(self):
783
+ def check_file_header(file_path, regex_pattern):
784
+ header_size = 1024
785
+ file_path = f'{self.maindir}/{file_path}'
786
+ try:
787
+ with open(file_path, 'rb') as file:
788
+ file_header = file.read(header_size)
789
+ file_header_str = file_header.decode(errors='ignore')
790
+ except Exception:
791
+ file_header_str = ''
792
+
793
+ return re.search(regex_pattern, file_header_str)
794
+
795
+ read_data = self.get('read_data')
796
+ if read_data is None or 'CPU' in read_data:
797
+ self.logger.warning('Data file not specified in directory, will scan.')
798
+ data_files = os.listdir(self.maindir)
799
+ data_files = [
800
+ f for f in data_files if f.endswith('data') or f.startswith('data')
801
+ ]
802
+ if not data_files:
803
+ data_files = os.listdir(self.maindir)
804
+ data_files = [
805
+ f for f in data_files if check_file_header(f, 'LAMMPS data file')
806
+ ] # TODO: Should this be the default?
807
+ if len(data_files) > 1:
808
+ prefix = os.path.basename(self.mainfile).rsplit('.', 1)
809
+ prefix = (
810
+ prefix[1] if len(prefix) > 1 and prefix[1] != 'log' else prefix[0]
811
+ )
812
+ data_files = [f for f in data_files if prefix in f]
813
+ else:
814
+ data_files = read_data
815
+
816
+ return [os.path.join(self.maindir, f) for f in data_files]
817
+
818
+ def get_pbc(self):
819
+ pbc = self.get('boundary', ['p', 'p', 'p'])
820
+ return [v == 'p' for v in pbc]
821
+
822
+ def get_sampling_method(self):
823
+ fix_style = self.get('fix', [[''] * 3])[0][2]
824
+
825
+ sampling_method = (
826
+ 'langevin_dynamics' if 'langevin' in fix_style else 'molecular_dynamics'
827
+ )
828
+ return sampling_method, fix_style
829
+
830
+ def get_thermostat_settings(self):
831
+ fix = self.get('fix', [None])[0]
832
+ if fix is None:
833
+ return {}
834
+
835
+ try:
836
+ fix_style = fix[2]
837
+ except IndexError:
838
+ return {}
839
+
840
+ temp_unit = self.units.get('temperature', 1)
841
+ press_unit = self.units.get('pressure', 1)
842
+ time_unit = self.units.get('time', 1)
843
+
844
+ res = dict()
845
+ if fix_style.lower() == 'nvt':
846
+ try:
847
+ res['target_T'] = float(fix[5]) * temp_unit
848
+ res['thermostat_tau'] = float(fix[6]) * time_unit
849
+ except Exception:
850
+ pass
851
+
852
+ elif fix_style.lower() == 'npt':
853
+ try:
854
+ res['target_T'] = float(fix[5]) * temp_unit
855
+ res['thermostat_tau'] = float(fix[6]) * time_unit
856
+ res['target_P'] = float(fix[9]) * press_unit
857
+ res['barostat_tau'] = float(fix[10]) * time_unit
858
+ except Exception:
859
+ pass
860
+
861
+ elif fix_style.lower() == 'nph':
862
+ try:
863
+ res['target_P'] = float(fix[5]) * press_unit
864
+ res['barostat_tau'] = float(fix[6]) * time_unit
865
+ except Exception:
866
+ pass
867
+
868
+ elif fix_style.lower() == 'langevin':
869
+ try:
870
+ res['target_T'] = float(fix[4]) * temp_unit
871
+ res['langevin_gamma'] = float(fix[5]) * time_unit
872
+ except Exception:
873
+ pass
874
+
875
+ else:
876
+ self.logger.warning('Fix style not supported', data=dict(style=fix_style))
877
+
878
+ return res
879
+
880
+ def get_interactions(self):
881
+ styles_coeffs = []
882
+ for interaction in self._interactions:
883
+ styles = self.get('%s_style' % interaction, None)
884
+ if styles is None:
885
+ continue
886
+
887
+ if isinstance(styles[0], str):
888
+ styles = [styles]
889
+
890
+ for i in range(len(styles)):
891
+ if interaction == 'kspace':
892
+ coeff = [[float(c) for c in styles[i][1:]]]
893
+ style = styles[i][0]
894
+
895
+ else:
896
+ coeff = self.get('%s_coeff' % interaction)
897
+ style = ' '.join([str(si) for si in styles[i]])
898
+
899
+ styles_coeffs.append((style.strip(), coeff))
900
+
901
+ return styles_coeffs
902
+
903
+
904
+ class TrajParsers:
905
+ def __init__(self, parsers):
906
+ self._parsers = parsers
907
+ for parser in parsers:
908
+ parser.parse()
909
+
910
+ def __getitem__(self, index):
911
+ if self._parsers:
912
+ return self._parsers[index]
913
+
914
+ def eval(self, key, *args, **kwargs):
915
+ for parser in self._parsers:
916
+ parser_method = getattr(parser, key)
917
+ if parser_method is not None:
918
+ val = (
919
+ parser_method(*args, **kwargs) if args or kwargs else parser_method
920
+ )
921
+ if val is not None:
922
+ return val
923
+
924
+
925
+ class LammpsParser(MDParser):
926
+ def __init__(self):
927
+ super().__init__()
928
+ self.log_parser = LogParser()
929
+ self._traj_parser = TrajParser()
930
+ self._xyztraj_parser = XYZTrajParser()
931
+ self._mdanalysistraj_parser = MDAnalysisParser(
932
+ topology_format='DATA', format='LAMMPSDUMP'
933
+ )
934
+ self.data_parser = DataParser()
935
+ self.aux_log_parser = LogParser()
936
+ self._energy_mapping = {
937
+ 'e_pair': 'pair',
938
+ 'e_bond': 'bond',
939
+ 'e_angle': 'angle',
940
+ 'e_dihed': 'dihedral',
941
+ 'e_impro': 'improper',
942
+ 'e_coul': 'coulomb',
943
+ 'e_vdwl': 'van der Waals',
944
+ 'e_mol': 'molecular',
945
+ 'e_long': 'kspace long range',
946
+ 'e_tail': 'van der Waals long range',
947
+ 'kineng': 'kinetic',
948
+ 'poteng': 'potential',
949
+ }
950
+
951
+ def get_time_step(self):
952
+ time_unit = self.log_parser.units.get('time', None)
953
+ time_step = self.log_parser.get('timestep', [0], unit=time_unit)[0]
954
+ return time_step
955
+
956
+ def parse_thermodynamic_data(self):
957
+ sec_run = self.archive.run[-1]
958
+ # sec_system = sec_run.system
959
+
960
+ time_step = self.get_time_step()
961
+ thermo_data = self.log_parser.get_thermodynamic_data()
962
+ if thermo_data is None:
963
+ thermo_data = self.aux_log_parser.get_thermodynamic_data()
964
+ if not thermo_data:
965
+ thermo_data = {}
966
+ self.thermodynamics_steps = [int(n) for n in thermo_data.get('Step', [])]
967
+
968
+ if not thermo_data:
969
+ return
970
+
971
+ for step in self.thermodynamics_steps:
972
+ step_data = {
973
+ 'step': step,
974
+ 'time': step * time_step,
975
+ 'method_ref': sec_run.method[-1] if sec_run.method else None,
976
+ 'energy': {'contributions': []},
977
+ }
978
+ if step in self._trajectory_steps:
979
+ step_data['forces'] = (
980
+ dict(
981
+ total=dict(
982
+ value=self.traj_parsers.eval(
983
+ 'get_forces', self._trajectory_steps.index(step)
984
+ )
985
+ )
986
+ ),
987
+ )
988
+
989
+ data_n = self._thermodynamics_steps.index(step)
990
+ for key, val in thermo_data.items():
991
+ key = key.lower()
992
+ if (kind := self._energy_mapping.get(key)) is not None:
993
+ step_data['energy']['contributions'].append(
994
+ dict(kind=kind, value=val[data_n])
995
+ )
996
+ elif key == 'toteng':
997
+ step_data['energy']['current'] = dict(value=val[data_n])
998
+ step_data['energy']['total'] = dict(value=val[data_n])
999
+ elif key == 'press':
1000
+ step_data['pressure'] = val[data_n]
1001
+ elif key == 'temp':
1002
+ step_data['temperature'] = val[data_n]
1003
+ elif key == 'cpu':
1004
+ # approx time calc is dt / dstep
1005
+ max_step = len(self._thermodynamics_steps) - 1
1006
+ # calc time cannot be calculated for last iter, will be zero
1007
+ delta_time = float(val[min(data_n + 1, max_step)]) - float(
1008
+ val[data_n]
1009
+ )
1010
+ delta_step = (
1011
+ 1
1012
+ if data_n == max_step
1013
+ else self._thermodynamics_steps[data_n + 1] - step
1014
+ )
1015
+ step_data['time_calculation'] = delta_time * ureg.s / delta_step
1016
+ step_data['time_physical'] = (
1017
+ float(val[data_n]) * ureg.s + step_data['time_calculation']
1018
+ )
1019
+
1020
+ self.parse_thermodynamics_step(step_data)
1021
+
1022
+ def parse_workflow(self):
1023
+ sec_run = self.archive.run[-1]
1024
+ sec_calc = sec_run.get('calculation')
1025
+ sec_lammps = sec_run.x_lammps_section_control_parameters[-1]
1026
+
1027
+ units = self.log_parser.units
1028
+ if not units:
1029
+ self.logger.warning(
1030
+ 'Unit information not available. Assuming "real" units in workflow metainfo!'
1031
+ )
1032
+ units = get_unit('real')
1033
+ energy_conversion = ureg.convert(1.0, units.get('energy'), ureg.joule)
1034
+ force_conversion = ureg.convert(1.0, units.get('force'), ureg.newton)
1035
+ temperature_conversion = ureg.convert(
1036
+ 1.0, units.get('temperature'), ureg.kelvin
1037
+ )
1038
+ pressure_conversion = ureg.convert(1.0, units.get('pressure'), ureg.pascal)
1039
+
1040
+ minimization_stats = self.log_parser.get('minimization_stats', None)
1041
+ workflow = None
1042
+ if minimization_stats is not None:
1043
+ workflow = GeometryOptimization(
1044
+ method=GeometryOptimizationMethod(),
1045
+ results=GeometryOptimizationResults(),
1046
+ )
1047
+ workflow.method.type = 'atomic'
1048
+
1049
+ min_style = self.log_parser.get('min_style')
1050
+ min_style = min_style[0].lower() if min_style else 'none'
1051
+ min_style_map = {
1052
+ 'cg': 'polak_ribiere_conjugant_gradient',
1053
+ 'hftn': 'hessian_free_truncated_newton',
1054
+ 'sd': 'steepest_descent',
1055
+ 'quickmin': 'damped_dynamics',
1056
+ 'fire': 'damped_dynamics',
1057
+ 'spin': 'damped_dynamics',
1058
+ }
1059
+ value = min_style_map.get(
1060
+ min_style,
1061
+ [val for key, val in min_style_map.items() if key in min_style],
1062
+ )
1063
+ value = (
1064
+ value
1065
+ if not isinstance(value, list)
1066
+ else value[0]
1067
+ if len(value) != 0
1068
+ else None
1069
+ )
1070
+ workflow.method.method = value
1071
+
1072
+ minimization_stats = minimization_stats.split('\n')
1073
+ energy_index = [
1074
+ i
1075
+ for i, s in enumerate(minimization_stats)
1076
+ if 'Energy initial, next-to-last, final' in s
1077
+ ]
1078
+ if len(energy_index) != 0:
1079
+ energy_stats = minimization_stats[energy_index[0] + 1].split()
1080
+ workflow.results.final_energy_difference = (
1081
+ float(energy_stats[-1]) - float(energy_stats[-2])
1082
+ ) * energy_conversion
1083
+
1084
+ force_index = [
1085
+ i
1086
+ for i, s in enumerate(minimization_stats)
1087
+ if 'Force two-norm initial, final = 3167.24 0.509175' in s
1088
+ ]
1089
+ if len(force_index) != 0:
1090
+ force_stats = minimization_stats[force_index[0]].split('=')[1]
1091
+ force_stats = force_stats.split()
1092
+ workflow.results.final_force_maximum = (
1093
+ float(force_stats[-1]) * force_conversion
1094
+ )
1095
+
1096
+ minimize_parameters = self.log_parser.get('minimize')
1097
+ if not minimize_parameters:
1098
+ minimize_parameters = self.log_parser.get('minimize/kk')
1099
+ if minimize_parameters:
1100
+ workflow.method.optimization_steps_maximum = int(
1101
+ minimize_parameters[0][2]
1102
+ )
1103
+ workflow.method.convergence_tolerance_force_maximum = (
1104
+ minimize_parameters[0][1] * force_conversion
1105
+ )
1106
+ workflow.method.convergence_tolerance_energy_difference = (
1107
+ minimize_parameters[0][0] * energy_conversion
1108
+ )
1109
+
1110
+ energies = []
1111
+ steps = []
1112
+ for calc in sec_calc:
1113
+ val = calc.get('energy')
1114
+ energy = val.get('total') if val else None
1115
+ if energy:
1116
+ energies.append(energy.value.magnitude)
1117
+ step = calc.get('step')
1118
+ steps.append(step)
1119
+ workflow.results.energies = energies
1120
+ workflow.results.steps = steps
1121
+ workflow.results.optimization_steps = len(energies) + 1
1122
+ self.archive.workflow2 = workflow
1123
+
1124
+ else:
1125
+ method, results = {}, {}
1126
+ results['finished_normally'] = self.log_parser.get('finished') is not None
1127
+ dump_params = sec_lammps.x_lammps_inout_control_dump.split()
1128
+ if ',' in dump_params[3]:
1129
+ coordinate_save_frequency = dump_params[3].replace(',', '')
1130
+ else:
1131
+ coordinate_save_frequency = dump_params[3]
1132
+ method['coordinate_save_frequency'] = int(coordinate_save_frequency)
1133
+ method['n_steps'] = (len(sec_run.system) - 1) * method[
1134
+ 'coordinate_save_frequency'
1135
+ ]
1136
+ if (
1137
+ 'vx' in dump_params[7:]
1138
+ or 'vy' in dump_params[7:]
1139
+ or 'vz' in dump_params[7:]
1140
+ ):
1141
+ method['velocity_save_frequency'] = int(dump_params[3])
1142
+ if (
1143
+ 'fx' in dump_params[7:]
1144
+ or 'fy' in dump_params[7:]
1145
+ or 'fz' in dump_params[7:]
1146
+ ):
1147
+ method['force_save_frequency'] = int(dump_params[3])
1148
+ if sec_lammps.x_lammps_inout_control_thermo is not None:
1149
+ method['thermodynamics_save_frequency'] = int(
1150
+ sec_lammps.x_lammps_inout_control_thermo.split()[0]
1151
+ )
1152
+ # runstyle has 2 options: Velocity-Verlet (default) or rRESPA Multi-Timescale
1153
+ runstyle = sec_lammps.x_lammps_inout_control_runstyle
1154
+ if runstyle is not None:
1155
+ if 'respa' in runstyle.lower:
1156
+ method['integrator_type'] = 'rRESPA_multitimescale'
1157
+ else:
1158
+ method['integrator_type'] = 'velocity_verlet'
1159
+ else:
1160
+ method['integrator_type'] = 'velocity_verlet'
1161
+ integration_timestep = self.get_time_step()
1162
+ method['integration_timestep'] = integration_timestep
1163
+
1164
+ thermostat_parameters, barostat_parameters = {}, {}
1165
+ val = self.log_parser.get('fix', None)
1166
+ if val is not None:
1167
+ val_remove_duplicates = val if len(val) == 1 else []
1168
+ val_tmp = val[0]
1169
+ for i in range(1, len(val)):
1170
+ if val[i][:3] == val[i - 1][:3]:
1171
+ val_tmp = val[i]
1172
+ else:
1173
+ val_remove_duplicates.append(val_tmp)
1174
+ val_tmp = val[i]
1175
+ val_remove_duplicates.append(val_tmp)
1176
+ val = val_remove_duplicates
1177
+ for fix in val:
1178
+ fix = [str(i).lower() for i in fix]
1179
+ fix = np.array(fix)
1180
+ fix_group = fix[1]
1181
+ fix_style = fix[2]
1182
+
1183
+ if fix_group != 'all': # ignore any complex settings
1184
+ continue
1185
+
1186
+ reference_temperature = None
1187
+ coupling_constant = None
1188
+ if 'nvt' in fix_style or 'npt' in fix_style:
1189
+ thermostat_parameters['thermostat_type'] = 'nose_hoover'
1190
+ if 'temp' in fix:
1191
+ i_temp = np.where(fix == 'temp')[0]
1192
+ reference_temperature = float(fix[i_temp + 2]) # stop temp
1193
+ coupling_constant = (
1194
+ float(fix[i_temp + 3]) * integration_timestep
1195
+ )
1196
+ elif fix_style == 'temp/berendsen':
1197
+ thermostat_parameters['thermostat_type'] = 'berendsen'
1198
+ i_temp = 3
1199
+ reference_temperature = float(fix[i_temp + 2]) # stop temp
1200
+ coupling_constant = (
1201
+ float(fix[i_temp + 3]) * integration_timestep
1202
+ )
1203
+ elif fix_style == 'temp/csvr':
1204
+ thermostat_parameters['thermostat_type'] = 'velocity_rescaling'
1205
+ i_temp = 3
1206
+ reference_temperature = float(fix[i_temp + 2]) # stop temp
1207
+ coupling_constant = (
1208
+ float(fix[i_temp + 3]) * integration_timestep
1209
+ )
1210
+ elif fix_style == 'temp/csld':
1211
+ thermostat_parameters['thermostat_type'] = (
1212
+ 'velocity_rescaling_langevin'
1213
+ )
1214
+ i_temp = 3
1215
+ reference_temperature = float(fix[i_temp + 2]) # stop temp
1216
+ coupling_constant = (
1217
+ float(fix[i_temp + 3]) * integration_timestep
1218
+ )
1219
+ elif fix_style == 'langevin':
1220
+ thermostat_parameters['thermostat_type'] = 'langevin_schneider'
1221
+ i_temp = 3
1222
+ reference_temperature = float(fix[i_temp + 2]) # stop temp
1223
+ coupling_constant = (
1224
+ float(fix[i_temp + 3]) * integration_timestep
1225
+ )
1226
+ elif 'brownian' in fix_style:
1227
+ thermostat_parameters['thermostat_type'] = 'brownian'
1228
+ i_temp = 3
1229
+ reference_temperature = float(fix[i_temp + 2]) # stop temp
1230
+ # coupling_constant = # ignore multiple coupling parameters
1231
+ thermostat_parameters['reference_temperature'] = (
1232
+ reference_temperature * temperature_conversion
1233
+ )
1234
+ thermostat_parameters['coupling_constant'] = coupling_constant
1235
+
1236
+ barostat_type = None
1237
+ if 'npt' in fix_style or 'nph' in fix_style:
1238
+ coupling_constant = np.zeros(shape=(3, 3))
1239
+ reference_pressure = np.zeros(shape=(3, 3))
1240
+ compressibility = None
1241
+ barostat_type = 'nose_hoover'
1242
+ if 'iso' in fix:
1243
+ i_baro = np.where(fix == 'iso')[0]
1244
+ barostat_parameters['coupling_type'] = 'isotropic'
1245
+ np.fill_diagonal(coupling_constant, float(fix[i_baro + 3]))
1246
+ np.fill_diagonal(reference_pressure, float(fix[i_baro + 2]))
1247
+ else:
1248
+ barostat_parameters['coupling_type'] = 'anisotropic'
1249
+ if 'x' in fix:
1250
+ i_baro = np.where(fix == 'x')[0]
1251
+ coupling_constant[0, 0] = float(fix[i_baro + 3])
1252
+ reference_pressure[0, 0] = float(fix[i_baro + 2])
1253
+ if 'y' in fix:
1254
+ i_baro = np.where(fix == 'y')[0]
1255
+ coupling_constant[1, 1] = float(fix[i_baro + 3])
1256
+ reference_pressure[1, 1] = float(fix[i_baro + 2])
1257
+ if 'z' in fix:
1258
+ i_baro = np.where(fix == 'z')[0]
1259
+ coupling_constant[2, 2] = float(fix[i_baro + 3])
1260
+ reference_pressure[2, 2] = float(fix[i_baro + 2])
1261
+ if 'xy' in fix:
1262
+ i_baro = np.where(fix == 'xy')[0]
1263
+ coupling_constant[0, 1] = float(fix[i_baro + 3])
1264
+ coupling_constant[1, 0] = float(fix[i_baro + 3])
1265
+ reference_pressure[0, 1] = float(fix[i_baro + 2])
1266
+ reference_pressure[1, 0] = float(fix[i_baro + 2])
1267
+ if 'yz' in fix:
1268
+ i_baro = np.where(fix == 'yz')[0]
1269
+ coupling_constant[1, 2] = float(fix[i_baro + 3])
1270
+ coupling_constant[2, 1] = float(fix[i_baro + 3])
1271
+ reference_pressure[1, 2] = float(fix[i_baro + 2])
1272
+ reference_pressure[2, 1] = float(fix[i_baro + 2])
1273
+ if 'xz' in fix:
1274
+ i_baro = np.where(fix == 'xz')[0]
1275
+ coupling_constant[0, 3] = float(fix[i_baro + 3])
1276
+ coupling_constant[3, 0] = float(fix[i_baro + 3])
1277
+ reference_pressure[0, 3] = float(fix[i_baro + 2])
1278
+ reference_pressure[3, 0] = float(fix[i_baro + 2])
1279
+ barostat_parameters['reference_pressure'] = (
1280
+ reference_pressure * pressure_conversion
1281
+ ) # stop pressure
1282
+ barostat_parameters['coupling_constant'] = (
1283
+ coupling_constant * integration_timestep
1284
+ )
1285
+ barostat_parameters['compressibility'] = compressibility
1286
+
1287
+ if fix_style == 'press/berendsen':
1288
+ barostat_type = 'berendsen'
1289
+ if 'iso' in fix:
1290
+ i_baro = np.where(fix == 'iso')[0]
1291
+ barostat_parameters['coupling_type'] = 'isotropic'
1292
+ np.fill_diagonal(coupling_constant, float(fix[i_baro + 3]))
1293
+ elif 'aniso' in fix:
1294
+ i_baro = np.where(fix == 'aniso')[0]
1295
+ barostat_parameters['coupling_type'] = 'anisotropic'
1296
+ coupling_constant[:3] += 1.0
1297
+ coupling_constant[:3] *= float(fix[i_baro + 3])
1298
+ else:
1299
+ barostat_parameters['coupling_type'] = 'anisotropic'
1300
+ if 'x' in fix:
1301
+ i_baro = np.where(fix == 'x')[0]
1302
+ coupling_constant[0] = float(fix[i_baro + 3])
1303
+ if 'y' in fix:
1304
+ i_baro = np.where(fix == 'y')[0]
1305
+ coupling_constant[1] = float(fix[i_baro + 3])
1306
+ if 'z' in fix:
1307
+ i_baro = np.where(fix == 'z')[0]
1308
+ coupling_constant[2] = float(fix[i_baro + 3])
1309
+ if 'couple' in fix:
1310
+ i_baro = np.where(fix == 'couple')[0]
1311
+ couple = fix[i_baro]
1312
+ if couple == 'xyz':
1313
+ barostat_parameters['coupling_type'] = 'isotropic'
1314
+ elif couple == 'xy' or couple == 'yz' or couple == 'xz':
1315
+ barostat_parameters['coupling_type'] = 'anisotropic'
1316
+ barostat_parameters['reference_pressure'] = (
1317
+ float(fix[i_baro + 2]) * pressure_conversion
1318
+ ) # stop pressure
1319
+ barostat_parameters['coupling_constant'] = (
1320
+ np.ones(shape=(3, 3))
1321
+ * float(fix[i_baro + 3])
1322
+ * integration_timestep
1323
+ )
1324
+ barostat_parameters['barostat_type'] = barostat_type
1325
+
1326
+ if thermostat_parameters:
1327
+ method['thermodynamic_ensemble'] = (
1328
+ 'NPT' if barostat_type == 'nose_hoover' else 'NVT'
1329
+ )
1330
+ elif barostat_type == 'nose_hoover':
1331
+ method['thermodynamic_ensemble'] = 'NPH'
1332
+ else:
1333
+ method['thermodynamic_ensemble'] = 'NVE'
1334
+
1335
+ method['thermostat_parameters'] = thermostat_parameters
1336
+ method['barostat_parameters'] = barostat_parameters
1337
+
1338
+ self.parse_md_workflow(dict(method=method, results=results))
1339
+
1340
+ def parse_system(self):
1341
+ sec_run = self.archive.run[-1]
1342
+
1343
+ n_traj = self.traj_parsers.eval('n_frames')
1344
+ if n_traj is None:
1345
+ return
1346
+
1347
+ self.n_atoms = [self.traj_parsers.eval('get_n_atoms', n) for n in range(n_traj)]
1348
+ self.trajectory_steps = [
1349
+ step
1350
+ for n in range(n_traj)
1351
+ if (step := self.traj_parsers.eval('get_step', n)) is not None
1352
+ ]
1353
+
1354
+ units = self.log_parser.units
1355
+
1356
+ def apply_unit(value, unit):
1357
+ if not hasattr(value, 'units'):
1358
+ value = value * units.get(unit, 1)
1359
+ return value
1360
+
1361
+ def get_composition(children_names):
1362
+ children_count_tup = np.unique(children_names, return_counts=True)
1363
+ formula = ''.join(
1364
+ [f'{name}({count})' for name, count in zip(*children_count_tup)]
1365
+ )
1366
+ return formula
1367
+
1368
+ for step in self.trajectory_steps:
1369
+ traj_n = self._trajectory_steps.index(step)
1370
+ lattice_vectors = self.traj_parsers.eval('get_lattice_vectors', traj_n)
1371
+ if lattice_vectors is not None:
1372
+ lattice_vectors = apply_unit(lattice_vectors, 'distance')
1373
+ velocities = self.traj_parsers.eval('get_velocities', traj_n)
1374
+ if velocities is not None:
1375
+ velocities = apply_unit(velocities, 'velocity')
1376
+ bond_list = []
1377
+ if traj_n == 0: # TODO add references to the bond list for other steps
1378
+ bond_list = get_bond_list_from_model_contributions(
1379
+ sec_run, method_index=-1, model_index=-1
1380
+ )
1381
+ self.parse_trajectory_step(
1382
+ {
1383
+ 'atoms': {
1384
+ 'n_atoms': self.traj_parsers.eval('get_n_atoms', traj_n),
1385
+ 'lattice_vectors': lattice_vectors,
1386
+ 'periodic': self.traj_parsers.eval('get_pbc', traj_n),
1387
+ 'positions': apply_unit(
1388
+ self.traj_parsers.eval('get_positions', traj_n), 'distance'
1389
+ ),
1390
+ 'labels': self.traj_parsers.eval('get_atom_labels', traj_n),
1391
+ 'velocities': velocities,
1392
+ 'bond_list': bond_list if bond_list else None,
1393
+ }
1394
+ }
1395
+ )
1396
+
1397
+ if not sec_run.system:
1398
+ return
1399
+
1400
+ sec_system = sec_run.system[-1]
1401
+ # parse atomsgroup (moltypes --> molecules --> residues)
1402
+ atoms_info = self._mdanalysistraj_parser.get('atoms_info', None)
1403
+ if atoms_info is None:
1404
+ atoms_info = self.traj_parsers.eval('atoms_info')
1405
+ if isinstance(atoms_info, list):
1406
+ atoms_info = (
1407
+ atoms_info[0] if atoms_info else None
1408
+ ) # using info from the initial frame
1409
+ if atoms_info is not None:
1410
+ atoms_moltypes = np.array(atoms_info.get('moltypes', []))
1411
+ atoms_molnums = np.array(atoms_info.get('molnums', []))
1412
+ atoms_resids = np.array(atoms_info.get('resids', []))
1413
+ atoms_elements = np.array(atoms_info.get('elements', ['X'] * self.n_atoms))
1414
+ atoms_types = np.array(atoms_info.get('types', []))
1415
+ atom_labels = sec_system.atoms.get('labels')
1416
+ if 'X' in atoms_elements:
1417
+ atoms_elements = (
1418
+ np.array(atom_labels)
1419
+ if atom_labels and 'X' not in atom_labels
1420
+ else atoms_types
1421
+ )
1422
+ atoms_resnames = np.array(atoms_info.get('resnames', []))
1423
+ moltypes = np.unique(atoms_moltypes)
1424
+ for i_moltype, moltype in enumerate(moltypes):
1425
+ # Only add atomsgroup for initial system for now
1426
+ sec_molecule_group = AtomsGroup()
1427
+ sec_run.system[0].atoms_group.append(sec_molecule_group)
1428
+ sec_molecule_group.label = f'group_{moltype}'
1429
+ sec_molecule_group.type = 'molecule_group'
1430
+ sec_molecule_group.index = i_moltype
1431
+ sec_molecule_group.atom_indices = np.where(atoms_moltypes == moltype)[0]
1432
+ sec_molecule_group.n_atoms = len(sec_molecule_group.atom_indices)
1433
+ sec_molecule_group.is_molecule = False
1434
+ # mol_nums is the molecule identifier for each atom
1435
+ mol_nums = atoms_molnums[sec_molecule_group.atom_indices]
1436
+ moltype_count = np.unique(mol_nums).shape[0]
1437
+ sec_molecule_group.composition_formula = f'{moltype}({moltype_count})'
1438
+
1439
+ molecules = atoms_molnums
1440
+ for i_molecule, molecule in enumerate(
1441
+ np.unique(molecules[sec_molecule_group.atom_indices])
1442
+ ):
1443
+ sec_molecule = AtomsGroup()
1444
+ sec_molecule_group.atoms_group.append(sec_molecule)
1445
+ sec_molecule.index = i_molecule
1446
+ sec_molecule.atom_indices = np.where(molecules == molecule)[0]
1447
+ sec_molecule.n_atoms = len(sec_molecule.atom_indices)
1448
+ # use first particle to get the moltype
1449
+ # not sure why but this value is being cast to int, cast back to str
1450
+ sec_molecule.label = str(
1451
+ atoms_moltypes[sec_molecule.atom_indices[0]]
1452
+ )
1453
+ sec_molecule.type = 'molecule'
1454
+ sec_molecule.is_molecule = True
1455
+
1456
+ mol_resids = np.unique(atoms_resids[sec_molecule.atom_indices])
1457
+ n_res = mol_resids.shape[0]
1458
+ if n_res == 1:
1459
+ elements = atoms_elements[sec_molecule.atom_indices]
1460
+ sec_molecule.composition_formula = get_composition(elements)
1461
+ else:
1462
+ mol_resnames = atoms_resnames[sec_molecule.atom_indices]
1463
+ restypes = np.unique(mol_resnames)
1464
+ for i_restype, restype in enumerate(restypes):
1465
+ sec_monomer_group = AtomsGroup()
1466
+ sec_molecule.atoms_group.append(sec_monomer_group)
1467
+ restype_indices = np.where(atoms_resnames == restype)[0]
1468
+ sec_monomer_group.label = f'group_{restype}'
1469
+ sec_monomer_group.type = 'monomer_group'
1470
+ sec_monomer_group.index = i_restype
1471
+ sec_monomer_group.atom_indices = np.intersect1d(
1472
+ restype_indices, sec_molecule.atom_indices
1473
+ )
1474
+ sec_monomer_group.n_atoms = len(
1475
+ sec_monomer_group.atom_indices
1476
+ )
1477
+ sec_monomer_group.is_molecule = False
1478
+
1479
+ restype_resids = np.unique(
1480
+ atoms_resids[sec_monomer_group.atom_indices]
1481
+ )
1482
+ restype_count = restype_resids.shape[0]
1483
+ sec_monomer_group.composition_formula = (
1484
+ f'{restype}({restype_count})'
1485
+ )
1486
+ for i_res, res_id in enumerate(restype_resids):
1487
+ sec_residue = AtomsGroup()
1488
+ sec_monomer_group.atoms_group.append(sec_residue)
1489
+ sec_residue.index = i_res
1490
+ atom_indices = np.where(atoms_resids == res_id)[0]
1491
+ sec_residue.atom_indices = np.intersect1d(
1492
+ atom_indices, sec_monomer_group.atom_indices
1493
+ )
1494
+ sec_residue.n_atoms = len(sec_residue.atom_indices)
1495
+ sec_residue.label = str(restype)
1496
+ sec_residue.type = 'monomer'
1497
+ sec_residue.is_molecule = False
1498
+ elements = atoms_elements[sec_residue.atom_indices]
1499
+ sec_residue.composition_formula = get_composition(
1500
+ elements
1501
+ )
1502
+
1503
+ names = atoms_resnames[sec_molecule.atom_indices]
1504
+ ids = atoms_resids[sec_molecule.atom_indices]
1505
+ # filter for the first instance of each residue, as to not overcount
1506
+ __, ids_count = np.unique(ids, return_counts=True)
1507
+ # get the index of the first atom of each residue
1508
+ ids_firstatom = np.cumsum(ids_count)[:-1]
1509
+ # add the 0th index manually
1510
+ ids_firstatom = np.insert(ids_firstatom, 0, 0)
1511
+ names_firstatom = names[ids_firstatom]
1512
+ sec_molecule.composition_formula = get_composition(
1513
+ names_firstatom
1514
+ )
1515
+
1516
+ def parse_method(self):
1517
+ sec_run = self.archive.run[-1]
1518
+
1519
+ if self.traj_parsers[0].mainfile is None or self.data_parser.mainfile is None:
1520
+ return
1521
+
1522
+ if self.traj_parsers.eval('n_frames') is None:
1523
+ return
1524
+
1525
+ sec_method = Method()
1526
+ sec_run.method.append(sec_method)
1527
+ sec_force_field = ForceField()
1528
+ sec_method.force_field = sec_force_field
1529
+ sec_model = Model()
1530
+ sec_force_field.model.append(sec_model)
1531
+
1532
+ # Old parsing of method with text parser
1533
+ masses = self.data_parser.get('Masses', None)
1534
+ self.traj_parsers[0].masses = masses
1535
+ # @Landinesa: we should be able to set the atom masses with the TrajParser, but I don't quite understand how to use this.
1536
+ # Can you add the implementation here, and then we can make the MDA implementation below as a backup?
1537
+ # Can you also get the charges somehow?
1538
+
1539
+ # parse method with MDAnalysis (should be a backup for the charges and masses...but the interactions are most easily read from the MDA universe right now)
1540
+ n_atoms = self.traj_parsers.eval('get_n_atoms', 0)
1541
+ if n_atoms is not None:
1542
+ atoms_info = self._mdanalysistraj_parser.get('atoms_info', None)
1543
+ for n in range(n_atoms):
1544
+ sec_atom = AtomParameters()
1545
+ sec_method.atom_parameters.append(sec_atom)
1546
+ sec_atom.charge = atoms_info.get('charges', [None] * (n + 1))[n]
1547
+ sec_atom.mass = atoms_info.get('masses', [None] * (n + 1))[n]
1548
+
1549
+ # TODO address case types are numbered instead of giving atom labels (fix tests accordingly)
1550
+ interactions = self._mdanalysistraj_parser.get_interactions()
1551
+ # for interaction in interactions:
1552
+ # for key, val in interaction.items():
1553
+ # quantity_def = Interaction.m_def.all_quantities.get(key)
1554
+ # if quantity_def and quantity_def.shape:
1555
+ # # TODO reshape properly
1556
+ # interaction[key] = [val]
1557
+ self.parse_interactions(interactions, sec_model)
1558
+
1559
+ # Force Calculation Parameters
1560
+ sec_force_calculations = ForceCalculations()
1561
+ sec_force_field.force_calculations = sec_force_calculations
1562
+ for pairstyle in self.log_parser.get('pair_style', []):
1563
+ pairstyle_args = pairstyle[1:]
1564
+ pairstyle = pairstyle[0].lower()
1565
+ if (
1566
+ 'lj' in pairstyle and 'coul' not in pairstyle
1567
+ ): # only cover the simplest case
1568
+ sec_force_calculations.vdw_cutoff = (
1569
+ float(pairstyle_args[-1]) * ureg.nanometer
1570
+ )
1571
+ if 'coul' in pairstyle:
1572
+ if 'streitz' in pairstyle:
1573
+ cutoff = float(pairstyle_args[0])
1574
+ else:
1575
+ cutoff = float(pairstyle_args[-1])
1576
+ sec_force_calculations.coulomb_cutoff = cutoff * ureg.nanometer
1577
+ val = self.log_parser.get('kspace_style', None)
1578
+ if val is not None:
1579
+ kspacestyle = val[0][0].lower()
1580
+ if 'ewald' in kspacestyle:
1581
+ sec_force_calculations.coulomb_type = 'ewald'
1582
+ elif 'pppm' in kspacestyle:
1583
+ sec_force_calculations.coulomb_type = (
1584
+ 'particle_particle_particle_mesh'
1585
+ )
1586
+ elif 'msm' in kspacestyle:
1587
+ sec_force_calculations.coulomb_type = 'multilevel_summation'
1588
+
1589
+ sec_neighbor_searching = NeighborSearching()
1590
+ sec_force_calculations.neighbor_searching = sec_neighbor_searching
1591
+ val = self.log_parser.get('neighbor', None)
1592
+ if val is not None:
1593
+ neighbor = val[0][0] # just use the first instance for now
1594
+ vdw_cutoff = sec_force_calculations.vdw_cutoff
1595
+ if vdw_cutoff is not None:
1596
+ sec_neighbor_searching.neighbor_update_cutoff = (
1597
+ float(neighbor) * ureg.nanometer
1598
+ )
1599
+ sec_neighbor_searching.neighbor_update_cutoff += vdw_cutoff
1600
+ val = self.log_parser.get('neigh_modify', None)
1601
+ if val is not None:
1602
+ neighmodify = val[0] # just use the first instace for now
1603
+ neighmodify = np.array([str(i).lower() for i in neighmodify])
1604
+ if 'every' in neighmodify:
1605
+ index = np.where(neighmodify == 'every')[0]
1606
+ sec_neighbor_searching.neighbor_update_frequency = int(
1607
+ neighmodify[index + 1]
1608
+ )
1609
+
1610
+ def parse_input(self):
1611
+ sec_run = self.archive.run[-1]
1612
+ sec_input_output_files = x_lammps_section_input_output_files()
1613
+ sec_run.x_lammps_section_input_output_files.append(sec_input_output_files)
1614
+
1615
+ if self.data_parser.mainfile is not None:
1616
+ sec_input_output_files.x_lammps_inout_file_data = os.path.basename(
1617
+ self.data_parser.mainfile
1618
+ )
1619
+
1620
+ if self.traj_parsers[0].mainfile is not None:
1621
+ sec_input_output_files.x_lammps_inout_file_trajectory = os.path.basename(
1622
+ self.traj_parsers[0].mainfile
1623
+ )
1624
+
1625
+ sec_control_parameters = x_lammps_section_control_parameters()
1626
+ sec_run.x_lammps_section_control_parameters.append(sec_control_parameters)
1627
+ keys = self.log_parser._commands
1628
+ for key in keys:
1629
+ val = self.log_parser.get(key, None)
1630
+ if val is None:
1631
+ continue
1632
+ val = val[0] if len(val) == 1 else val
1633
+ key = (
1634
+ 'x_lammps_inout_control_%s'
1635
+ % key.replace('_', '').replace('/', '').lower()
1636
+ )
1637
+ if hasattr(sec_control_parameters, key):
1638
+ if isinstance(val, list):
1639
+ val = ' '.join([str(v) for v in val])
1640
+ setattr(sec_control_parameters, key, str(val))
1641
+
1642
+ def write_to_archive(self):
1643
+ self.log_parser.mainfile = self.mainfile
1644
+ self.log_parser.logger = self.logger
1645
+ self._traj_parser.logger = self.logger
1646
+ self._mdanalysistraj_parser.logger = self.logger
1647
+ self._xyztraj_parser.logger = self.logger
1648
+ self.data_parser.logger = self.logger
1649
+ # auxilliary log parser for thermo data
1650
+ self.aux_log_parser.logger = self.logger
1651
+ self.log_parser._units = None
1652
+ self._traj_parser._chemical_symbols = None
1653
+
1654
+ sec_run = Run()
1655
+ self.archive.run.append(sec_run)
1656
+
1657
+ # parse basic
1658
+ sec_run.program = Program(
1659
+ name='LAMMPS', version=self.log_parser.get('program_version', '')
1660
+ )
1661
+
1662
+ # parse data file associated with calculation
1663
+ data_files = self.log_parser.get_data_files()
1664
+ if len(data_files) > 1:
1665
+ self.logger.warning('Multiple data files are specified')
1666
+ if data_files:
1667
+ self.data_parser.mainfile = data_files[0]
1668
+
1669
+ # parse trajectorty file associated with calculation
1670
+ traj_files = self.log_parser.get_traj_files()
1671
+ if len(traj_files) > 1:
1672
+ self.logger.warning('Multiple traj files are specified')
1673
+
1674
+ parsers = []
1675
+ for n, traj_file in enumerate(traj_files):
1676
+ # parser initialization for each traj file cannot be avoided as there are
1677
+ # cases where traj files can share the same parser
1678
+ file_type = self.log_parser.get(
1679
+ 'dump', [[1, 'all', traj_file[-3:]]] * (n + 1)
1680
+ )[n][2]
1681
+ if file_type == 'dcd' and data_files:
1682
+ traj_parser = MDAnalysisParser(topology_format='DATA', format='DCD')
1683
+ traj_parser.mainfile = data_files[0]
1684
+ traj_parser.auxilliary_files = [traj_file]
1685
+ self._mdanalysistraj_parser = traj_parser
1686
+ elif file_type == 'xyz' and data_files:
1687
+ traj_parser = MDAnalysisParser(topology_format='DATA', format='XYZ')
1688
+ traj_parser.mainfile = data_files[0]
1689
+ traj_parser.auxilliary_files = [traj_file]
1690
+ self._mdanalysistraj_parser = traj_parser
1691
+ elif file_type == 'custom' and data_files:
1692
+ custom_options = self.log_parser.get('dump')[n][5:]
1693
+ custom_options = [
1694
+ option.replace('xu', 'x') for option in custom_options
1695
+ ]
1696
+ custom_options = [
1697
+ option.replace('yu', 'y') for option in custom_options
1698
+ ]
1699
+ custom_options = [
1700
+ option.replace('zu', 'z') for option in custom_options
1701
+ ]
1702
+ custom_options = ' '.join(custom_options)
1703
+ traj_parser = MDAnalysisParser(
1704
+ topology_format='DATA',
1705
+ format='LAMMPSDUMP',
1706
+ atom_style=custom_options,
1707
+ )
1708
+ if data_files:
1709
+ traj_parser.mainfile = data_files[0]
1710
+ traj_parser.auxilliary_files = [traj_file]
1711
+ # try to check if MDAnalysis can construct the universe or at least parse
1712
+ # the atoms, otherwise will fall back to TrajParser
1713
+ if traj_parser.universe is None or 'X' in traj_parser.get(
1714
+ 'atoms_info', {}
1715
+ ).get('names', []):
1716
+ # mda necessary to calculate rdf and atomsgroup
1717
+ if n == 0:
1718
+ self._mdanalysistraj_parser = traj_parser
1719
+ traj_parser = TrajParser()
1720
+ traj_parser.mainfile = traj_file
1721
+ else:
1722
+ traj_parser = TrajParser()
1723
+ traj_parser.mainfile = traj_file
1724
+ # TODO provide support for other file types
1725
+ parsers.append(traj_parser)
1726
+
1727
+ self.traj_parsers = TrajParsers(parsers)
1728
+ if self.traj_parsers[0] is None:
1729
+ return
1730
+
1731
+ # parse data from auxiliary log file
1732
+ if self.log_parser.get('log') is not None:
1733
+ self.aux_log_parser.mainfile = os.path.join(
1734
+ self.log_parser.maindir, self.log_parser.get('log')[0]
1735
+ )
1736
+ # we assign units here which is read from log parser
1737
+ self.aux_log_parser._units = self.log_parser.units
1738
+
1739
+ self.parse_method()
1740
+
1741
+ self.parse_system()
1742
+
1743
+ # include input controls from log file
1744
+ self.parse_input()
1745
+
1746
+ # parse thermodynamic data from log file
1747
+ self.parse_thermodynamic_data()
1748
+
1749
+ self.parse_workflow()
1750
+
1751
+ self._mdanalysistraj_parser.close()
1752
+ for parser in parsers:
1753
+ parser.close()