nomad-parser-plugins-atomistic 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atomisticparsers/__init__.py +400 -0
- atomisticparsers/amber/__init__.py +19 -0
- atomisticparsers/amber/__main__.py +31 -0
- atomisticparsers/amber/metainfo/__init__.py +19 -0
- atomisticparsers/amber/metainfo/amber.py +495 -0
- atomisticparsers/amber/parser.py +42 -0
- atomisticparsers/asap/__init__.py +19 -0
- atomisticparsers/asap/__main__.py +31 -0
- atomisticparsers/asap/metainfo/__init__.py +19 -0
- atomisticparsers/asap/metainfo/asap.py +75 -0
- atomisticparsers/asap/parser.py +197 -0
- atomisticparsers/bopfox/__init__.py +19 -0
- atomisticparsers/bopfox/__main__.py +31 -0
- atomisticparsers/bopfox/metainfo/__init__.py +19 -0
- atomisticparsers/bopfox/metainfo/bopfox.py +225 -0
- atomisticparsers/bopfox/parser.py +808 -0
- atomisticparsers/dftbplus/__init__.py +19 -0
- atomisticparsers/dftbplus/__main__.py +31 -0
- atomisticparsers/dftbplus/metainfo/__init__.py +19 -0
- atomisticparsers/dftbplus/metainfo/dftbplus.py +217 -0
- atomisticparsers/dftbplus/parser.py +500 -0
- atomisticparsers/dlpoly/__init__.py +19 -0
- atomisticparsers/dlpoly/__main__.py +31 -0
- atomisticparsers/dlpoly/metainfo/__init__.py +19 -0
- atomisticparsers/dlpoly/metainfo/dl_poly.py +312 -0
- atomisticparsers/dlpoly/parser.py +798 -0
- atomisticparsers/gromacs/__init__.py +19 -0
- atomisticparsers/gromacs/__main__.py +31 -0
- atomisticparsers/gromacs/metainfo/__init__.py +19 -0
- atomisticparsers/gromacs/metainfo/gromacs.py +2388 -0
- atomisticparsers/gromacs/parser.py +1581 -0
- atomisticparsers/gromos/__init__.py +19 -0
- atomisticparsers/gromos/__main__.py +31 -0
- atomisticparsers/gromos/metainfo/__init__.py +19 -0
- atomisticparsers/gromos/metainfo/gromos.py +1995 -0
- atomisticparsers/gromos/parser.py +58 -0
- atomisticparsers/gulp/__init__.py +19 -0
- atomisticparsers/gulp/__main__.py +31 -0
- atomisticparsers/gulp/metainfo/__init__.py +19 -0
- atomisticparsers/gulp/metainfo/gulp.py +1117 -0
- atomisticparsers/gulp/parser.py +1316 -0
- atomisticparsers/h5md/__init__.py +19 -0
- atomisticparsers/h5md/__main__.py +31 -0
- atomisticparsers/h5md/metainfo/__init__.py +19 -0
- atomisticparsers/h5md/metainfo/h5md.py +239 -0
- atomisticparsers/h5md/parser.py +901 -0
- atomisticparsers/lammps/__init__.py +19 -0
- atomisticparsers/lammps/__main__.py +31 -0
- atomisticparsers/lammps/metainfo/__init__.py +19 -0
- atomisticparsers/lammps/metainfo/lammps.py +1417 -0
- atomisticparsers/lammps/parser.py +1753 -0
- atomisticparsers/libatoms/__init__.py +19 -0
- atomisticparsers/libatoms/__main__.py +31 -0
- atomisticparsers/libatoms/metainfo/__init__.py +19 -0
- atomisticparsers/libatoms/metainfo/lib_atoms.py +251 -0
- atomisticparsers/libatoms/parser.py +38 -0
- atomisticparsers/namd/__init__.py +19 -0
- atomisticparsers/namd/__main__.py +31 -0
- atomisticparsers/namd/metainfo/__init__.py +19 -0
- atomisticparsers/namd/metainfo/namd.py +1605 -0
- atomisticparsers/namd/parser.py +312 -0
- atomisticparsers/tinker/__init__.py +19 -0
- atomisticparsers/tinker/__main__.py +31 -0
- atomisticparsers/tinker/metainfo/__init__.py +18 -0
- atomisticparsers/tinker/metainfo/tinker.py +1363 -0
- atomisticparsers/tinker/parser.py +685 -0
- atomisticparsers/utils/__init__.py +22 -0
- atomisticparsers/utils/mdanalysis.py +662 -0
- atomisticparsers/utils/parsers.py +226 -0
- atomisticparsers/xtb/__init__.py +19 -0
- atomisticparsers/xtb/__main__.py +32 -0
- atomisticparsers/xtb/metainfo/__init__.py +19 -0
- atomisticparsers/xtb/metainfo/xtb.py +256 -0
- atomisticparsers/xtb/parser.py +979 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/LICENSE +202 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/METADATA +327 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/RECORD +80 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/WHEEL +5 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/entry_points.txt +15 -0
- nomad_parser_plugins_atomistic-1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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
|
+
)
|