lammpsparser 0.0.1__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.
- lammpsparser/__init__.py +38 -0
- lammpsparser/_version.py +34 -0
- lammpsparser/compatibility/__init__.py +0 -0
- lammpsparser/compatibility/calculate.py +484 -0
- lammpsparser/compatibility/constraints.py +106 -0
- lammpsparser/compatibility/file.py +285 -0
- lammpsparser/compatibility/structure.py +336 -0
- lammpsparser/output.py +340 -0
- lammpsparser/output_raw.py +251 -0
- lammpsparser/potential.py +380 -0
- lammpsparser/structure.py +536 -0
- lammpsparser/units.py +246 -0
- lammpsparser-0.0.1.dist-info/METADATA +146 -0
- lammpsparser-0.0.1.dist-info/RECORD +16 -0
- lammpsparser-0.0.1.dist-info/WHEEL +4 -0
- lammpsparser-0.0.1.dist-info/licenses/LICENSE +29 -0
lammpsparser/output.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from ase.atoms import Atoms
|
|
10
|
+
|
|
11
|
+
from lammpsparser.output_raw import (
|
|
12
|
+
parse_raw_dump_from_h5md,
|
|
13
|
+
parse_raw_dump_from_text,
|
|
14
|
+
parse_raw_lammps_log,
|
|
15
|
+
)
|
|
16
|
+
from lammpsparser.structure import UnfoldingPrism
|
|
17
|
+
from lammpsparser.units import UnitConverter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def remap_indices_ase(
|
|
21
|
+
lammps_indices: Union[np.ndarray, List],
|
|
22
|
+
potential_elements: Union[np.ndarray, List],
|
|
23
|
+
structure: Atoms,
|
|
24
|
+
) -> np.ndarray:
|
|
25
|
+
"""
|
|
26
|
+
Give the Lammps-dumped indices, re-maps these back onto the structure's indices to preserve the species.
|
|
27
|
+
|
|
28
|
+
The issue is that for an N-element potential, Lammps dumps the chemical index from 1 to N based on the order
|
|
29
|
+
that these species are written in the Lammps input file. But the indices for a given structure are based on the
|
|
30
|
+
order in which chemical species were added to that structure, and run from 0 up to the number of species
|
|
31
|
+
currently in that structure. Therefore we need to be a little careful with mapping.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
lammps_indices (numpy.ndarray/list): The Lammps-dumped integers.
|
|
35
|
+
potential_elements (numpy.ndarray/list):
|
|
36
|
+
structure (pyiron_atomistics.atomistics.structure.Atoms):
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
numpy.ndarray: Those integers mapped onto the structure.
|
|
40
|
+
"""
|
|
41
|
+
lammps_symbol_order = np.array(potential_elements)
|
|
42
|
+
|
|
43
|
+
# Create a map between the lammps indices and structure indices to preserve species
|
|
44
|
+
structure_symbol_order = np.unique(structure.get_chemical_symbols())
|
|
45
|
+
map_ = np.array(
|
|
46
|
+
[
|
|
47
|
+
int(np.argwhere(lammps_symbol_order == symbol)[0][0]) + 1
|
|
48
|
+
for symbol in structure_symbol_order
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
structure_indices = np.array(lammps_indices)
|
|
53
|
+
for i_struct, i_lammps in enumerate(map_):
|
|
54
|
+
np.place(structure_indices, lammps_indices == i_lammps, i_struct)
|
|
55
|
+
# TODO: Vectorize this for-loop for computational efficiency
|
|
56
|
+
|
|
57
|
+
return structure_indices
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parse_lammps_output(
|
|
61
|
+
working_directory: str,
|
|
62
|
+
structure: Atoms,
|
|
63
|
+
potential_elements: Union[np.ndarray, List],
|
|
64
|
+
units: str,
|
|
65
|
+
prism: Optional[UnfoldingPrism] = None,
|
|
66
|
+
dump_h5_file_name: str = "dump.h5",
|
|
67
|
+
dump_out_file_name: str = "dump.out",
|
|
68
|
+
log_lammps_file_name: str = "log.lammps",
|
|
69
|
+
remap_indices_funct: callable = remap_indices_ase,
|
|
70
|
+
) -> Dict:
|
|
71
|
+
if prism is None:
|
|
72
|
+
prism = UnfoldingPrism(structure.cell)
|
|
73
|
+
dump_dict = _parse_dump(
|
|
74
|
+
dump_h5_full_file_name=os.path.join(working_directory, dump_h5_file_name),
|
|
75
|
+
dump_out_full_file_name=os.path.join(working_directory, dump_out_file_name),
|
|
76
|
+
prism=prism,
|
|
77
|
+
structure=structure,
|
|
78
|
+
potential_elements=potential_elements,
|
|
79
|
+
remap_indices_funct=remap_indices_funct,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
generic_keys_lst, pressure_dict, df = _parse_log(
|
|
83
|
+
log_lammps_full_file_name=os.path.join(working_directory, log_lammps_file_name),
|
|
84
|
+
prism=prism,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
convert_units = UnitConverter(units).convert_array_to_pyiron_units
|
|
88
|
+
|
|
89
|
+
hdf_output = {"generic": {}, "lammps": {}}
|
|
90
|
+
hdf_generic = hdf_output["generic"]
|
|
91
|
+
hdf_lammps = hdf_output["lammps"]
|
|
92
|
+
|
|
93
|
+
if "computes" in dump_dict.keys():
|
|
94
|
+
for k, v in dump_dict.pop("computes").items():
|
|
95
|
+
hdf_generic[k] = convert_units(np.array(v), label=k)
|
|
96
|
+
|
|
97
|
+
hdf_generic["steps"] = convert_units(
|
|
98
|
+
np.array(dump_dict.pop("steps"), dtype=int), label="steps"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
for k, v in dump_dict.items():
|
|
102
|
+
if len(v) > 0:
|
|
103
|
+
try:
|
|
104
|
+
hdf_generic[k] = convert_units(np.array(v), label=k)
|
|
105
|
+
except ValueError:
|
|
106
|
+
hdf_generic[k] = [convert_units(np.array(val), label=k) for val in v]
|
|
107
|
+
|
|
108
|
+
if df is not None and pressure_dict is not None and generic_keys_lst is not None:
|
|
109
|
+
for k, v in df.items():
|
|
110
|
+
v = convert_units(np.array(v), label=k)
|
|
111
|
+
if k in generic_keys_lst:
|
|
112
|
+
hdf_generic[k] = v
|
|
113
|
+
else: # This is a hack for backward comparability
|
|
114
|
+
hdf_lammps[k] = v
|
|
115
|
+
|
|
116
|
+
# Store pressures as numpy arrays
|
|
117
|
+
for key, val in pressure_dict.items():
|
|
118
|
+
hdf_generic[key] = convert_units(val, label=key)
|
|
119
|
+
else:
|
|
120
|
+
warnings.warn("LAMMPS warning: No log.lammps output file found.")
|
|
121
|
+
|
|
122
|
+
return hdf_output
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _parse_dump(
|
|
126
|
+
dump_h5_full_file_name: str,
|
|
127
|
+
dump_out_full_file_name: str,
|
|
128
|
+
prism: UnfoldingPrism,
|
|
129
|
+
structure: Atoms,
|
|
130
|
+
potential_elements: Union[np.ndarray, List],
|
|
131
|
+
remap_indices_funct: callable = remap_indices_ase,
|
|
132
|
+
) -> Dict:
|
|
133
|
+
if os.path.isfile(dump_h5_full_file_name):
|
|
134
|
+
if not _check_ortho_prism(prism=prism):
|
|
135
|
+
raise RuntimeError(
|
|
136
|
+
"The Lammps output will not be mapped back to pyiron correctly."
|
|
137
|
+
)
|
|
138
|
+
return parse_raw_dump_from_h5md(
|
|
139
|
+
file_name=dump_h5_full_file_name,
|
|
140
|
+
)
|
|
141
|
+
elif os.path.exists(dump_out_full_file_name):
|
|
142
|
+
return _collect_dump_from_text(
|
|
143
|
+
file_name=dump_out_full_file_name,
|
|
144
|
+
prism=prism,
|
|
145
|
+
structure=structure,
|
|
146
|
+
potential_elements=potential_elements,
|
|
147
|
+
remap_indices_funct=remap_indices_funct,
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
raise FileNotFoundError(
|
|
151
|
+
f"Neither {dump_h5_full_file_name} nor {dump_out_full_file_name} exist."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _collect_dump_from_text(
|
|
156
|
+
file_name: str,
|
|
157
|
+
prism: UnfoldingPrism,
|
|
158
|
+
structure: Atoms,
|
|
159
|
+
potential_elements: Union[np.ndarray, List],
|
|
160
|
+
remap_indices_funct: callable = remap_indices_ase,
|
|
161
|
+
) -> Dict:
|
|
162
|
+
"""
|
|
163
|
+
general purpose routine to extract static from a lammps dump file
|
|
164
|
+
"""
|
|
165
|
+
rotation_lammps2orig = prism.R.T
|
|
166
|
+
dump_lammps_dict = parse_raw_dump_from_text(file_name=file_name)
|
|
167
|
+
dump_dict = {}
|
|
168
|
+
for key, val in dump_lammps_dict.items():
|
|
169
|
+
if key in ["cells"]:
|
|
170
|
+
dump_dict[key] = [prism.unfold_cell(cell=cell) for cell in val]
|
|
171
|
+
elif key in ["indices"]:
|
|
172
|
+
dump_dict[key] = [
|
|
173
|
+
remap_indices_funct(
|
|
174
|
+
lammps_indices=indices,
|
|
175
|
+
potential_elements=potential_elements,
|
|
176
|
+
structure=structure,
|
|
177
|
+
)
|
|
178
|
+
for indices in val
|
|
179
|
+
]
|
|
180
|
+
elif key in [
|
|
181
|
+
"forces",
|
|
182
|
+
"mean_forces",
|
|
183
|
+
"velocities",
|
|
184
|
+
"mean_velocities",
|
|
185
|
+
"mean_unwrapped_positions",
|
|
186
|
+
]:
|
|
187
|
+
dump_dict[key] = [np.matmul(v, rotation_lammps2orig) for v in val]
|
|
188
|
+
elif key in ["positions", "unwrapped_positions"]:
|
|
189
|
+
dump_dict[key] = [
|
|
190
|
+
np.matmul(np.matmul(v, lammps_cell), rotation_lammps2orig)
|
|
191
|
+
for v, lammps_cell in zip(val, dump_lammps_dict["cells"])
|
|
192
|
+
]
|
|
193
|
+
else:
|
|
194
|
+
dump_dict[key] = val
|
|
195
|
+
return dump_dict
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _parse_log(
|
|
199
|
+
log_lammps_full_file_name: str, prism: UnfoldingPrism
|
|
200
|
+
) -> Union[Tuple[List[str], Dict, pd.DataFrame], Tuple[None, None, None]]:
|
|
201
|
+
"""
|
|
202
|
+
If it exists, parses the lammps log file and either raises an exception if errors
|
|
203
|
+
occurred or returns data. Just returns a tuple of Nones if there is no file at the
|
|
204
|
+
given location.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
log_lammps_full_file_name (str): The path to the lammps log file.
|
|
208
|
+
prism (pyiron_atomistics.lammps.structure.UnfoldingPrism): For mapping between
|
|
209
|
+
lammps and pyiron structures
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
(list | None): Generic keys
|
|
213
|
+
(dict | None): Pressures
|
|
214
|
+
(pandas.DataFrame | None): A dataframe with the rest of the information
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
(RuntimeError): If there are "ERROR" tags in the log.
|
|
218
|
+
"""
|
|
219
|
+
if os.path.exists(log_lammps_full_file_name):
|
|
220
|
+
return _collect_output_log(
|
|
221
|
+
file_name=log_lammps_full_file_name,
|
|
222
|
+
prism=prism,
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
return None, None, None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _collect_output_log(
|
|
229
|
+
file_name: str, prism: UnfoldingPrism
|
|
230
|
+
) -> Tuple[List[str], Dict, pd.DataFrame]:
|
|
231
|
+
"""
|
|
232
|
+
general purpose routine to extract static from a lammps log file
|
|
233
|
+
"""
|
|
234
|
+
df = parse_raw_lammps_log(file_name=file_name)
|
|
235
|
+
|
|
236
|
+
h5_dict = {
|
|
237
|
+
"Step": "steps",
|
|
238
|
+
"Temp": "temperature",
|
|
239
|
+
"PotEng": "energy_pot",
|
|
240
|
+
"TotEng": "energy_tot",
|
|
241
|
+
"Volume": "volume",
|
|
242
|
+
"LogStep": "LogStep",
|
|
243
|
+
}
|
|
244
|
+
if "LogStep" not in df.columns:
|
|
245
|
+
del h5_dict["LogStep"]
|
|
246
|
+
|
|
247
|
+
for key in df.columns[df.columns.str.startswith("f_mean")]:
|
|
248
|
+
h5_dict[key] = key.replace("f_", "")
|
|
249
|
+
|
|
250
|
+
df = df.rename(index=str, columns=h5_dict)
|
|
251
|
+
pressure_dict = dict()
|
|
252
|
+
if all(
|
|
253
|
+
[
|
|
254
|
+
x in df.columns.values
|
|
255
|
+
for x in [
|
|
256
|
+
"Pxx",
|
|
257
|
+
"Pxy",
|
|
258
|
+
"Pxz",
|
|
259
|
+
"Pxy",
|
|
260
|
+
"Pyy",
|
|
261
|
+
"Pyz",
|
|
262
|
+
"Pxz",
|
|
263
|
+
"Pyz",
|
|
264
|
+
"Pzz",
|
|
265
|
+
]
|
|
266
|
+
]
|
|
267
|
+
):
|
|
268
|
+
pressures = (
|
|
269
|
+
np.stack(
|
|
270
|
+
(
|
|
271
|
+
df.Pxx,
|
|
272
|
+
df.Pxy,
|
|
273
|
+
df.Pxz,
|
|
274
|
+
df.Pxy,
|
|
275
|
+
df.Pyy,
|
|
276
|
+
df.Pyz,
|
|
277
|
+
df.Pxz,
|
|
278
|
+
df.Pyz,
|
|
279
|
+
df.Pzz,
|
|
280
|
+
),
|
|
281
|
+
axis=-1,
|
|
282
|
+
)
|
|
283
|
+
.reshape(-1, 3, 3)
|
|
284
|
+
.astype("float64")
|
|
285
|
+
)
|
|
286
|
+
# Rotate pressures from Lammps frame to pyiron frame if necessary
|
|
287
|
+
if _check_ortho_prism(prism=prism):
|
|
288
|
+
rotation_matrix = prism.R.T
|
|
289
|
+
pressures = rotation_matrix.T @ pressures @ rotation_matrix
|
|
290
|
+
|
|
291
|
+
df = df.drop(
|
|
292
|
+
columns=df.columns[
|
|
293
|
+
((df.columns.str.len() == 3) & df.columns.str.startswith("P"))
|
|
294
|
+
]
|
|
295
|
+
)
|
|
296
|
+
pressure_dict["pressures"] = pressures
|
|
297
|
+
else:
|
|
298
|
+
warnings.warn(
|
|
299
|
+
"LAMMPS warning: log.lammps does not contain the required pressure values."
|
|
300
|
+
)
|
|
301
|
+
if "mean_pressure[1]" in df.columns:
|
|
302
|
+
pressures = (
|
|
303
|
+
np.stack(
|
|
304
|
+
tuple(df[f"mean_pressure[{i}]"] for i in [1, 4, 5, 4, 2, 6, 5, 6, 3]),
|
|
305
|
+
axis=-1,
|
|
306
|
+
)
|
|
307
|
+
.reshape(-1, 3, 3)
|
|
308
|
+
.astype("float64")
|
|
309
|
+
)
|
|
310
|
+
if _check_ortho_prism(prism=prism):
|
|
311
|
+
rotation_matrix = prism.R.T
|
|
312
|
+
pressures = rotation_matrix.T @ pressures @ rotation_matrix
|
|
313
|
+
df = df.drop(
|
|
314
|
+
columns=df.columns[
|
|
315
|
+
(
|
|
316
|
+
df.columns.str.startswith("mean_pressure")
|
|
317
|
+
& df.columns.str.endswith("]")
|
|
318
|
+
)
|
|
319
|
+
]
|
|
320
|
+
)
|
|
321
|
+
pressure_dict["mean_pressures"] = pressures
|
|
322
|
+
generic_keys_lst = list(h5_dict.values())
|
|
323
|
+
return generic_keys_lst, pressure_dict, df
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _check_ortho_prism(
|
|
327
|
+
prism: UnfoldingPrism, rtol: float = 0.0, atol: float = 1e-08
|
|
328
|
+
) -> bool:
|
|
329
|
+
"""
|
|
330
|
+
Check if the rotation matrix of the UnfoldingPrism object is sufficiently close to a unit matrix
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
prism (pyiron_atomistics.lammps.structure.UnfoldingPrism): UnfoldingPrism object to check
|
|
334
|
+
rtol (float): relative precision for numpy.isclose()
|
|
335
|
+
atol (float): absolute precision for numpy.isclose()
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
boolean: True or False
|
|
339
|
+
"""
|
|
340
|
+
return np.isclose(prism.R, np.eye(3), rtol=rtol, atol=atol).all()
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from dataclasses import asdict, dataclass, field
|
|
5
|
+
from io import StringIO
|
|
6
|
+
from typing import Dict, List, Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class DumpData:
|
|
14
|
+
steps: List = field(default_factory=lambda: [])
|
|
15
|
+
natoms: List = field(default_factory=lambda: [])
|
|
16
|
+
cells: List = field(default_factory=lambda: [])
|
|
17
|
+
indices: List = field(default_factory=lambda: [])
|
|
18
|
+
forces: List = field(default_factory=lambda: [])
|
|
19
|
+
mean_forces: List = field(default_factory=lambda: [])
|
|
20
|
+
velocities: List = field(default_factory=lambda: [])
|
|
21
|
+
mean_velocities: List = field(default_factory=lambda: [])
|
|
22
|
+
unwrapped_positions: List = field(default_factory=lambda: [])
|
|
23
|
+
mean_unwrapped_positions: List = field(default_factory=lambda: [])
|
|
24
|
+
positions: List = field(default_factory=lambda: [])
|
|
25
|
+
computes: Dict = field(default_factory=lambda: {})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def to_amat(l_list: Union[np.ndarray, List]) -> List:
|
|
29
|
+
lst = np.reshape(l_list, -1)
|
|
30
|
+
if len(lst) == 9:
|
|
31
|
+
(
|
|
32
|
+
xlo_bound,
|
|
33
|
+
xhi_bound,
|
|
34
|
+
xy,
|
|
35
|
+
ylo_bound,
|
|
36
|
+
yhi_bound,
|
|
37
|
+
xz,
|
|
38
|
+
zlo_bound,
|
|
39
|
+
zhi_bound,
|
|
40
|
+
yz,
|
|
41
|
+
) = lst
|
|
42
|
+
|
|
43
|
+
elif len(lst) == 6:
|
|
44
|
+
xlo_bound, xhi_bound, ylo_bound, yhi_bound, zlo_bound, zhi_bound = lst
|
|
45
|
+
xy, xz, yz = 0.0, 0.0, 0.0
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError("This format for amat not yet implemented: " + str(len(lst)))
|
|
48
|
+
|
|
49
|
+
# > xhi_bound - xlo_bound = xhi -xlo + MAX(0.0, xy, xz, xy + xz) - MIN(0.0, xy, xz, xy + xz)
|
|
50
|
+
# > xhili = xhi -xlo = xhi_bound - xlo_bound - MAX(0.0, xy, xz, xy + xz) + MIN(0.0, xy, xz, xy + xz)
|
|
51
|
+
xhilo = (
|
|
52
|
+
(xhi_bound - xlo_bound)
|
|
53
|
+
- max([0.0, xy, xz, xy + xz])
|
|
54
|
+
+ min([0.0, xy, xz, xy + xz])
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# > yhilo = yhi -ylo = yhi_bound -ylo_bound - MAX(0.0, yz) + MIN(0.0, yz)
|
|
58
|
+
yhilo = (yhi_bound - ylo_bound) - max([0.0, yz]) + min([0.0, yz])
|
|
59
|
+
|
|
60
|
+
# > zhi - zlo = zhi_bound- zlo_bound
|
|
61
|
+
zhilo = zhi_bound - zlo_bound
|
|
62
|
+
|
|
63
|
+
cell = [[xhilo, 0, 0], [xy, yhilo, 0], [xz, yz, zhilo]]
|
|
64
|
+
return cell
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_raw_dump_from_h5md(file_name: str) -> Dict:
|
|
68
|
+
import h5py
|
|
69
|
+
|
|
70
|
+
with h5py.File(file_name, mode="r", libver="latest", swmr=True) as h5md:
|
|
71
|
+
positions = [pos_i.tolist() for pos_i in h5md["/particles/all/position/value"]]
|
|
72
|
+
steps = [steps_i.tolist() for steps_i in h5md["/particles/all/position/step"]]
|
|
73
|
+
forces = [for_i.tolist() for for_i in h5md["/particles/all/force/value"]]
|
|
74
|
+
# following the explanation at: http://nongnu.org/h5md/h5md.html
|
|
75
|
+
cell = [
|
|
76
|
+
np.eye(3) * np.array(cell_i.tolist())
|
|
77
|
+
for cell_i in h5md["/particles/all/box/edges/value"]
|
|
78
|
+
]
|
|
79
|
+
return {
|
|
80
|
+
"forces": forces,
|
|
81
|
+
"positions": positions,
|
|
82
|
+
"steps": steps,
|
|
83
|
+
"cells": cell,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_raw_dump_from_text(file_name: str) -> Dict:
|
|
88
|
+
"""
|
|
89
|
+
Docstring for _parse_dump_from_text
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
file_name (str): The path to the lammps dump file.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dict: Parsed dump data.
|
|
96
|
+
"""
|
|
97
|
+
with open(file_name, "r") as f:
|
|
98
|
+
dump = DumpData()
|
|
99
|
+
|
|
100
|
+
for line in f:
|
|
101
|
+
if "ITEM: TIMESTEP" in line:
|
|
102
|
+
dump.steps.append(int(f.readline()))
|
|
103
|
+
|
|
104
|
+
elif "ITEM: BOX BOUNDS" in line:
|
|
105
|
+
c1 = np.fromstring(f.readline(), dtype=float, sep=" ")
|
|
106
|
+
c2 = np.fromstring(f.readline(), dtype=float, sep=" ")
|
|
107
|
+
c3 = np.fromstring(f.readline(), dtype=float, sep=" ")
|
|
108
|
+
cell = np.concatenate([c1, c2, c3])
|
|
109
|
+
dump.cells.append(to_amat(cell))
|
|
110
|
+
|
|
111
|
+
elif "ITEM: NUMBER OF ATOMS" in line:
|
|
112
|
+
n = int(f.readline())
|
|
113
|
+
dump.natoms.append(n)
|
|
114
|
+
|
|
115
|
+
elif "ITEM: ATOMS" in line:
|
|
116
|
+
# get column names from line
|
|
117
|
+
columns = line.lstrip("ITEM: ATOMS").split()
|
|
118
|
+
|
|
119
|
+
# Read line by line of snapshot into a string buffer
|
|
120
|
+
# Than parse using pandas for speed and column acces
|
|
121
|
+
buf = StringIO()
|
|
122
|
+
for _ in range(n):
|
|
123
|
+
buf.write(f.readline())
|
|
124
|
+
buf.seek(0)
|
|
125
|
+
df = pd.read_csv(
|
|
126
|
+
buf,
|
|
127
|
+
nrows=n,
|
|
128
|
+
sep="\\s+",
|
|
129
|
+
header=None,
|
|
130
|
+
names=columns,
|
|
131
|
+
engine="c",
|
|
132
|
+
)
|
|
133
|
+
df.sort_values(by="id", ignore_index=True, inplace=True)
|
|
134
|
+
# Coordinate transform lammps->pyiron
|
|
135
|
+
dump.indices.append(df["type"].array.astype(int))
|
|
136
|
+
|
|
137
|
+
dump.forces.append(
|
|
138
|
+
np.stack([df["fx"].array, df["fy"].array, df["fz"].array], axis=1)
|
|
139
|
+
)
|
|
140
|
+
if "f_mean_forces[1]" in columns:
|
|
141
|
+
dump.mean_forces.append(
|
|
142
|
+
np.stack(
|
|
143
|
+
[
|
|
144
|
+
df["f_mean_forces[1]"].array,
|
|
145
|
+
df["f_mean_forces[2]"].array,
|
|
146
|
+
df["f_mean_forces[3]"].array,
|
|
147
|
+
],
|
|
148
|
+
axis=1,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
if "vx" in columns and "vy" in columns and "vz" in columns:
|
|
152
|
+
dump.velocities.append(
|
|
153
|
+
np.stack(
|
|
154
|
+
[
|
|
155
|
+
df["vx"].array,
|
|
156
|
+
df["vy"].array,
|
|
157
|
+
df["vz"].array,
|
|
158
|
+
],
|
|
159
|
+
axis=1,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if "f_mean_velocities[1]" in columns:
|
|
164
|
+
dump.mean_velocities.append(
|
|
165
|
+
np.stack(
|
|
166
|
+
[
|
|
167
|
+
df["f_mean_velocities[1]"].array,
|
|
168
|
+
df["f_mean_velocities[2]"].array,
|
|
169
|
+
df["f_mean_velocities[3]"].array,
|
|
170
|
+
],
|
|
171
|
+
axis=1,
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if "xsu" in columns:
|
|
176
|
+
direct_unwrapped_positions = np.stack(
|
|
177
|
+
[
|
|
178
|
+
df["xsu"].array,
|
|
179
|
+
df["ysu"].array,
|
|
180
|
+
df["zsu"].array,
|
|
181
|
+
],
|
|
182
|
+
axis=1,
|
|
183
|
+
)
|
|
184
|
+
dump.unwrapped_positions.append(direct_unwrapped_positions)
|
|
185
|
+
dump.positions.append(
|
|
186
|
+
direct_unwrapped_positions
|
|
187
|
+
- np.floor(direct_unwrapped_positions)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if "f_mean_positions[1]" in columns:
|
|
191
|
+
dump.mean_unwrapped_positions.append(
|
|
192
|
+
np.stack(
|
|
193
|
+
[
|
|
194
|
+
df["f_mean_positions[1]"].array,
|
|
195
|
+
df["f_mean_positions[2]"].array,
|
|
196
|
+
df["f_mean_positions[3]"].array,
|
|
197
|
+
],
|
|
198
|
+
axis=1,
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
for k in columns:
|
|
202
|
+
if k.startswith("c_"):
|
|
203
|
+
kk = k.replace("c_", "")
|
|
204
|
+
if kk not in dump.computes.keys():
|
|
205
|
+
dump.computes[kk] = []
|
|
206
|
+
dump.computes[kk].append(df[k].array)
|
|
207
|
+
|
|
208
|
+
return asdict(dump)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def parse_raw_lammps_log(file_name: str) -> pd.DataFrame:
|
|
212
|
+
"""
|
|
213
|
+
Docstring for _parse_lammps_log
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
file_name (str): The path to the lammps log file.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
pd.DataFrame: Dataframe containing the parsed log data.
|
|
220
|
+
"""
|
|
221
|
+
with open(file_name, "r") as f:
|
|
222
|
+
dfs = []
|
|
223
|
+
read_thermo = False
|
|
224
|
+
for l in f:
|
|
225
|
+
l = l.lstrip()
|
|
226
|
+
|
|
227
|
+
if l.startswith("Step"):
|
|
228
|
+
thermo_lines = ""
|
|
229
|
+
read_thermo = True
|
|
230
|
+
|
|
231
|
+
if read_thermo:
|
|
232
|
+
if l.startswith("Loop") or l.startswith("ERROR"):
|
|
233
|
+
read_thermo = False
|
|
234
|
+
dfs.append(
|
|
235
|
+
pd.read_csv(StringIO(thermo_lines), sep="\\s+", engine="c")
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
elif l.startswith("WARNING:"):
|
|
239
|
+
warnings.warn(f"A warning was found in the log:\n{l}")
|
|
240
|
+
|
|
241
|
+
else:
|
|
242
|
+
thermo_lines += l
|
|
243
|
+
|
|
244
|
+
if len(dfs) == 1:
|
|
245
|
+
df = dfs[0]
|
|
246
|
+
else:
|
|
247
|
+
for i in range(len(dfs)):
|
|
248
|
+
df = dfs[i]
|
|
249
|
+
df["LogStep"] = np.ones(len(df)) * i
|
|
250
|
+
df = pd.concat(dfs, ignore_index=True)
|
|
251
|
+
return df
|