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,808 @@
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 logging
21
+ import numpy as np
22
+
23
+ from nomad.units import ureg
24
+ from nomad.parsing.file_parser import TextParser, Quantity, DataTextParser
25
+ from runschema.run import Run, Program
26
+ from runschema.method import Method, TB, xTB, ForceField, Model, Interaction
27
+ from runschema.system import System, Atoms
28
+ from runschema.calculation import (
29
+ Calculation,
30
+ Energy,
31
+ EnergyEntry,
32
+ Forces,
33
+ ForcesEntry,
34
+ Stress,
35
+ StressEntry,
36
+ Charges,
37
+ ChargesValue,
38
+ )
39
+ from simulationworkflowschema import (
40
+ GeometryOptimization,
41
+ GeometryOptimizationMethod,
42
+ SinglePoint,
43
+ )
44
+ from atomisticparsers.utils import MDParser
45
+ from atomisticparsers.bopfox.metainfo.bopfox import (
46
+ x_bopfox_onsite_levels,
47
+ x_bopfox_onsite_levels_value,
48
+ )
49
+
50
+
51
+ re_f = r'[-+]?\d*\.\d*(?:[Ee][-+]\d+)?'
52
+ re_n = r'[\n\r]'
53
+
54
+
55
+ class ModelsbxParser(TextParser):
56
+ def init_quantities(self):
57
+ def to_parameters(val_in):
58
+ parameters = dict()
59
+ for val in val_in.strip().splitlines():
60
+ if val.startswith('!'):
61
+ continue
62
+ val = val.split('=')
63
+ if len(val) == 2:
64
+ val[1] = val[1].split()
65
+ parameters[val[0].strip().lower()] = (
66
+ val[1][0] if len(val[1]) == 1 else val[1]
67
+ )
68
+ return parameters
69
+
70
+ self._quantities = [
71
+ Quantity(
72
+ 'model',
73
+ rf'(el *= *\w+ *{re_n}[\s\S]+?)(?:{re_n} *mod|\Z)',
74
+ repeats=True,
75
+ sub_parser=TextParser(
76
+ quantities=[
77
+ Quantity('name', r'el *= *(\S+)', dtype=str),
78
+ Quantity(
79
+ 'parameters',
80
+ r'((?:[\w!]+ *= *\w+\s+)+)',
81
+ str_operation=to_parameters,
82
+ ),
83
+ Quantity(
84
+ 'atom',
85
+ r'([aA]tom *= *[A-Z]\S*[\s\S]+?(?:[\w\!]+ *= *[\w\. \-\+]+\s+)+)',
86
+ repeats=True,
87
+ str_operation=to_parameters,
88
+ ),
89
+ Quantity(
90
+ 'bond',
91
+ r'([bB]ond *= *[A-Z]\S* +[A-Z]\S*[\s\S]+?(?:[\w\!]+ *= *[\w\. \-\+]+\s+)+)',
92
+ repeats=True,
93
+ str_operation=to_parameters,
94
+ ),
95
+ ]
96
+ ),
97
+ )
98
+ ]
99
+
100
+
101
+ class StrucbxParser(TextParser):
102
+ def init_quantities(self):
103
+ def to_magnetisation(val_in):
104
+ val = val_in.strip().splitlines()
105
+ magnetisation = val[0].lower().startswith('t')
106
+ values = np.array(
107
+ [v.strip().split() for v in val[1:]], dtype=np.dtype(np.float64)
108
+ )
109
+ return magnetisation, values
110
+
111
+ self._quantities = [
112
+ Quantity(
113
+ 'lattice_constant', rf'[aA][lL][aA][tT] *= *({re_f})', dtype=np.float64
114
+ ),
115
+ Quantity(
116
+ 'lattice_vectors',
117
+ rf'[aA]\d+ *= *({re_f} +{re_f} +{re_f})',
118
+ repeats=True,
119
+ dtype=np.dtype(np.float64),
120
+ ),
121
+ Quantity('coordinate_type', r'[cC][oO][oO][rR][dD] *= *(\S+)', dtype=str),
122
+ Quantity(
123
+ 'label_position',
124
+ rf'{re_n} *([A-Z][a-z]* +{re_f} +{re_f} +{re_f})',
125
+ repeats=True,
126
+ ),
127
+ Quantity(
128
+ 'magnetisation',
129
+ rf'[mM][aA][gG][nN][eE][tT][iI][sS][aA][tT][iI][oO][nN] *= *(\S+[\s\d\.]+)',
130
+ str_operation=to_magnetisation,
131
+ ),
132
+ ]
133
+
134
+ # def get_positions(self):
135
+ # positions = np.array([v[1:4] for v in self.get('label_position', [])])
136
+ # if self.get('coordinate_type').lower().startswith('d'):
137
+ # # positions are scaled by lattice vectors
138
+ # if self.lattice_vectors is not None:
139
+ # positions = np.dot(positions, self.lattice_vectors)
140
+ # return positions
141
+
142
+
143
+ # we do not use MDA for xyz file in order to read stress and forces data which are not
144
+ # necessarily printed in mainfile
145
+ class XYZParser(TextParser):
146
+ def init_quantities(self):
147
+ def to_frame(val_in):
148
+ val = val_in.strip().splitlines()
149
+ n_atoms = int(val[0])
150
+ md = 'fs' in val[1]
151
+ step = float(val[1].split()[0])
152
+ labels = []
153
+ positions = np.zeros((n_atoms, 3))
154
+ constraints = []
155
+ energies = np.zeros(n_atoms)
156
+ forces = np.zeros((n_atoms, 3))
157
+ stresses = np.zeros((n_atoms, 6))
158
+ for n, line in enumerate(val[2 : 2 + n_atoms]):
159
+ line = line.split()
160
+ labels.append(line[0])
161
+ positions[n] = line[1:4]
162
+ constraints.append(list(line[4]))
163
+ if md:
164
+ forces[n] = line[5:8]
165
+ else:
166
+ energies[n] = line[5]
167
+ forces[n] = line[6:9]
168
+ stresses[n] = line[9:15]
169
+ return dict(
170
+ n_atoms=n_atoms,
171
+ step=step,
172
+ labels=labels,
173
+ positions=positions,
174
+ constraints=constraints,
175
+ energies_total=energies,
176
+ forces_total=forces,
177
+ stresses_total=stresses,
178
+ )
179
+
180
+ self._quantities = [
181
+ Quantity(
182
+ 'frame',
183
+ rf'(\d+\s+\d+.*?\s+(?:[A-Z][a-z]* +{re_f} +{re_f} +{re_f}.+\s+)+)',
184
+ repeats=True,
185
+ str_operation=to_frame,
186
+ )
187
+ ]
188
+
189
+
190
+ class InfoxParser(TextParser):
191
+ def init_quantities(self):
192
+ self._quantities = [
193
+ Quantity(
194
+ 'parameter',
195
+ r'(\w+ *= *.+)',
196
+ repeats=True,
197
+ str_operation=lambda x: [v.strip() for v in x.split('=')],
198
+ )
199
+ ]
200
+
201
+ def get_parameters(self):
202
+ return {v[0].lower(): v[1] for v in self.get('parameter', [])}
203
+
204
+
205
+ class MainfileParser(TextParser):
206
+ def init_quantities(self):
207
+ calc_quantities = [
208
+ Quantity(
209
+ 'energy',
210
+ r'(?:Contributions to the Energy|Energies)\s+\=+([\s\S]+?)\={50}',
211
+ sub_parser=TextParser(
212
+ quantities=[
213
+ Quantity(
214
+ 'contribution',
215
+ r'(U_\w+.+?\( *atom = +1 *\)[\s\S]+?U_\w+/atom.+)',
216
+ repeats=True,
217
+ sub_parser=TextParser(
218
+ quantities=[
219
+ Quantity('type', r'U_(\w+)', dtype=str),
220
+ Quantity(
221
+ 'atomic',
222
+ rf'atom += +\d+ \).+?({re_f})',
223
+ repeats=True,
224
+ dtype=np.float64,
225
+ ),
226
+ Quantity(
227
+ 'total',
228
+ rf'U_\w+/atom.+?({re_f})',
229
+ dtype=np.float64,
230
+ ),
231
+ ]
232
+ ),
233
+ )
234
+ ]
235
+ ),
236
+ ),
237
+ Quantity(
238
+ 'forces',
239
+ r'(?:Contributions to the Forces|Forces \(Fx,Fy,Fz,x,y,z\))\s+\=+([\s\S]+?)\={50}',
240
+ sub_parser=TextParser(
241
+ quantities=[
242
+ Quantity(
243
+ 'contribution',
244
+ r'(FBOP \(\w+ *\) +1 +[\s\S]+?)\-{50}',
245
+ repeats=True,
246
+ sub_parser=TextParser(
247
+ quantities=[
248
+ Quantity('type', r'FBOP \((\w+)', dtype=str),
249
+ Quantity(
250
+ 'atomic',
251
+ rf'\) +\d+ +({re_f} +{re_f} +{re_f})',
252
+ repeats=True,
253
+ dtype=np.dtype(np.float64),
254
+ ),
255
+ ]
256
+ ),
257
+ )
258
+ ]
259
+ ),
260
+ ),
261
+ Quantity(
262
+ 'stress',
263
+ r'(?:Contributions for the stresses|stresses \(11,22,33,23,13,12\))\s+\=+([\s\S]+?)\={50}',
264
+ sub_parser=TextParser(
265
+ quantities=[
266
+ Quantity(
267
+ 'total',
268
+ rf'sum\(stress\)/volume +({re_f} +{re_f} +{re_f} +{re_f} +{re_f} +{re_f})',
269
+ dtype=np.dtype(np.float64),
270
+ ),
271
+ Quantity(
272
+ 'contribution',
273
+ r'(stress \(\w+ *\) +1 +[\s\S]+?)\-{50}',
274
+ repeats=True,
275
+ sub_parser=TextParser(
276
+ quantities=[
277
+ Quantity('type', r'stress \((\w+)', dtype=str),
278
+ Quantity(
279
+ 'atomic',
280
+ rf'\) +\d+ +({re_f} +{re_f} +{re_f} +{re_f} +{re_f} +{re_f})',
281
+ repeats=True,
282
+ dtype=np.dtype(np.float64),
283
+ ),
284
+ ]
285
+ ),
286
+ ),
287
+ ]
288
+ ),
289
+ ),
290
+ Quantity('energy_fermi', rf'E_Fermi +.+?({re_f})', dtype=np.float64),
291
+ Quantity(
292
+ 'charges',
293
+ r'(?:Charge terms|Charges)\s+\=+([\s\S]+?)\={50}',
294
+ sub_parser=TextParser(
295
+ quantities=[
296
+ Quantity(
297
+ 'n_electrons',
298
+ rf'Nelec \( atom = +\d+ \).+?({re_f})',
299
+ dtype=np.float64,
300
+ repeats=True,
301
+ ),
302
+ Quantity(
303
+ 'charge',
304
+ rf'Charge \( atom = +\d+ \).+?({re_f})',
305
+ dtype=np.float64,
306
+ repeats=True,
307
+ ),
308
+ ]
309
+ ),
310
+ ),
311
+ Quantity(
312
+ 'magnetic_moments',
313
+ r'Magnetic moments\s+\=+([\s\S]+?)\={50}',
314
+ sub_parser=TextParser(
315
+ quantities=[
316
+ Quantity(
317
+ 'mag_mom',
318
+ rf'Mag_mom \( atom = +(\d+), orbital = +((?:s|p|d)) \) +({re_f}) +({re_f}) +({re_f})',
319
+ repeats=True,
320
+ )
321
+ ]
322
+ ),
323
+ ),
324
+ Quantity(
325
+ 'onsite_levels',
326
+ r'Onsite levels\s+\=+([\s\S]+?)\={50}',
327
+ sub_parser=TextParser(
328
+ quantities=[
329
+ Quantity(
330
+ 'energy',
331
+ rf'E((?:s|p|d)) \( atom = +(\d+), spin = \d+ \) +({re_f})',
332
+ repeats=True,
333
+ )
334
+ ]
335
+ ),
336
+ ),
337
+ ]
338
+
339
+ self._quantities = [
340
+ Quantity(
341
+ 'program_version',
342
+ r'BOPfox \(v (\S+)\) (rev\. \d+)',
343
+ dtype=str,
344
+ flatten=False,
345
+ ),
346
+ Quantity(
347
+ 'simulation',
348
+ r'(\w+ +\: +\S+ +\( *\S+ *\)\s+[\s\S]+?)init\: N\(',
349
+ sub_parser=TextParser(
350
+ quantities=[
351
+ Quantity(
352
+ 'parameter',
353
+ r'(\w+) +\: +(\S+) +\( *(\S+) *\)\s+',
354
+ repeats=True,
355
+ )
356
+ ]
357
+ ),
358
+ ),
359
+ Quantity(
360
+ 'lattice_vectors',
361
+ rf'cell\(\:,\d+\) \: +({re_f}) +({re_f}) +({re_f})',
362
+ repeats=True,
363
+ dtype=np.dtype(np.float64),
364
+ ),
365
+ Quantity(
366
+ 'label_position',
367
+ rf'init\: atom/type/pos/fix\: +\d+ +([A-Z]\S*) +({re_f}) +({re_f}) +({re_f}) +([ FT]+)',
368
+ repeats=True,
369
+ ),
370
+ Quantity(
371
+ 'n_atoms',
372
+ r'Atoms in cell/cluster\: +(\d+) +(\d+)',
373
+ dtype=np.dtype(np.int32),
374
+ ),
375
+ Quantity(
376
+ 'relaxation',
377
+ r'(relax\: [\s\S]+?(?:relax\: cycle finished|\Z))',
378
+ sub_parser=TextParser(
379
+ quantities=[
380
+ Quantity(
381
+ 'cycle',
382
+ rf'( \d+ +{re_f} +{re_f} +\d+ *{re_n}[\s\S]+?)(?:relax\: |\Z)',
383
+ repeats=True,
384
+ sub_parser=TextParser(quantities=calc_quantities),
385
+ )
386
+ ]
387
+ ),
388
+ ),
389
+ Quantity('md_column_names', r'col \d+\: (.+?) +\[', repeats=True),
390
+ ] + calc_quantities
391
+
392
+ def get_simulation_parameters(self):
393
+ return {
394
+ v[0]: (v[2] if v[2] != 'none' else None) if v[1] == '--' else v[1]
395
+ for v in self.get('simulation', {}).get('parameter', [])
396
+ }
397
+
398
+
399
+ class BOPfoxParser(MDParser):
400
+ def __init__(self):
401
+ self.mainfile_parser = MainfileParser()
402
+ self.strucbx_parser = StrucbxParser()
403
+ self.xyz_parser = XYZParser()
404
+ self.modelsbx_parser = ModelsbxParser()
405
+ self.dat_parser = DataTextParser()
406
+ self.infox_parser = InfoxParser()
407
+ self._metainfo_map = {
408
+ 'binding': 'total',
409
+ 'coulomb': 'electrostatic',
410
+ 'ionic': 'nuclear_repulsion',
411
+ 'total': 'total',
412
+ }
413
+ super().__init__()
414
+
415
+ def write_to_archive(self) -> None:
416
+ self.mainfile_parser.logger = self.logger
417
+ self.mainfile_parser.mainfile = self.mainfile
418
+ self.strucbx_parser.logger = self.logger
419
+ self.xyz_parser.logger = self.logger
420
+ self.modelsbx_parser.logger = self.logger
421
+ self.dat_parser.logger = self.logger
422
+ self.infox_parser.logger = self.logger
423
+ self.maindir = os.path.dirname(self.mainfile)
424
+
425
+ sec_run = Run()
426
+ self.archive.run.append(sec_run)
427
+ sec_run.program = Program(
428
+ name='BOPfox', version=self.mainfile_parser.get('program_version')
429
+ )
430
+
431
+ sec_method = Method()
432
+ sec_run.method.append(sec_method)
433
+ parameters = self.mainfile_parser.get_simulation_parameters()
434
+ sec_method.x_bopfox_simulation_parameters = parameters
435
+ # force field parameters
436
+ self.modelsbx_parser.mainfile = os.path.join(
437
+ self.maindir, parameters.get('modelfile', 'models.bx')
438
+ )
439
+ for model in self.modelsbx_parser.get('model', []):
440
+ # pick out only the model indicated in parameters
441
+ if model.name == parameters.get('model'):
442
+ # bop uses a tight-binding model
443
+ tb = model.parameters.get('version', 'bop').lower() in [
444
+ 'bop',
445
+ 'tight-binding',
446
+ ]
447
+ if tb:
448
+ sec_model = xTB()
449
+ sec_method.tb = TB(xtb=sec_model)
450
+ else:
451
+ sec_model = Model()
452
+ sec_method.force_field = ForceField(model=[sec_model])
453
+
454
+ sec_model.name = model.name
455
+ sec_model.x_bopfox_parameters = model.parameters
456
+ # interaction between each bond pair
457
+ for bond in model.get('bond', []):
458
+ # functional terms for each contribution
459
+ for key, val in bond.items():
460
+ key = key.lower()
461
+ if tb:
462
+ if (
463
+ key.endswith('sigma')
464
+ or key.endswith('pi')
465
+ or key.endswith('delta')
466
+ ):
467
+ sec_model.hamiltonian.append(
468
+ Interaction(
469
+ name=key,
470
+ functional_form=val[0],
471
+ parameters=val[1:],
472
+ atom_labels=[bond.get('bond')],
473
+ x_bopfox_valence=bond.get('valence'),
474
+ x_bopfox_cutoff=bond.get('rcut'),
475
+ x_bopfox_dcutoff=bond.get('dcut'),
476
+ x_bopfox_chargetransfer=bond.get(
477
+ 'chargetransfer'
478
+ ),
479
+ )
480
+ )
481
+ if key.endswith('overlap'):
482
+ sec_model.overlap.append(
483
+ Interaction(
484
+ name=key,
485
+ functional_form=val[0],
486
+ parameters=val[1:],
487
+ atom_labels=bond.get('bond'),
488
+ x_bopfox_valence=bond.get('valence'),
489
+ x_bopfox_cutoff=bond.get('rcut'),
490
+ x_bopfox_dcutoff=bond.get('dcut'),
491
+ )
492
+ )
493
+ elif key.startswith('rep'):
494
+ sec_model.repulsion.append(
495
+ Interaction(
496
+ name=key,
497
+ functional_form=val[0],
498
+ parameters=val[1:],
499
+ atom_labels=[bond.get('bond')],
500
+ x_bopfox_cutoff=bond.get('r2cut'),
501
+ x_bopfox_dcutoff=bond.get('d2cut'),
502
+ )
503
+ )
504
+ else:
505
+ if key.startswith('rep'):
506
+ sec_model.contributions.append(
507
+ Interaction(
508
+ name=key,
509
+ functional_form=val[0],
510
+ parameters=val[1:],
511
+ atom_labels=[bond.get('bond')],
512
+ x_bopfox_cutoff=bond.get('r2cut'),
513
+ x_bopfox_dcutoff=bond.get('d2cut'),
514
+ )
515
+ )
516
+
517
+ def parse_system(source, target=None):
518
+ if source is None:
519
+ return
520
+
521
+ label_position = source.get('label_position')
522
+ lattice_vectors = source.get('lattice_vectors')
523
+ if lattice_vectors is not None:
524
+ lattice_vectors = np.array(lattice_vectors) * source.get(
525
+ 'lattice_constant', 1.0
526
+ )
527
+ if label_position is not None:
528
+ labels = [v[0] for v in label_position]
529
+ positions = np.array([v[1:4] for v in label_position])
530
+ if source.get('coordinate_type', '').lower().startswith('d'):
531
+ # positions are scaled by lattice vectors
532
+ if lattice_vectors is not None:
533
+ positions = np.dot(positions, lattice_vectors)
534
+ else:
535
+ labels = source.get('labels')
536
+ positions = source.get('positions')
537
+
538
+ if positions is None:
539
+ return
540
+
541
+ sec_system = System()
542
+ sec_run.system.append(sec_system) if target is None else target
543
+ sec_system.atoms = Atoms(labels=labels, positions=positions * ureg.angstrom)
544
+ if lattice_vectors is not None:
545
+ sec_system.atoms.lattice_vectors = lattice_vectors * ureg.angstrom
546
+
547
+ return sec_system
548
+
549
+ def parse_calculation(source, target=None):
550
+ sec_calc = Calculation()
551
+ sec_run.calculation.append(sec_calc) if target is None else target
552
+
553
+ # energy
554
+ n_atoms = self.mainfile_parser.get('n_atoms', [1, 1])[0]
555
+ if source.get('energy') is not None:
556
+ sec_energy = Energy()
557
+ sec_calc.energy = sec_energy
558
+ for contribution in source.energy.get('contribution', []):
559
+ name = self._metainfo_map.get(contribution.type)
560
+ energy_entry = EnergyEntry(
561
+ value=contribution.total * ureg.eV * n_atoms,
562
+ values_per_atom=contribution.atomic * ureg.eV,
563
+ )
564
+ if name is None:
565
+ energy_entry.kind = contribution.type
566
+ sec_energy.contributions.append(energy_entry)
567
+ else:
568
+ setattr(sec_energy, name, energy_entry)
569
+ if source.energy_fermi is not None:
570
+ sec_energy.fermi = source.energy_fermi * ureg.eV
571
+
572
+ # forces
573
+ if source.get('forces') is not None:
574
+ sec_forces = Forces()
575
+ sec_calc.forces = sec_forces
576
+ for contribution in source.forces.get('contribution', []):
577
+ name = self._metainfo_map.get(contribution.type)
578
+ forces_entry = ForcesEntry(
579
+ value=contribution.atomic * ureg.eV / ureg.angstrom
580
+ )
581
+ if name is None:
582
+ forces_entry.kind = contribution.type
583
+ sec_forces.contributions.append(forces_entry)
584
+ else:
585
+ setattr(sec_forces, name, forces_entry)
586
+
587
+ def symmetrize(stress):
588
+ symmetrized = np.zeros((3, 3))
589
+ symmetrized[0][0] = stress[0]
590
+ symmetrized[1][1] = stress[1]
591
+ symmetrized[2][2] = stress[2]
592
+ symmetrized[1][2] = symmetrized[2][1] = stress[3]
593
+ symmetrized[0][2] = symmetrized[2][0] = stress[4]
594
+ symmetrized[0][1] = symmetrized[1][0] = stress[5]
595
+ return symmetrized
596
+
597
+ # stress
598
+ if source.get('stress') is not None:
599
+ sec_stress = Stress()
600
+ sec_calc.stress = sec_stress
601
+ for contribution in source.stress.get('contribution', []):
602
+ name = self._metainfo_map.get(contribution.type)
603
+ stress_entry = StressEntry(
604
+ values_per_atom=[
605
+ symmetrize(atomic) for atomic in contribution.atomic
606
+ ]
607
+ * ureg.eV
608
+ / ureg.angstrom**3
609
+ )
610
+ if name is None:
611
+ stress_entry.kind = contribution.type
612
+ sec_stress.contributions.append(stress_entry)
613
+ else:
614
+ if name == 'total':
615
+ stress_entry.value = (
616
+ symmetrize(source.stress.total)
617
+ * ureg.eV
618
+ / ureg.angstrom**3
619
+ )
620
+ setattr(sec_stress, name, stress_entry)
621
+
622
+ # charges
623
+ if source.get('charges') is not None:
624
+ sec_charges = Charges()
625
+ sec_calc.charges.append(sec_charges)
626
+ sec_charges.n_electrons = source.charges.n_electrons
627
+ sec_charges.value = source.charges.charge * ureg.elementary_charge
628
+ # magnetic moments
629
+ if source.magnetic_moments is not None:
630
+ for mag_mom in source.magnetic_moments.get('mag_mom', []):
631
+ sec_charges.orbital_projected.append(
632
+ ChargesValue(
633
+ atom_index=mag_mom[0] - 1,
634
+ orbital=mag_mom[1],
635
+ spin_z=mag_mom[2],
636
+ )
637
+ )
638
+
639
+ # onsite levels
640
+ if source.get('onsite_levels') is not None:
641
+ sec_onsite = x_bopfox_onsite_levels()
642
+ sec_calc.x_bopfox_onsite_levels.append(sec_onsite)
643
+ for onsite in source.onsite_levels.get('energy', []):
644
+ sec_onsite.orbital_projected.append(
645
+ x_bopfox_onsite_levels_value(
646
+ orbital=onsite[0], atom_index=onsite[1] - 1, value=onsite[2]
647
+ )
648
+ )
649
+
650
+ # energies and forces from trajectory file
651
+ if source.get('energies_total') is not None:
652
+ sec_calc.energy = Energy(
653
+ total=EnergyEntry(
654
+ values_per_atom=source.get('energies_total') * ureg.eV,
655
+ value=sum(source.get('energies_total')) * ureg.eV,
656
+ )
657
+ )
658
+ if source.get('forces_total') is not None:
659
+ sec_calc.forces = Forces(
660
+ total=ForcesEntry(
661
+ value=source.get('forces_total') * ureg.eV / ureg.angstrom
662
+ )
663
+ )
664
+
665
+ # total energy from struc.log.dat
666
+ if source.get('energy_total') is not None:
667
+ sec_calc.energy = Energy(
668
+ total=EnergyEntry(value=source.get('energy_total') * ureg.eV)
669
+ )
670
+
671
+ return sec_calc
672
+
673
+ task = parameters.get('task')
674
+ # read the strucfile from infox because string may be truncated in mainfile
675
+ self.infox_parser.mainfile = os.path.join(self.maindir, 'infox.bx')
676
+ struc_basename = (
677
+ self.infox_parser.get_parameters().get('strucfile', '').rstrip('.bx')
678
+ )
679
+
680
+ # initial structure
681
+ self.strucbx_parser.mainfile = os.path.join(
682
+ self.maindir, f'{struc_basename}.bx'
683
+ )
684
+ sec_system = parse_system(self.strucbx_parser)
685
+
686
+ # initial single point calculation
687
+ sec_calc = parse_calculation(self.mainfile_parser)
688
+ sec_calc.system_ref = sec_system
689
+ workflow = None
690
+ if task in ['energy', 'force']:
691
+ self.archive.workflow2 = SinglePoint()
692
+
693
+ elif task == 'relax':
694
+ # relaxation trajectory from struc.RX.xyz
695
+ self.xyz_parser.mainfile = os.path.join(
696
+ self.maindir, f'{struc_basename}.RX.xyz'
697
+ )
698
+ frames = {
699
+ int(frame.get('step')): frame
700
+ for frame in self.xyz_parser.get('frame', [])
701
+ }
702
+ self.dat_parser.mainfile = os.path.join(
703
+ self.maindir, f'{struc_basename}.log.dat'
704
+ )
705
+ for n, cycle in enumerate(self.mainfile_parser.relaxation.get('cycle', [])):
706
+ sec_calc = parse_calculation(cycle)
707
+ frame = frames.get(n + 1, dict(energy_total=self.dat_parser.data[n][1]))
708
+ if sec_calc.energy is None:
709
+ # if energy is not present in the case of non-verbose output, read energy
710
+ # from trajectory file or from struc.log.dat
711
+ sec_calc = parse_calculation(frame, sec_calc)
712
+
713
+ # read frame from trajectory
714
+ sec_system = parse_system(frame)
715
+ sec_calc.system_ref = sec_system
716
+
717
+ # read final structure from struc.final.bx
718
+ self.strucbx_parser.mainfile = os.path.join(
719
+ self.maindir, f'{struc_basename}.final.bx'
720
+ )
721
+ sec_calc.system_ref = parse_system(self.strucbx_parser, sec_calc.system_ref)
722
+
723
+ workflow = GeometryOptimization(method=GeometryOptimizationMethod())
724
+ workflow.method.convergence_tolerance_energy_difference = (
725
+ parameters.get('rxeconv', 0) * ureg.eV
726
+ )
727
+ workflow.method.convergence_tolerance_force_maximum = (
728
+ parameters.get('rxfconv', 0) * ureg.eV / ureg.angstrom
729
+ )
730
+ self.archive.workflow2 = workflow
731
+
732
+ elif task == 'md':
733
+ # md trajectory from struc.MD.xyz
734
+ self.xyz_parser.mainfile = os.path.join(
735
+ self.maindir, f'{struc_basename}.MD.xyz'
736
+ )
737
+ # thermodynamic properties from struc.erg.dat
738
+ # TODO determine if the column names are arbitrary
739
+ self.dat_parser.mainfile = os.path.join(
740
+ self.maindir, f'{struc_basename}.erg.dat'
741
+ )
742
+
743
+ traj_steps = [
744
+ int(frame.get('step', 0)) for frame in self.xyz_parser.get('frame', [])
745
+ ]
746
+ time_step = parameters.get('mdtimestep', 1.0)
747
+ thermo_steps = [int(d[0] / time_step) for d in self.dat_parser.data]
748
+ n_atoms = self.mainfile_parser.get('n_atoms', [1, 1])[0]
749
+
750
+ self.n_atoms = n_atoms
751
+ self.trajectory_steps = traj_steps
752
+ self.thermodynamics_steps = thermo_steps
753
+
754
+ if (lattice_vectors := self.strucbx_parser.get('lattice_vectors')) is None:
755
+ lattice_vectors = lattice_vectors * ureg.angstrom
756
+ for step in self.trajectory_steps:
757
+ frame = self.xyz_parser.frame[traj_steps.index(step)]
758
+ labels = frame.get('labels')
759
+ positions = frame.get('positions')
760
+ if positions is None or labels is None:
761
+ continue
762
+ self.parse_trajectory_step(
763
+ dict(
764
+ atoms=dict(
765
+ labels=labels,
766
+ positions=positions * ureg.angstrom,
767
+ lattice_vectors=lattice_vectors,
768
+ )
769
+ )
770
+ )
771
+
772
+ for n, step in enumerate(self.thermodynamics_steps):
773
+ # md does not do an initial single point calculation so override first calc
774
+ data = self.dat_parser.data[n]
775
+ thermo_data = dict(
776
+ time_physical=data[0] * ureg.fs,
777
+ time_step=step,
778
+ energy=dict(
779
+ total=dict(
780
+ value=data[1] * n_atoms * ureg.eV,
781
+ potential=data[2] * n_atoms * ureg.eV,
782
+ kinetic=data[3] * n_atoms * ureg.eV,
783
+ )
784
+ ),
785
+ temperature=data[5] * ureg.K,
786
+ pressure=data[7] * ureg.MPa,
787
+ )
788
+ if n == 0:
789
+ self.parse_section(thermo_data, self.archive.run[-1].calculation[0])
790
+ else:
791
+ self.parse_thermodynamics_step(thermo_data)
792
+
793
+ integrator_map = {'velocity-verlet': 'velocity_verlet'}
794
+ ensemble = None
795
+ if parameters.get('mdthermostat'):
796
+ ensemble = 'NVT'
797
+ elif parameters.get('mdbarostat'):
798
+ ensemble = 'NVE'
799
+ self.parse_md_workflow(
800
+ dict(
801
+ method=dict(
802
+ integration_timestep=time_step * ureg.fs,
803
+ integrator_type=integrator_map.get(parameters.get('mdkernel')),
804
+ n_steps=parameters.get('mdsteps'),
805
+ thermodynamic_ensemble=ensemble,
806
+ )
807
+ )
808
+ )