lammpsparser 0.0.4__tar.gz → 0.0.6__tar.gz
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-0.0.4 → lammpsparser-0.0.6}/PKG-INFO +5 -5
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/pyproject.toml +5 -5
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/_version.py +2 -2
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/compatibility/calculate.py +47 -2
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/compatibility/constraints.py +52 -0
- lammpsparser-0.0.6/src/lammpsparser/compatibility/data.py +87 -0
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/compatibility/file.py +69 -6
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/compatibility/structure.py +39 -18
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/output.py +114 -2
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/output_raw.py +91 -6
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/potential.py +149 -3
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/structure.py +111 -24
- lammpsparser-0.0.4/src/lammpsparser/compatibility/data.py +0 -33
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/.gitignore +0 -0
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/LICENSE +0 -0
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/README.md +0 -0
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/__init__.py +0 -0
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/compatibility/__init__.py +0 -0
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/compatibility/output.py +0 -0
- {lammpsparser-0.0.4 → lammpsparser-0.0.6}/src/lammpsparser/units.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lammpsparser
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Vector-oriented LAMMPS interface to rapidly iterate over series of atomistic structures or interatomic potentials.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pyiron/lammpsparser
|
|
6
6
|
Project-URL: Documentation, https://github.com/pyiron/lammpsparser
|
|
@@ -49,12 +49,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
49
49
|
Classifier: Programming Language :: Python :: 3.14
|
|
50
50
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
51
51
|
Requires-Python: <3.15,>=3.9
|
|
52
|
-
Requires-Dist: ase<=3.
|
|
53
|
-
Requires-Dist: numpy<=2.4.
|
|
54
|
-
Requires-Dist: pandas<=3.0.
|
|
52
|
+
Requires-Dist: ase<=3.29.0,>=3.22.1
|
|
53
|
+
Requires-Dist: numpy<=2.4.6,>=1.26.0
|
|
54
|
+
Requires-Dist: pandas<=3.0.3,>=2.0.3
|
|
55
55
|
Requires-Dist: scipy<=1.17.1,>=1.11.1
|
|
56
56
|
Provides-Extra: compatibility
|
|
57
|
-
Requires-Dist: structuretoolkit<=0.0.
|
|
57
|
+
Requires-Dist: structuretoolkit<=0.0.45,>=0.0.30; extra == 'compatibility'
|
|
58
58
|
Provides-Extra: h5md
|
|
59
59
|
Requires-Dist: h5py<=3.16.0,>=3.9.0; extra == 'h5md'
|
|
60
60
|
Description-Content-Type: text/markdown
|
|
@@ -5,7 +5,7 @@ requires = [
|
|
|
5
5
|
"h5py",
|
|
6
6
|
"pandas",
|
|
7
7
|
"scipy",
|
|
8
|
-
"hatchling==1.
|
|
8
|
+
"hatchling==1.30.1",
|
|
9
9
|
"hatch-vcs==0.5.0",
|
|
10
10
|
]
|
|
11
11
|
build-backend = "hatchling.build"
|
|
@@ -34,9 +34,9 @@ classifiers = [
|
|
|
34
34
|
"Programming Language :: Python :: 3.14",
|
|
35
35
|
]
|
|
36
36
|
dependencies = [
|
|
37
|
-
"ase>=3.22.1,<=3.
|
|
38
|
-
"numpy>=1.26.0,<=2.4.
|
|
39
|
-
"pandas>=2.0.3,<=3.0.
|
|
37
|
+
"ase>=3.22.1,<=3.29.0",
|
|
38
|
+
"numpy>=1.26.0,<=2.4.6",
|
|
39
|
+
"pandas>=2.0.3,<=3.0.3",
|
|
40
40
|
"scipy>=1.11.1,<=1.17.1",
|
|
41
41
|
]
|
|
42
42
|
dynamic = ["version"]
|
|
@@ -48,7 +48,7 @@ Repository = "https://github.com/pyiron/lammpsparser"
|
|
|
48
48
|
|
|
49
49
|
[project.optional-dependencies]
|
|
50
50
|
h5md = ["h5py>=3.9.0,<=3.16.0"]
|
|
51
|
-
compatibility = ["structuretoolkit>=0.0.30,<=0.0.
|
|
51
|
+
compatibility = ["structuretoolkit>=0.0.30,<=0.0.45"]
|
|
52
52
|
|
|
53
53
|
[tool.hatch.build]
|
|
54
54
|
include = [
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
21
|
+
__version__ = version = '0.0.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 6)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -308,10 +308,31 @@ def calc_minimize(
|
|
|
308
308
|
|
|
309
309
|
|
|
310
310
|
def calc_static():
|
|
311
|
+
"""
|
|
312
|
+
Generate LAMMPS input commands for a single-point (static) energy calculation.
|
|
313
|
+
|
|
314
|
+
Configures thermodynamic output and runs zero MD steps so that LAMMPS
|
|
315
|
+
computes and prints forces, stresses, and energies for the current
|
|
316
|
+
configuration without moving any atoms.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
list[str]: LAMMPS input lines to append to the input script.
|
|
320
|
+
"""
|
|
311
321
|
return ["variable thermotime equal 1"] + _get_thermo() + ["run 0"]
|
|
312
322
|
|
|
313
323
|
|
|
314
324
|
def _get_thermo():
|
|
325
|
+
"""
|
|
326
|
+
Return the standard thermodynamic output commands used by all calc modes.
|
|
327
|
+
|
|
328
|
+
Writes step, temperature, potential energy, total energy, full pressure
|
|
329
|
+
tensor, and volume every ``${thermotime}`` steps. The ``${thermotime}``
|
|
330
|
+
variable must be defined before these lines are appended.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
list[str]: Three LAMMPS input lines: ``thermo_style``,
|
|
334
|
+
``thermo_modify``, and ``thermo``.
|
|
335
|
+
"""
|
|
315
336
|
return [
|
|
316
337
|
"thermo_style custom step temp pe etotal pxx pxy pxz pyy pyz pzz vol",
|
|
317
338
|
"thermo_modify format float %20.15g",
|
|
@@ -320,6 +341,20 @@ def _get_thermo():
|
|
|
320
341
|
|
|
321
342
|
|
|
322
343
|
def _is_isotropic_hydrostatic(pressure):
|
|
344
|
+
"""
|
|
345
|
+
Return ``True`` when ``pressure`` describes an isotropic, hydrostatic state.
|
|
346
|
+
|
|
347
|
+
A pressure specification is considered isotropic and hydrostatic when the
|
|
348
|
+
three diagonal (axial) components are equal and the three off-diagonal
|
|
349
|
+
(shear) components are either all ``None`` or all equal to zero.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
pressure (list): Six-element list ``[pxx, pyy, pzz, pxy, pxz, pyz]``
|
|
353
|
+
where ``None`` means "unconstrained".
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
bool: ``True`` if the pressure is isotropic hydrostatic.
|
|
357
|
+
"""
|
|
323
358
|
axial_all_alike = None not in pressure[:3] and np.allclose(
|
|
324
359
|
pressure[:3], pressure[0]
|
|
325
360
|
)
|
|
@@ -433,12 +468,22 @@ def _pressure_to_lammps(pressure, rotation_matrix, units="metal"):
|
|
|
433
468
|
|
|
434
469
|
def _get_rotation_matrix(structure, pressure):
|
|
435
470
|
"""
|
|
471
|
+
Derive the rotation matrix from the structure's prism and optionally perturb
|
|
472
|
+
the cell to support non-diagonal pressure components.
|
|
473
|
+
|
|
474
|
+
If the requested pressure includes shear components and the cell is not
|
|
475
|
+
already triclinic, a tiny cell perturbation is applied so that LAMMPS
|
|
476
|
+
permits the off-diagonal deformations.
|
|
436
477
|
|
|
437
478
|
Args:
|
|
438
|
-
|
|
479
|
+
structure (ase.atoms.Atoms): Current atomic structure.
|
|
480
|
+
pressure (float or list): Pressure specification (scalar or up to six
|
|
481
|
+
components).
|
|
439
482
|
|
|
440
483
|
Returns:
|
|
441
|
-
|
|
484
|
+
tuple:
|
|
485
|
+
- numpy.ndarray: 3×3 rotation matrix from the ASE to LAMMPS frame.
|
|
486
|
+
- ase.atoms.Atoms: Possibly perturbed structure.
|
|
442
487
|
"""
|
|
443
488
|
if structure is not None:
|
|
444
489
|
prism = UnfoldingPrism(structure.cell)
|
|
@@ -3,6 +3,27 @@ from ase.atoms import Atoms
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def _get_fixed_atom_boolean_vector(structure: Atoms) -> np.ndarray:
|
|
6
|
+
"""
|
|
7
|
+
Convert ASE constraints to a per-atom, per-direction boolean array.
|
|
8
|
+
|
|
9
|
+
Translates ASE :class:`~ase.constraints.FixAtoms` and
|
|
10
|
+
:class:`~ase.constraints.FixedPlane` constraints on ``structure`` into a
|
|
11
|
+
boolean array where ``True`` indicates that the corresponding degree of
|
|
12
|
+
freedom is frozen.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
structure (ase.atoms.Atoms): Structure whose ``constraints`` attribute
|
|
16
|
+
is inspected.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
numpy.ndarray: Boolean array of shape ``(N, 3)`` where ``True`` means
|
|
20
|
+
the atom's motion in that Cartesian direction is fixed.
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
ValueError: If a constraint type other than ``FixAtoms`` or
|
|
24
|
+
``FixedPlane`` is encountered, or if the ``FixedPlane`` direction
|
|
25
|
+
is not one of the supported axis combinations.
|
|
26
|
+
"""
|
|
6
27
|
fixed_atom_vector = np.array([[False, False, False]] * len(structure))
|
|
7
28
|
for c in structure.constraints:
|
|
8
29
|
c_dict = c.todict()
|
|
@@ -28,6 +49,37 @@ def _get_fixed_atom_boolean_vector(structure: Atoms) -> np.ndarray:
|
|
|
28
49
|
|
|
29
50
|
|
|
30
51
|
def set_selective_dynamics(structure: Atoms, calc_md: bool = False) -> dict[str, str]:
|
|
52
|
+
"""
|
|
53
|
+
Translate ASE constraints into LAMMPS ``group`` / ``fix setforce`` commands.
|
|
54
|
+
|
|
55
|
+
Groups atoms by their frozen degrees of freedom and generates the
|
|
56
|
+
corresponding LAMMPS commands to zero the force (and optionally the
|
|
57
|
+
velocity) on those atoms. Supports constraints on individual Cartesian
|
|
58
|
+
directions (x, y, z) as well as the combined xy, yz, and zx pairs.
|
|
59
|
+
|
|
60
|
+
The LAMMPS equivalent of ASE's ``FixAtoms`` is ``fix setforce 0.0 0.0 0.0``
|
|
61
|
+
and ``FixedPlane`` along an axis is ``fix setforce 0.0 NULL NULL`` (or the
|
|
62
|
+
appropriate permutation).
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
structure (ase.atoms.Atoms): Structure whose ``constraints`` are
|
|
66
|
+
mapped to LAMMPS commands.
|
|
67
|
+
calc_md (bool): If ``True``, also add ``velocity … set`` commands to
|
|
68
|
+
initialise constrained atoms with zero velocity (required for MD
|
|
69
|
+
to prevent initial kinetic energy in frozen directions).
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
dict[str, str]: Ordered mapping of LAMMPS command keyword to the rest
|
|
73
|
+
of the command line. For example::
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
"group constraintxyz": "id 1 2 3",
|
|
77
|
+
"fix constraintxyz": "constraintxyz setforce 0.0 0.0 0.0",
|
|
78
|
+
"velocity constraintxyz": "set 0.0 0.0 0.0", # only when calc_md=True
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Returns an empty dict if the structure has no constraints.
|
|
82
|
+
"""
|
|
31
83
|
control_dict: dict[str, str] = {}
|
|
32
84
|
if len(structure.constraints) > 0:
|
|
33
85
|
sel_dyn = _get_fixed_atom_boolean_vector(structure=structure)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CalcMDInput:
|
|
9
|
+
"""
|
|
10
|
+
Input parameters for a LAMMPS molecular-dynamics run via :func:`~lammpsparser.compatibility.calculate.calc_md`.
|
|
11
|
+
|
|
12
|
+
All time-like values (``time_step``, ``temperature_damping_timescale``,
|
|
13
|
+
``pressure_damping_timescale``) are given in femtoseconds and are
|
|
14
|
+
internally rescaled to the target LAMMPS unit system before being written
|
|
15
|
+
to the input script.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
temperature: Target temperature in K. Scalar for a fixed target, or
|
|
19
|
+
a two-element list ``[T_start, T_end]`` for a linear ramp. If
|
|
20
|
+
``None``, an NVE run is performed.
|
|
21
|
+
pressure: Target pressure in GPa. Scalar for isotropic, list of up
|
|
22
|
+
to 6 values for ``[xx, yy, zz, xy, xz, yz]`` components. ``None``
|
|
23
|
+
values leave the corresponding cell degree of freedom unconstrained.
|
|
24
|
+
If ``None``, no barostat is applied.
|
|
25
|
+
n_ionic_steps: Number of MD timesteps to run.
|
|
26
|
+
time_step: MD timestep in fs (default: ``1.0``).
|
|
27
|
+
n_print: Write thermo and dump output every this many steps.
|
|
28
|
+
temperature_damping_timescale: Nosé–Hoover thermostat coupling time
|
|
29
|
+
in fs (LAMMPS ``Tdamp``).
|
|
30
|
+
pressure_damping_timescale: Nosé–Hoover barostat coupling time in fs
|
|
31
|
+
(LAMMPS ``Pdamp``).
|
|
32
|
+
seed: Random seed for velocity initialisation and Langevin dynamics.
|
|
33
|
+
tloop: Optional ``tloop`` argument for the ``fix nvt/npt`` command.
|
|
34
|
+
initial_temperature: Temperature used to initialise velocities.
|
|
35
|
+
``None`` defaults to twice the target temperature. ``0`` skips
|
|
36
|
+
velocity initialisation.
|
|
37
|
+
langevin: Use Langevin dynamics instead of Nosé–Hoover.
|
|
38
|
+
delta_temp: Deprecated alias for ``temperature_damping_timescale``.
|
|
39
|
+
delta_press: Deprecated alias for ``pressure_damping_timescale``.
|
|
40
|
+
rotation_matrix: 3×3 rotation matrix from the ASE to LAMMPS coordinate
|
|
41
|
+
frame. Derived automatically when ``None``.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
temperature: Optional[Union[float, list]] = None
|
|
45
|
+
pressure: Optional[Union[float, list, np.ndarray]] = None
|
|
46
|
+
n_ionic_steps: int = 1
|
|
47
|
+
time_step: float = 1.0
|
|
48
|
+
n_print: int = 100
|
|
49
|
+
temperature_damping_timescale: float = 100.0
|
|
50
|
+
pressure_damping_timescale: float = 1000.0
|
|
51
|
+
seed: int = 80996
|
|
52
|
+
tloop: Optional[int] = None
|
|
53
|
+
initial_temperature: Optional[float] = None
|
|
54
|
+
langevin: bool = False
|
|
55
|
+
delta_temp: Optional[float] = None
|
|
56
|
+
delta_press: Optional[float] = None
|
|
57
|
+
rotation_matrix: Optional[Union[list, np.ndarray]] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class CalcMinimizeInput:
|
|
62
|
+
"""
|
|
63
|
+
Input parameters for a LAMMPS geometry optimisation via :func:`~lammpsparser.compatibility.calculate.calc_minimize`.
|
|
64
|
+
|
|
65
|
+
Attributes:
|
|
66
|
+
ionic_energy_tolerance: Convergence criterion on the energy change
|
|
67
|
+
between consecutive steps in eV (LAMMPS ``etol``). The
|
|
68
|
+
minimisation stops when ``|ΔE| ≤ ionic_energy_tolerance``.
|
|
69
|
+
ionic_force_tolerance: Convergence criterion on the global force
|
|
70
|
+
vector magnitude in eV/Å (LAMMPS ``ftol``).
|
|
71
|
+
max_iter: Maximum number of minimisation iterations.
|
|
72
|
+
pressure: Target pressure in GPa for cell relaxation. Scalar for
|
|
73
|
+
isotropic, list of up to 6 Voigt components. ``None`` means
|
|
74
|
+
fixed-volume relaxation.
|
|
75
|
+
n_print: Write thermo and dump output every this many steps.
|
|
76
|
+
style: LAMMPS ``min_style`` keyword (e.g. ``"cg"`` for conjugate
|
|
77
|
+
gradient, ``"sd"`` for steepest descent).
|
|
78
|
+
rotation_matrix: 3×3 rotation matrix from the ASE to LAMMPS frame.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
ionic_energy_tolerance: float = 0.0
|
|
82
|
+
ionic_force_tolerance: float = 1e-4
|
|
83
|
+
max_iter: int = 100000
|
|
84
|
+
pressure: Optional[Union[float, list, np.ndarray]] = None
|
|
85
|
+
n_print: int = 1
|
|
86
|
+
style: str = "cg"
|
|
87
|
+
rotation_matrix: Optional[Union[list, np.ndarray]] = None
|
|
@@ -149,7 +149,7 @@ def lammps_file_interface_function(
|
|
|
149
149
|
]
|
|
150
150
|
|
|
151
151
|
if calc_mode == "static":
|
|
152
|
-
|
|
152
|
+
lmp_str_constraint_lst = [
|
|
153
153
|
k + " " + v
|
|
154
154
|
for k, v in set_selective_dynamics(
|
|
155
155
|
structure=structure, calc_md=False
|
|
@@ -158,11 +158,11 @@ def lammps_file_interface_function(
|
|
|
158
158
|
lmp_str_tmp_lst = calc_static()
|
|
159
159
|
lmp_str_lst = _modify_input_dict(
|
|
160
160
|
input_control_file=input_control_file,
|
|
161
|
-
lmp_str_lst=lmp_str_lst + lmp_str_tmp_lst[:-1],
|
|
161
|
+
lmp_str_lst=lmp_str_lst + lmp_str_tmp_lst[:-1] + lmp_str_constraint_lst,
|
|
162
162
|
)
|
|
163
163
|
lmp_str_lst.append(lmp_str_tmp_lst[-1])
|
|
164
164
|
elif calc_mode == "md":
|
|
165
|
-
|
|
165
|
+
lmp_str_constraint_lst = [
|
|
166
166
|
k + " " + v
|
|
167
167
|
for k, v in set_selective_dynamics(
|
|
168
168
|
structure=structure, calc_md=True
|
|
@@ -178,14 +178,14 @@ def lammps_file_interface_function(
|
|
|
178
178
|
lmp_str_lst += calc_md(**calc_kwargs)
|
|
179
179
|
lmp_str_lst = _modify_input_dict(
|
|
180
180
|
input_control_file=input_control_file,
|
|
181
|
-
lmp_str_lst=lmp_str_lst,
|
|
181
|
+
lmp_str_lst=lmp_str_lst + lmp_str_constraint_lst,
|
|
182
182
|
)
|
|
183
183
|
if read_restart_file:
|
|
184
184
|
lmp_str_lst += ["reset_timestep 0"]
|
|
185
185
|
lmp_str_lst += ["run {} ".format(n_ionic_steps)]
|
|
186
186
|
elif calc_mode == "minimize":
|
|
187
187
|
calc_kwargs["units"] = units
|
|
188
|
-
|
|
188
|
+
lmp_str_constraint_lst = [
|
|
189
189
|
k + " " + v
|
|
190
190
|
for k, v in set_selective_dynamics(
|
|
191
191
|
structure=structure, calc_md=False
|
|
@@ -194,7 +194,7 @@ def lammps_file_interface_function(
|
|
|
194
194
|
lmp_str_tmp_lst, structure = calc_minimize(structure=structure, **calc_kwargs)
|
|
195
195
|
lmp_str_lst = _modify_input_dict(
|
|
196
196
|
input_control_file=input_control_file,
|
|
197
|
-
lmp_str_lst=lmp_str_lst + lmp_str_tmp_lst[:-1],
|
|
197
|
+
lmp_str_lst=lmp_str_lst + lmp_str_tmp_lst[:-1] + lmp_str_constraint_lst,
|
|
198
198
|
)
|
|
199
199
|
lmp_str_lst.append(lmp_str_tmp_lst[-1])
|
|
200
200
|
else:
|
|
@@ -245,6 +245,28 @@ def lammps_file_interface_function(
|
|
|
245
245
|
def lammps_file_initialization(
|
|
246
246
|
structure, dimension=3, units="metal", read_restart_file=False, restart_file=None
|
|
247
247
|
):
|
|
248
|
+
"""
|
|
249
|
+
Generate the LAMMPS input script preamble (units, boundary, atom_style, read_data).
|
|
250
|
+
|
|
251
|
+
Produces the first lines of a LAMMPS input script that set up the
|
|
252
|
+
simulation box. When restarting from a binary restart file the
|
|
253
|
+
``read_restart`` command is used instead of ``read_data``. Boundary
|
|
254
|
+
conditions are derived from the ASE ``pbc`` attribute of ``structure``
|
|
255
|
+
(``p`` = periodic, ``f`` = fixed).
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
structure (ase.atoms.Atoms): Structure whose periodic boundary
|
|
259
|
+
conditions determine the ``boundary`` command.
|
|
260
|
+
dimension (int): Spatial dimensionality (default: ``3``).
|
|
261
|
+
units (str): LAMMPS unit system (default: ``"metal"``).
|
|
262
|
+
read_restart_file (bool): If ``True``, emit ``read_restart`` instead
|
|
263
|
+
of ``read_data`` (default: ``False``).
|
|
264
|
+
restart_file (str, optional): Filename of the restart file; required
|
|
265
|
+
when ``read_restart_file=True``.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
list[str]: LAMMPS input lines for the initialisation section.
|
|
269
|
+
"""
|
|
248
270
|
init_commands = ["units " + units]
|
|
249
271
|
boundary = " ".join(["p" if coord else "f" for coord in structure.pbc])
|
|
250
272
|
if read_restart_file:
|
|
@@ -263,6 +285,23 @@ def _modify_input_dict(
|
|
|
263
285
|
input_control_file: Optional[dict] = None,
|
|
264
286
|
lmp_str_lst: list[str] = [],
|
|
265
287
|
):
|
|
288
|
+
"""
|
|
289
|
+
Apply user-supplied overrides to a LAMMPS input line list.
|
|
290
|
+
|
|
291
|
+
Iterates over ``lmp_str_lst`` and replaces any line whose first token
|
|
292
|
+
matches a key in ``input_control_file`` with the corresponding value.
|
|
293
|
+
Keys in ``input_control_file`` that do not match any existing line are
|
|
294
|
+
appended at the end.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
input_control_file (dict, optional): Mapping from LAMMPS command
|
|
298
|
+
keyword (e.g. ``"timestep"``) to the replacement value string
|
|
299
|
+
(e.g. ``"0.5"``). ``None`` (default) leaves the list unchanged.
|
|
300
|
+
lmp_str_lst (list[str]): Current list of LAMMPS input lines.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
list[str]: Modified (or unchanged) list of LAMMPS input lines.
|
|
304
|
+
"""
|
|
266
305
|
if input_control_file is not None:
|
|
267
306
|
lmp_tmp_lst, keys_used = [], []
|
|
268
307
|
for line in lmp_str_lst:
|
|
@@ -284,6 +323,30 @@ def _modify_input_dict(
|
|
|
284
323
|
|
|
285
324
|
|
|
286
325
|
def _get_potential(potential, resource_path: Optional[str] = None):
|
|
326
|
+
"""
|
|
327
|
+
Resolve a potential specification to LAMMPS input lines and species list.
|
|
328
|
+
|
|
329
|
+
Accepts a potential name string, a :class:`pandas.DataFrame`, or a
|
|
330
|
+
:class:`pandas.Series` and separates the ``"Config"`` lines into those
|
|
331
|
+
that override initialisation settings (``units``, ``atom_style``,
|
|
332
|
+
``dimension``) and those that are actual force-field commands
|
|
333
|
+
(``pair_style``, ``pair_coeff``, …).
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
potential (str or pandas.DataFrame or pandas.Series): Potential
|
|
337
|
+
specification. A string is looked up by name using
|
|
338
|
+
:func:`~lammpsparser.potential.get_potential_by_name`.
|
|
339
|
+
resource_path (str, optional): Path to the ``iprpy`` resource directory
|
|
340
|
+
passed to :func:`~lammpsparser.potential.get_potential_by_name`.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
tuple:
|
|
344
|
+
- list[str]: Force-field LAMMPS input lines.
|
|
345
|
+
- dict: Overrides for ``"units"``, ``"atom_style"``, and/or
|
|
346
|
+
``"dimension"`` (keys present only when the potential redefines
|
|
347
|
+
them).
|
|
348
|
+
- list[str]: Ordered chemical species as declared in the potential.
|
|
349
|
+
"""
|
|
287
350
|
if isinstance(potential, str):
|
|
288
351
|
potential_dataframe = get_potential_by_name(
|
|
289
352
|
potential_name=potential, resource_path=resource_path
|
|
@@ -21,22 +21,20 @@ class LammpsStructureCompatibility(LammpsStructure):
|
|
|
21
21
|
|
|
22
22
|
@property
|
|
23
23
|
def structure(self) -> Optional[Atoms]:
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
|
|
28
|
-
"""
|
|
24
|
+
"""The ASE :class:`~ase.atoms.Atoms` object associated with this instance."""
|
|
29
25
|
return self._structure
|
|
30
26
|
|
|
31
27
|
@structure.setter
|
|
32
28
|
def structure(self, structure):
|
|
33
29
|
"""
|
|
30
|
+
Set the atomic structure and serialise it to the internal LAMMPS data string.
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
32
|
+
Dispatches to :meth:`structure_full`, :meth:`structure_bond`,
|
|
33
|
+
:meth:`structure_charge`, or :meth:`structure_atomic` based on
|
|
34
|
+
``self._atom_type``.
|
|
39
35
|
|
|
36
|
+
Args:
|
|
37
|
+
structure (ase.atoms.Atoms): The structure to serialise.
|
|
40
38
|
"""
|
|
41
39
|
self._structure = structure
|
|
42
40
|
if self.atom_type == "full":
|
|
@@ -52,9 +50,16 @@ class LammpsStructureCompatibility(LammpsStructure):
|
|
|
52
50
|
|
|
53
51
|
def structure_bond(self):
|
|
54
52
|
"""
|
|
53
|
+
Serialise the structure for LAMMPS ``atom_style bond``.
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
Generates an ``Atoms`` block with the format
|
|
56
|
+
``atom-ID molecule-ID atom-type x y z`` and a ``Bonds`` block that
|
|
57
|
+
lists all bonds inferred from the nearest-neighbour topology (first
|
|
58
|
+
coordination shell by default, or within ``cutoff_radius`` when set).
|
|
59
|
+
Bond types are assigned based on the pair of species involved.
|
|
57
60
|
|
|
61
|
+
Returns:
|
|
62
|
+
str: Complete LAMMPS data file content (header + atoms + bonds).
|
|
58
63
|
"""
|
|
59
64
|
species_lammps_id_dict = self.get_lammps_id_dict(self.el_eam_lst)
|
|
60
65
|
self.molecule_ids = None
|
|
@@ -159,10 +164,18 @@ class LammpsStructureCompatibility(LammpsStructure):
|
|
|
159
164
|
|
|
160
165
|
def structure_full(self):
|
|
161
166
|
"""
|
|
162
|
-
|
|
167
|
+
Serialise the structure for LAMMPS ``atom_style full``.
|
|
163
168
|
|
|
164
|
-
|
|
169
|
+
Generates an ``Atoms`` block with the format
|
|
170
|
+
``atom-ID molecule-ID atom-type charge x y z`` and optional ``Bonds``
|
|
171
|
+
and ``Angles`` blocks. Bonds and angles are derived from
|
|
172
|
+
``self._bond_dict``, which specifies for each element which neighbours
|
|
173
|
+
to bond with, the cutoff distance, and the bond/angle type IDs.
|
|
174
|
+
Per-atom charges are read from the potential object.
|
|
165
175
|
|
|
176
|
+
Returns:
|
|
177
|
+
str: Complete LAMMPS data file content
|
|
178
|
+
(header + atoms + bonds + angles).
|
|
166
179
|
"""
|
|
167
180
|
species_lammps_id_dict = self.get_lammps_id_dict(self.el_eam_lst)
|
|
168
181
|
self.molecule_ids = None
|
|
@@ -311,16 +324,24 @@ def get_bonds(
|
|
|
311
324
|
structure: Atoms, radius=np.inf, max_shells=None, prec=0.1, num_neighbors=20
|
|
312
325
|
):
|
|
313
326
|
"""
|
|
327
|
+
Identify bonds in a structure based on neighbour distances.
|
|
328
|
+
|
|
329
|
+
Wraps :func:`structuretoolkit.analyse.get_neighbors` and its
|
|
330
|
+
``get_bonds`` method to return a shell-based bond list for each atom.
|
|
314
331
|
|
|
315
332
|
Args:
|
|
316
|
-
structure (ase.Atoms):
|
|
317
|
-
radius (float):
|
|
318
|
-
max_shells:
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
structure (ase.atoms.Atoms): Atomic structure to analyse.
|
|
334
|
+
radius (float): Maximum bond length in Å (default: ``numpy.inf``).
|
|
335
|
+
max_shells (int, optional): Maximum number of coordination shells to
|
|
336
|
+
include. ``None`` (default) imposes no limit.
|
|
337
|
+
prec (float): Minimum distance gap between two shells; shells closer
|
|
338
|
+
than ``prec`` are merged into one (default: ``0.1`` Å).
|
|
339
|
+
num_neighbors (int): Upper bound on the number of neighbours to
|
|
340
|
+
consider per atom (default: ``20``).
|
|
321
341
|
|
|
322
342
|
Returns:
|
|
323
|
-
|
|
343
|
+
list: Nested bond list as returned by
|
|
344
|
+
:meth:`structuretoolkit.analyse.neighbors.get_bonds`.
|
|
324
345
|
"""
|
|
325
346
|
neighbors = get_neighbors(
|
|
326
347
|
structure=structure,
|
|
@@ -68,6 +68,54 @@ def parse_lammps_output(
|
|
|
68
68
|
log_lammps_file_name: str = "log.lammps",
|
|
69
69
|
remap_indices_funct: Callable[..., np.ndarray] = remap_indices_ase,
|
|
70
70
|
) -> Dict[str, Dict[str, Any]]:
|
|
71
|
+
"""
|
|
72
|
+
Parse all output files from a finished LAMMPS calculation.
|
|
73
|
+
|
|
74
|
+
Looks for a dump file (H5MD format preferred, text format as fallback) and
|
|
75
|
+
a log file in ``working_directory``, converts all quantities from the
|
|
76
|
+
LAMMPS unit system to pyiron/ASE units (Å, eV, ps, …), and returns the
|
|
77
|
+
results in a nested dictionary that can be stored directly in an HDF5 file.
|
|
78
|
+
|
|
79
|
+
The function resolves the LAMMPS triclinic-to-orthogonal coordinate
|
|
80
|
+
rotation via :class:`~lammpsparser.structure.UnfoldingPrism` and re-maps
|
|
81
|
+
atom-type indices back to structure indices via ``remap_indices_funct``.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
working_directory (str): Directory that contains the LAMMPS output files.
|
|
85
|
+
structure (ase.atoms.Atoms): The input structure used for the calculation.
|
|
86
|
+
Required for index remapping and prism construction.
|
|
87
|
+
potential_elements (numpy.ndarray or list): Ordered list of chemical
|
|
88
|
+
symbols as they appear in the LAMMPS potential definition. The
|
|
89
|
+
order determines the mapping from LAMMPS integer type IDs to
|
|
90
|
+
element symbols.
|
|
91
|
+
units (str): LAMMPS unit system used in the calculation (e.g.
|
|
92
|
+
``"metal"``, ``"real"``, ``"si"``, ``"cgs"``, ``"electron"``).
|
|
93
|
+
prism (UnfoldingPrism, optional): Pre-constructed prism object. If
|
|
94
|
+
``None`` (default) a new one is built from ``structure.cell``.
|
|
95
|
+
dump_h5_file_name (str): Filename of the H5MD dump inside
|
|
96
|
+
``working_directory`` (default: ``"dump.h5"``).
|
|
97
|
+
dump_out_file_name (str): Filename of the text dump inside
|
|
98
|
+
``working_directory`` (default: ``"dump.out"``).
|
|
99
|
+
log_lammps_file_name (str): Filename of the LAMMPS log file inside
|
|
100
|
+
``working_directory`` (default: ``"log.lammps"``).
|
|
101
|
+
remap_indices_funct (callable): Function used to map LAMMPS integer
|
|
102
|
+
type indices onto structure indices. Defaults to
|
|
103
|
+
:func:`remap_indices_ase`.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
dict: Nested dictionary with two top-level keys:
|
|
107
|
+
|
|
108
|
+
- ``"generic"`` – quantities stored in pyiron/ASE units:
|
|
109
|
+
``"steps"``, ``"cells"``, ``"positions"``, ``"forces"``,
|
|
110
|
+
``"velocities"``, ``"indices"``, ``"temperature"``,
|
|
111
|
+
``"energy_pot"``, ``"energy_tot"``, ``"volume"``,
|
|
112
|
+
``"pressures"`` (if available), and any per-atom computes.
|
|
113
|
+
- ``"lammps"`` – raw LAMMPS-specific thermo columns that have no
|
|
114
|
+
generic equivalent.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
FileNotFoundError: If neither the H5MD nor the text dump file exists.
|
|
118
|
+
"""
|
|
71
119
|
if prism is None:
|
|
72
120
|
prism = UnfoldingPrism(structure.cell)
|
|
73
121
|
dump_dict = _parse_dump(
|
|
@@ -130,6 +178,28 @@ def _parse_dump(
|
|
|
130
178
|
potential_elements: Union[np.ndarray, List],
|
|
131
179
|
remap_indices_funct: Callable[..., np.ndarray] = remap_indices_ase,
|
|
132
180
|
) -> Dict[str, Any]:
|
|
181
|
+
"""
|
|
182
|
+
Dispatch dump parsing to the correct reader (H5MD or text).
|
|
183
|
+
|
|
184
|
+
Prefers the H5MD file when it exists. H5MD files do not require
|
|
185
|
+
coordinate rotation, but the prism must be orthorhombic; a
|
|
186
|
+
:exc:`RuntimeError` is raised when that condition is violated.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
dump_h5_full_file_name (str): Absolute path to the H5MD dump file.
|
|
190
|
+
dump_out_full_file_name (str): Absolute path to the text dump file.
|
|
191
|
+
prism (UnfoldingPrism): Prism object for coordinate transformations.
|
|
192
|
+
structure (ase.atoms.Atoms): Input structure for index remapping.
|
|
193
|
+
potential_elements (numpy.ndarray or list): Ordered element list for index remapping.
|
|
194
|
+
remap_indices_funct (callable): Index remapping function.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
dict: Raw dump data dictionary as returned by the underlying parser.
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
RuntimeError: If an H5MD file is present but the prism is not orthorhombic.
|
|
201
|
+
FileNotFoundError: If neither dump file exists.
|
|
202
|
+
"""
|
|
133
203
|
if os.path.isfile(dump_h5_full_file_name):
|
|
134
204
|
if not _check_ortho_prism(prism=prism):
|
|
135
205
|
raise RuntimeError(
|
|
@@ -160,7 +230,27 @@ def _collect_dump_from_text(
|
|
|
160
230
|
remap_indices_funct: Callable[..., np.ndarray] = remap_indices_ase,
|
|
161
231
|
) -> Dict[str, Any]:
|
|
162
232
|
"""
|
|
163
|
-
|
|
233
|
+
Post-process a raw text dump dictionary: rotate vectors and remap indices.
|
|
234
|
+
|
|
235
|
+
Applies the inverse of the LAMMPS coordinate rotation to forces, velocities,
|
|
236
|
+
and positions so that all vectors are expressed in the original ASE cell
|
|
237
|
+
frame. Cell matrices are unfolded via
|
|
238
|
+
:meth:`~lammpsparser.structure.UnfoldingPrism.unfold_cell` and atom-type
|
|
239
|
+
indices are remapped from LAMMPS integer IDs to structure indices.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
file_name (str): Path to the LAMMPS text dump file.
|
|
243
|
+
prism (UnfoldingPrism): Prism built from the input structure's cell.
|
|
244
|
+
structure (ase.atoms.Atoms): Input structure used for index remapping.
|
|
245
|
+
potential_elements (numpy.ndarray or list): Ordered element list.
|
|
246
|
+
remap_indices_funct (callable): Function mapping LAMMPS type IDs to
|
|
247
|
+
structure indices (default: :func:`remap_indices_ase`).
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
dict: Processed dump dictionary with the same keys as
|
|
251
|
+
:func:`~lammpsparser.output_raw.parse_raw_dump_from_text` but with
|
|
252
|
+
all vector quantities rotated to the ASE frame and Cartesian positions
|
|
253
|
+
computed from fractional coordinates.
|
|
164
254
|
"""
|
|
165
255
|
rotation_lammps2orig = prism.R.T
|
|
166
256
|
dump_lammps_dict = parse_raw_dump_from_text(file_name=file_name)
|
|
@@ -229,7 +319,29 @@ def _collect_output_log(
|
|
|
229
319
|
file_name: str, prism: UnfoldingPrism
|
|
230
320
|
) -> Tuple[List[str], Dict, pd.DataFrame]:
|
|
231
321
|
"""
|
|
232
|
-
|
|
322
|
+
Parse the LAMMPS log file and organise thermo data into generic and pressure outputs.
|
|
323
|
+
|
|
324
|
+
Renames standard LAMMPS thermo column names to pyiron equivalents
|
|
325
|
+
(e.g. ``Temp`` → ``temperature``, ``PotEng`` → ``energy_pot``).
|
|
326
|
+
If all six independent Voigt pressure components (``Pxx``, ``Pyy``,
|
|
327
|
+
``Pzz``, ``Pxy``, ``Pxz``, ``Pyz``) are present they are assembled into
|
|
328
|
+
a symmetric 3×3 pressure tensor per frame and rotated to the ASE frame
|
|
329
|
+
when the prism rotation is non-trivial. Time-averaged pressures from
|
|
330
|
+
``fix ave/time`` (``mean_pressure[1-6]``) are handled analogously.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
file_name (str): Path to the LAMMPS log file.
|
|
334
|
+
prism (UnfoldingPrism): Prism object used to rotate the pressure
|
|
335
|
+
tensor when the simulation cell is triclinic.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
tuple:
|
|
339
|
+
- list[str]: Column names that belong in the ``"generic"`` output
|
|
340
|
+
dictionary.
|
|
341
|
+
- dict: Pressure arrays keyed by ``"pressures"`` and/or
|
|
342
|
+
``"mean_pressures"``, shape ``(N_frames, 3, 3)``.
|
|
343
|
+
- pandas.DataFrame: Remaining thermo data (pressure columns
|
|
344
|
+
removed) with renamed columns.
|
|
233
345
|
"""
|
|
234
346
|
df = parse_raw_lammps_log(file_name=file_name)
|
|
235
347
|
|
|
@@ -26,6 +26,31 @@ class DumpData:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def to_amat(l_list: Union[np.ndarray, List]) -> List:
|
|
29
|
+
"""
|
|
30
|
+
Convert LAMMPS box bounds to a cell matrix in the lower-triangular convention used by ASE.
|
|
31
|
+
|
|
32
|
+
LAMMPS stores triclinic box boundaries as ``xlo_bound``, ``xhi_bound``, ``xy``,
|
|
33
|
+
``ylo_bound``, ``yhi_bound``, ``xz``, ``zlo_bound``, ``zhi_bound``, ``yz``
|
|
34
|
+
(the 9-element form written by ``dump … BOX BOUNDS xy xz yz``), or as the
|
|
35
|
+
6-element orthorhombic form without tilt factors. This function recovers the
|
|
36
|
+
conventional cell vectors ``a = (xhilo, 0, 0)``, ``b = (xy, yhilo, 0)``,
|
|
37
|
+
``c = (xz, yz, zhilo)`` following the conversion documented in the LAMMPS
|
|
38
|
+
manual under *Triclinic (non-orthogonal) simulation boxes*.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
l_list (numpy.ndarray or list): Flattened sequence of either 6 box-bound
|
|
42
|
+
values ``[xlo_bound, xhi_bound, ylo_bound, yhi_bound, zlo_bound,
|
|
43
|
+
zhi_bound]`` (orthogonal box) or 9 values ``[xlo_bound, xhi_bound,
|
|
44
|
+
xy, ylo_bound, yhi_bound, xz, zlo_bound, zhi_bound, yz]``
|
|
45
|
+
(triclinic box).
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
list: 3×3 nested list representing the lower-triangular cell matrix
|
|
49
|
+
``[[a, 0, 0], [xy, b, 0], [xz, yz, c]]``.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If ``l_list`` does not contain exactly 6 or 9 elements.
|
|
53
|
+
"""
|
|
29
54
|
lst = np.reshape(l_list, -1)
|
|
30
55
|
if len(lst) == 9:
|
|
31
56
|
(
|
|
@@ -65,6 +90,25 @@ def to_amat(l_list: Union[np.ndarray, List]) -> List:
|
|
|
65
90
|
|
|
66
91
|
|
|
67
92
|
def parse_raw_dump_from_h5md(file_name: str) -> Dict:
|
|
93
|
+
"""
|
|
94
|
+
Parse a LAMMPS dump file written in H5MD format.
|
|
95
|
+
|
|
96
|
+
H5MD (HDF5 Molecular Dynamics) output is produced when LAMMPS is compiled
|
|
97
|
+
with the H5MD package and the input script uses ``dump … h5md``. The
|
|
98
|
+
function reads positions, forces, simulation steps, and cell edge lengths
|
|
99
|
+
from the standard H5MD path ``/particles/all/…``.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
file_name (str): Path to the H5MD dump file (typically ``dump.h5``).
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
dict: Dictionary with the following keys:
|
|
106
|
+
|
|
107
|
+
- ``"forces"`` (list of list): Per-atom forces for each snapshot.
|
|
108
|
+
- ``"positions"`` (list of list): Per-atom Cartesian positions for each snapshot.
|
|
109
|
+
- ``"steps"`` (list of int): Simulation timestep indices.
|
|
110
|
+
- ``"cells"`` (list of numpy.ndarray): 3×3 diagonal cell matrices for each snapshot.
|
|
111
|
+
"""
|
|
68
112
|
import h5py
|
|
69
113
|
|
|
70
114
|
with h5py.File(file_name, mode="r", libver="latest", swmr=True) as h5md:
|
|
@@ -86,13 +130,38 @@ def parse_raw_dump_from_h5md(file_name: str) -> Dict:
|
|
|
86
130
|
|
|
87
131
|
def parse_raw_dump_from_text(file_name: str) -> Dict:
|
|
88
132
|
"""
|
|
89
|
-
|
|
133
|
+
Parse a LAMMPS custom text dump file into a structured dictionary.
|
|
134
|
+
|
|
135
|
+
The file must have been created with a ``dump … custom`` command that writes
|
|
136
|
+
at least the columns ``id``, ``type``, ``xsu``, ``ysu``, ``zsu``, ``fx``,
|
|
137
|
+
``fy``, ``fz``. Optional columns ``vx``/``vy``/``vz`` (velocities),
|
|
138
|
+
``f_mean_forces[1-3]``, ``f_mean_velocities[1-3]``, ``f_mean_positions[1-3]``
|
|
139
|
+
(time-averaged fixes), and any per-atom compute columns starting with ``c_``
|
|
140
|
+
are handled automatically.
|
|
141
|
+
|
|
142
|
+
Coordinates are expected as scaled (fractional) unwrapped positions
|
|
143
|
+
(``xsu``/``ysu``/``zsu``). Wrapped fractional positions are derived by
|
|
144
|
+
taking ``xsu - floor(xsu)``.
|
|
90
145
|
|
|
91
146
|
Args:
|
|
92
|
-
file_name (str):
|
|
147
|
+
file_name (str): Path to the LAMMPS text dump file (typically ``dump.out``).
|
|
93
148
|
|
|
94
149
|
Returns:
|
|
95
|
-
|
|
150
|
+
dict: Dictionary with the following keys (empty lists when the
|
|
151
|
+
corresponding columns were absent from the dump):
|
|
152
|
+
|
|
153
|
+
- ``"steps"`` (list of int): Simulation timestep indices.
|
|
154
|
+
- ``"natoms"`` (list of int): Number of atoms at each snapshot.
|
|
155
|
+
- ``"cells"`` (list of list): 3×3 cell matrices (one per snapshot).
|
|
156
|
+
- ``"indices"`` (list of numpy.ndarray): Integer LAMMPS atom-type indices (1-based).
|
|
157
|
+
- ``"forces"`` (list of numpy.ndarray): Per-atom force vectors, shape ``(N, 3)``.
|
|
158
|
+
- ``"mean_forces"`` (list of numpy.ndarray): Time-averaged forces from ``fix ave/atom``.
|
|
159
|
+
- ``"velocities"`` (list of numpy.ndarray): Per-atom velocity vectors, shape ``(N, 3)``.
|
|
160
|
+
- ``"mean_velocities"`` (list of numpy.ndarray): Time-averaged velocities.
|
|
161
|
+
- ``"unwrapped_positions"`` (list of numpy.ndarray): Unwrapped fractional coordinates, shape ``(N, 3)``.
|
|
162
|
+
- ``"mean_unwrapped_positions"`` (list of numpy.ndarray): Time-averaged unwrapped positions.
|
|
163
|
+
- ``"positions"`` (list of numpy.ndarray): Wrapped fractional coordinates, shape ``(N, 3)``.
|
|
164
|
+
- ``"computes"`` (dict): Per-atom compute results keyed by compute ID (``c_`` prefix stripped).
|
|
96
165
|
"""
|
|
97
166
|
with open(file_name, "r") as f:
|
|
98
167
|
dump = DumpData()
|
|
@@ -210,13 +279,29 @@ def parse_raw_dump_from_text(file_name: str) -> Dict:
|
|
|
210
279
|
|
|
211
280
|
def parse_raw_lammps_log(file_name: str) -> pd.DataFrame:
|
|
212
281
|
"""
|
|
213
|
-
|
|
282
|
+
Parse the thermodynamic output from a LAMMPS log file.
|
|
283
|
+
|
|
284
|
+
LAMMPS writes thermo output to ``log.lammps`` (or a user-specified log
|
|
285
|
+
file) between a header line that starts with ``Step`` and a footer line
|
|
286
|
+
that starts with ``Loop``. Multiple ``run`` commands in a single input
|
|
287
|
+
script produce multiple such blocks; all blocks are concatenated and an
|
|
288
|
+
extra ``LogStep`` column is added to identify which run each row belongs
|
|
289
|
+
to.
|
|
290
|
+
|
|
291
|
+
Lines beginning with ``WARNING:`` inside a thermo block are forwarded as
|
|
292
|
+
Python :func:`warnings.warn` calls. Lines beginning with ``ERROR`` cause
|
|
293
|
+
the current thermo block to be closed (the error is not re-raised here,
|
|
294
|
+
but the resulting DataFrame will be truncated).
|
|
214
295
|
|
|
215
296
|
Args:
|
|
216
|
-
file_name (str):
|
|
297
|
+
file_name (str): Path to the LAMMPS log file (typically ``log.lammps``).
|
|
217
298
|
|
|
218
299
|
Returns:
|
|
219
|
-
|
|
300
|
+
pandas.DataFrame: DataFrame whose columns match the thermo keywords
|
|
301
|
+
used in the LAMMPS ``thermo_style custom`` command (e.g. ``Step``,
|
|
302
|
+
``Temp``, ``PotEng``, ``TotEng``, ``Pxx``, …). When multiple runs
|
|
303
|
+
are present a ``LogStep`` column (integer run index, 0-based) is
|
|
304
|
+
appended.
|
|
220
305
|
"""
|
|
221
306
|
with open(file_name, "r") as f:
|
|
222
307
|
dfs = []
|
|
@@ -84,6 +84,20 @@ class PotentialAbstract:
|
|
|
84
84
|
]
|
|
85
85
|
|
|
86
86
|
def find_by_name(self, potential_name: str) -> pandas.DataFrame:
|
|
87
|
+
"""
|
|
88
|
+
Return the row(s) in the potential database matching an exact potential name.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
potential_name (str): Exact name of the potential as listed in the
|
|
92
|
+
``"Name"`` column of the database (e.g.
|
|
93
|
+
``"2009--Mendelev-M-I--Al-Mg--LAMMPS--ipr1"``).
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
pandas.DataFrame: Subset of the potential database with matching rows.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
ValueError: If no potential with that name is found.
|
|
100
|
+
"""
|
|
87
101
|
mask = self._potential_df["Name"] == potential_name
|
|
88
102
|
if not mask.any():
|
|
89
103
|
raise ValueError(f"Potential '{potential_name}' not found in database.")
|
|
@@ -116,13 +130,22 @@ class PotentialAbstract:
|
|
|
116
130
|
@staticmethod
|
|
117
131
|
def _get_potential_df(file_name_lst, resource_path):
|
|
118
132
|
"""
|
|
133
|
+
Walk ``resource_path`` and load the first CSV file whose name is in ``file_name_lst``.
|
|
134
|
+
|
|
135
|
+
The CSV is parsed with converters for the ``"Species"``, ``"Config"``,
|
|
136
|
+
and ``"Filename"`` columns so that Python lists stored as strings are
|
|
137
|
+
deserialised automatically.
|
|
119
138
|
|
|
120
139
|
Args:
|
|
121
|
-
file_name_lst (set):
|
|
122
|
-
|
|
140
|
+
file_name_lst (set): Set of candidate CSV filenames to search for
|
|
141
|
+
(e.g. ``{"potentials_lammps.csv"}``).
|
|
142
|
+
resource_path (str): Root directory to search recursively.
|
|
123
143
|
|
|
124
144
|
Returns:
|
|
125
|
-
pandas.DataFrame:
|
|
145
|
+
pandas.DataFrame: Potential database with one row per potential.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
ValueError: If no matching CSV is found under ``resource_path``.
|
|
126
149
|
"""
|
|
127
150
|
for path, _folder_lst, file_lst in os.walk(resource_path):
|
|
128
151
|
for periodic_table_file_name in file_name_lst:
|
|
@@ -234,6 +257,18 @@ class LammpsPotentialFile(PotentialAbstract):
|
|
|
234
257
|
|
|
235
258
|
|
|
236
259
|
class PotentialAvailable:
|
|
260
|
+
"""
|
|
261
|
+
Provides attribute-style access to a list of potential names.
|
|
262
|
+
|
|
263
|
+
Each potential name is exposed as an attribute with ``-`` and ``.``
|
|
264
|
+
replaced by ``_`` and a ``pot_`` prefix added, so that tab-completion
|
|
265
|
+
in interactive sessions works without quoting. The original name string
|
|
266
|
+
is returned when the attribute is accessed.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
list_of_potentials (list[str]): List of potential name strings.
|
|
270
|
+
"""
|
|
271
|
+
|
|
237
272
|
def __init__(self, list_of_potentials):
|
|
238
273
|
self._list_of_potentials = {
|
|
239
274
|
"pot_" + v.replace("-", "_").replace(".", "_"): v
|
|
@@ -254,6 +289,26 @@ class PotentialAvailable:
|
|
|
254
289
|
|
|
255
290
|
|
|
256
291
|
def find_potential_file_base(path, resource_path_lst, rel_path):
|
|
292
|
+
"""
|
|
293
|
+
Locate a potential file by searching a list of resource directories.
|
|
294
|
+
|
|
295
|
+
Checks both a direct path (``<resource_path>/<path>``) and an indirect
|
|
296
|
+
path (``<resource_path>/<rel_path>/<path>``) for each entry in
|
|
297
|
+
``resource_path_lst``.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
path (str or None): Filename or relative path of the potential file.
|
|
301
|
+
resource_path_lst (list[str]): Directories to search.
|
|
302
|
+
rel_path (str): Subdirectory appended to each resource path for the
|
|
303
|
+
indirect check.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
str: Absolute path to the first matching potential file.
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
ValueError: If ``path`` is ``None`` or no match is found in any
|
|
310
|
+
resource directory.
|
|
311
|
+
"""
|
|
257
312
|
if path is not None:
|
|
258
313
|
for resource_path in resource_path_lst:
|
|
259
314
|
path_direct = os.path.join(resource_path, path)
|
|
@@ -309,6 +364,23 @@ def convert_path_to_abs_posix(path: str) -> str:
|
|
|
309
364
|
def update_potential_paths(
|
|
310
365
|
df_pot: pandas.DataFrame, resource_path: str
|
|
311
366
|
) -> pandas.DataFrame:
|
|
367
|
+
"""
|
|
368
|
+
Replace bare filenames in the ``"Config"`` column with absolute paths.
|
|
369
|
+
|
|
370
|
+
LAMMPS ``pair_coeff`` commands reference potential files by filename only.
|
|
371
|
+
This function prepends ``resource_path`` so that LAMMPS can find the files
|
|
372
|
+
regardless of the working directory.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
df_pot (pandas.DataFrame): Potential dataframe as returned by
|
|
376
|
+
:meth:`~lammpsparser.potential.PotentialAbstract.find` or
|
|
377
|
+
:func:`view_potentials`.
|
|
378
|
+
resource_path (str): Directory that contains the potential files.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
pandas.DataFrame: Copy of ``df_pot`` with ``"Config"`` entries updated
|
|
382
|
+
to use absolute paths.
|
|
383
|
+
"""
|
|
312
384
|
config_lst = []
|
|
313
385
|
for row in df_pot.itertuples():
|
|
314
386
|
potential_file_lst = row.Filename
|
|
@@ -330,6 +402,25 @@ def update_potential_paths(
|
|
|
330
402
|
def get_resource_path_from_conda(
|
|
331
403
|
env_variables: tuple[str, ...] = ("CONDA_PREFIX", "CONDA_DIR"),
|
|
332
404
|
) -> str:
|
|
405
|
+
"""
|
|
406
|
+
Locate the ``iprpy-data`` resource directory from conda environment variables.
|
|
407
|
+
|
|
408
|
+
Checks each variable in ``env_variables`` and returns the path
|
|
409
|
+
``<conda_prefix>/share/iprpy`` for the first one that resolves to an
|
|
410
|
+
existing directory. The ``iprpy-data`` conda package installs the
|
|
411
|
+
``potentials_lammps.csv`` index and potential files under this path.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
env_variables (tuple[str, ...]): Environment variable names to check,
|
|
415
|
+
in priority order (default: ``("CONDA_PREFIX", "CONDA_DIR")``).
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
str: Absolute path to the ``iprpy`` resource directory.
|
|
419
|
+
|
|
420
|
+
Raises:
|
|
421
|
+
ValueError: If none of the environment variables is set or the
|
|
422
|
+
corresponding directory does not exist.
|
|
423
|
+
"""
|
|
333
424
|
env = os.environ
|
|
334
425
|
for conda_var in env_variables:
|
|
335
426
|
if conda_var in env:
|
|
@@ -340,6 +431,22 @@ def get_resource_path_from_conda(
|
|
|
340
431
|
|
|
341
432
|
|
|
342
433
|
def get_potential_dataframe(structure: Atoms, resource_path=None):
|
|
434
|
+
"""
|
|
435
|
+
Return a dataframe of all potentials compatible with a given structure.
|
|
436
|
+
|
|
437
|
+
Combines :func:`view_potentials` and :func:`update_potential_paths` so
|
|
438
|
+
the ``"Config"`` column contains ready-to-use absolute paths to potential
|
|
439
|
+
files.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
structure (ase.atoms.Atoms): Structure whose chemical species are used
|
|
443
|
+
to filter the potential database.
|
|
444
|
+
resource_path (str, optional): Path to the ``iprpy`` resource directory.
|
|
445
|
+
If ``None``, :func:`get_resource_path_from_conda` is called.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
pandas.DataFrame: Filtered potential dataframe with absolute file paths.
|
|
449
|
+
"""
|
|
343
450
|
if resource_path is None:
|
|
344
451
|
resource_path = get_resource_path_from_conda()
|
|
345
452
|
return update_potential_paths(
|
|
@@ -349,6 +456,23 @@ def get_potential_dataframe(structure: Atoms, resource_path=None):
|
|
|
349
456
|
|
|
350
457
|
|
|
351
458
|
def get_potential_by_name(potential_name: str, resource_path: Optional[str] = None):
|
|
459
|
+
"""
|
|
460
|
+
Retrieve a single potential row by its exact name string.
|
|
461
|
+
|
|
462
|
+
Looks up ``potential_name`` in the full potential database and returns its
|
|
463
|
+
row as a :class:`pandas.Series` with absolute file paths already inserted
|
|
464
|
+
in the ``"Config"`` field.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
potential_name (str): Exact name of the potential (e.g.
|
|
468
|
+
``"2009--Mendelev-M-I--Al-Mg--LAMMPS--ipr1"``).
|
|
469
|
+
resource_path (str, optional): Path to the ``iprpy`` resource directory.
|
|
470
|
+
If ``None``, :func:`get_resource_path_from_conda` is called.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
pandas.Series: Single row from the potential database with absolute
|
|
474
|
+
paths in the ``"Config"`` field.
|
|
475
|
+
"""
|
|
352
476
|
if resource_path is None:
|
|
353
477
|
resource_path = get_resource_path_from_conda()
|
|
354
478
|
df = LammpsPotentialFile(resource_path=resource_path).list()
|
|
@@ -360,6 +484,28 @@ def get_potential_by_name(potential_name: str, resource_path: Optional[str] = No
|
|
|
360
484
|
def validate_potential_dataframe(
|
|
361
485
|
potential_dataframe: pandas.DataFrame,
|
|
362
486
|
) -> Union[pandas.DataFrame, pandas.Series]:
|
|
487
|
+
"""
|
|
488
|
+
Ensure a potential selection contains exactly one potential.
|
|
489
|
+
|
|
490
|
+
Accepts either a :class:`pandas.Series` (already a single row) or a
|
|
491
|
+
:class:`pandas.DataFrame` and validates that it contains exactly one row
|
|
492
|
+
before returning the row as a :class:`pandas.Series`.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
potential_dataframe (pandas.DataFrame or pandas.Series): Potential
|
|
496
|
+
selection as returned by
|
|
497
|
+
:meth:`~lammpsparser.potential.PotentialAbstract.find` or
|
|
498
|
+
:func:`get_potential_dataframe`.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
pandas.Series: Single-row representation of the chosen potential.
|
|
502
|
+
|
|
503
|
+
Raises:
|
|
504
|
+
ValueError: If ``potential_dataframe`` is an empty DataFrame or
|
|
505
|
+
contains more than one row.
|
|
506
|
+
TypeError: If ``potential_dataframe`` is neither a DataFrame nor a
|
|
507
|
+
Series.
|
|
508
|
+
"""
|
|
363
509
|
if isinstance(potential_dataframe, pandas.Series):
|
|
364
510
|
return potential_dataframe
|
|
365
511
|
elif isinstance(potential_dataframe, pandas.DataFrame):
|
|
@@ -182,9 +182,11 @@ class UnfoldingPrism(PrismBase):
|
|
|
182
182
|
return tuple([x for x in np.dot(position, self.R)])
|
|
183
183
|
|
|
184
184
|
def f2qdec(self, f: float) -> dec.Decimal:
|
|
185
|
+
"""Round ``f`` down to the Cartesian precision of the prism."""
|
|
185
186
|
return dec.Decimal(str(f)).quantize(self.car_prec, dec.ROUND_DOWN)
|
|
186
187
|
|
|
187
188
|
def f2s(self, f: float) -> str:
|
|
189
|
+
"""Convert ``f`` to a string rounded to the Cartesian precision of the prism."""
|
|
188
190
|
return str(dec.Decimal(str(f)).quantize(self.car_prec, dec.ROUND_HALF_EVEN))
|
|
189
191
|
|
|
190
192
|
def get_lammps_prism_str(self) -> Tuple[str, ...]:
|
|
@@ -195,9 +197,21 @@ class UnfoldingPrism(PrismBase):
|
|
|
195
197
|
|
|
196
198
|
class LammpsStructure:
|
|
197
199
|
"""
|
|
200
|
+
Generate LAMMPS data file content from an ASE :class:`~ase.atoms.Atoms` structure.
|
|
201
|
+
|
|
202
|
+
After construction, set :attr:`el_eam_lst` to the ordered list of element
|
|
203
|
+
symbols used by the interatomic potential and assign an
|
|
204
|
+
:class:`~ase.atoms.Atoms` object to :attr:`structure`. The assignment
|
|
205
|
+
triggers the serialisation of the structure into LAMMPS data format. Call
|
|
206
|
+
:meth:`write_file` to persist the result.
|
|
207
|
+
|
|
208
|
+
Supported ``atom_style`` modes are ``"atomic"`` (default) and ``"charge"``.
|
|
198
209
|
|
|
199
210
|
Args:
|
|
200
|
-
|
|
211
|
+
bond_dict (dict, optional): Bond topology dictionary (used by derived
|
|
212
|
+
classes for ``"bond"`` and ``"full"`` atom styles).
|
|
213
|
+
units (str): LAMMPS unit system (default: ``"metal"``).
|
|
214
|
+
atom_type (str): LAMMPS ``atom_style`` (default: ``"atomic"``).
|
|
201
215
|
"""
|
|
202
216
|
|
|
203
217
|
def __init__(
|
|
@@ -228,22 +242,20 @@ class LammpsStructure:
|
|
|
228
242
|
|
|
229
243
|
@property
|
|
230
244
|
def structure(self) -> Optional[Atoms]:
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
Returns:
|
|
234
|
-
|
|
235
|
-
"""
|
|
245
|
+
"""The ASE :class:`~ase.atoms.Atoms` object associated with this instance."""
|
|
236
246
|
return self._structure
|
|
237
247
|
|
|
238
248
|
@structure.setter
|
|
239
249
|
def structure(self, structure: Atoms):
|
|
240
250
|
"""
|
|
251
|
+
Set the atomic structure and serialise it to the internal LAMMPS data string.
|
|
241
252
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
Returns:
|
|
253
|
+
The setter immediately builds the complete LAMMPS data file content
|
|
254
|
+
(header, cell, masses, atom positions, and optionally velocities) and
|
|
255
|
+
stores it in ``_string_input``.
|
|
246
256
|
|
|
257
|
+
Args:
|
|
258
|
+
structure (ase.atoms.Atoms): The structure to serialise.
|
|
247
259
|
"""
|
|
248
260
|
self._structure = structure
|
|
249
261
|
if self._atom_type == "charge":
|
|
@@ -278,27 +290,38 @@ class LammpsStructure:
|
|
|
278
290
|
|
|
279
291
|
@property
|
|
280
292
|
def el_eam_lst(self) -> List[str]:
|
|
281
|
-
"""
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
|
|
285
|
-
"""
|
|
293
|
+
"""Ordered list of element symbols as defined in the LAMMPS potential file."""
|
|
286
294
|
return self._el_eam_lst
|
|
287
295
|
|
|
288
296
|
@el_eam_lst.setter
|
|
289
297
|
def el_eam_lst(self, el_eam_lst: List[str]):
|
|
290
298
|
"""
|
|
299
|
+
Set the ordered element list used to assign LAMMPS integer type IDs.
|
|
291
300
|
|
|
292
301
|
Args:
|
|
293
|
-
el_eam_lst:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
302
|
+
el_eam_lst (list[str]): Chemical symbols in the same order as they
|
|
303
|
+
appear in the ``pair_coeff`` or ``pair_style`` commands of the
|
|
304
|
+
LAMMPS input script.
|
|
297
305
|
"""
|
|
298
306
|
self._el_eam_lst = el_eam_lst
|
|
299
307
|
|
|
300
308
|
@staticmethod
|
|
301
309
|
def get_lammps_id_dict(el_eam_lst: List[str]) -> Dict[str, int]:
|
|
310
|
+
"""
|
|
311
|
+
Build a mapping from element symbol to LAMMPS integer type ID (1-based).
|
|
312
|
+
|
|
313
|
+
The order of ``el_eam_lst`` determines the mapping, which must match
|
|
314
|
+
the order of species in the LAMMPS potential definition.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
el_eam_lst (list[str]): Ordered list of element symbols.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
dict: Mapping ``{symbol: lammps_type_id}`` with IDs starting at 1.
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
ValueError: If ``el_eam_lst`` is empty.
|
|
324
|
+
"""
|
|
302
325
|
if len(el_eam_lst) == 0:
|
|
303
326
|
raise ValueError("el_eam_list is empty. Can not determine order of species")
|
|
304
327
|
return {el: idx + 1 for idx, el in enumerate(el_eam_lst)}
|
|
@@ -313,6 +336,27 @@ class LammpsStructure:
|
|
|
313
336
|
nbond_types: Optional[int] = None,
|
|
314
337
|
nangle_types: Optional[int] = None,
|
|
315
338
|
) -> str:
|
|
339
|
+
"""
|
|
340
|
+
Generate the header section of a LAMMPS data file.
|
|
341
|
+
|
|
342
|
+
Produces the counts block (number of atoms, atom types, bonds, angles)
|
|
343
|
+
followed by the cell dimensions and the ``Masses`` section.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
structure (ase.atoms.Atoms): Structure whose atom count and species
|
|
347
|
+
masses are written.
|
|
348
|
+
cell_dimensions (str): Pre-formatted string with ``xlo``/``xhi``/…
|
|
349
|
+
lines as returned by :meth:`simulation_cell`.
|
|
350
|
+
species_lammps_id_dict (dict): Mapping ``{symbol: lammps_type_id}``
|
|
351
|
+
as returned by :meth:`get_lammps_id_dict`.
|
|
352
|
+
nbonds (int, optional): Total number of bonds (omitted when ``None``).
|
|
353
|
+
nangles (int, optional): Total number of angles (omitted when ``None``).
|
|
354
|
+
nbond_types (int, optional): Number of distinct bond types.
|
|
355
|
+
nangle_types (int, optional): Number of distinct angle types.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
str: Header string ready to be prepended to the data file.
|
|
359
|
+
"""
|
|
316
360
|
atomtypes = (
|
|
317
361
|
"Start File for LAMMPS \n"
|
|
318
362
|
+ "{0:d} atoms".format(len(structure))
|
|
@@ -338,9 +382,17 @@ class LammpsStructure:
|
|
|
338
382
|
|
|
339
383
|
def simulation_cell(self) -> str:
|
|
340
384
|
"""
|
|
385
|
+
Format the simulation cell dimensions as a LAMMPS data file box block.
|
|
386
|
+
|
|
387
|
+
Builds an :class:`UnfoldingPrism` from the current structure's cell and
|
|
388
|
+
writes the ``xlo xhi``, ``ylo yhi``, ``zlo zhi`` lines (and ``xy xz yz``
|
|
389
|
+
for skewed cells) in the high-precision format required by LAMMPS.
|
|
341
390
|
|
|
342
391
|
Returns:
|
|
392
|
+
str: Multi-line string with the box bounds block.
|
|
343
393
|
|
|
394
|
+
Raises:
|
|
395
|
+
ValueError: If no structure has been assigned.
|
|
344
396
|
"""
|
|
345
397
|
if self._structure is None:
|
|
346
398
|
raise ValueError("Structure not set")
|
|
@@ -361,10 +413,17 @@ class LammpsStructure:
|
|
|
361
413
|
|
|
362
414
|
def structure_atomic(self) -> str:
|
|
363
415
|
"""
|
|
364
|
-
|
|
416
|
+
Serialise the structure for LAMMPS ``atom_style atomic``.
|
|
417
|
+
|
|
418
|
+
Writes the header followed by an ``Atoms`` block where each line has
|
|
419
|
+
the format ``atom-ID atom-type x y z``. Positions are rotated to the
|
|
420
|
+
LAMMPS upper-triangular cell frame.
|
|
365
421
|
|
|
366
422
|
Returns:
|
|
423
|
+
str: Complete LAMMPS data file content (header + atoms).
|
|
367
424
|
|
|
425
|
+
Raises:
|
|
426
|
+
ValueError: If no structure or element list has been set.
|
|
368
427
|
"""
|
|
369
428
|
if self._structure is None:
|
|
370
429
|
raise ValueError("Structure not set")
|
|
@@ -395,12 +454,17 @@ class LammpsStructure:
|
|
|
395
454
|
|
|
396
455
|
def structure_charge(self):
|
|
397
456
|
"""
|
|
398
|
-
|
|
457
|
+
Serialise the structure for LAMMPS ``atom_style charge``.
|
|
399
458
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
459
|
+
Writes the header followed by an ``Atoms`` block where each line has
|
|
460
|
+
the format ``atom-ID atom-type charge x y z``. The per-atom charges
|
|
461
|
+
are read from :meth:`ase.atoms.Atoms.get_initial_charges`. By
|
|
462
|
+
convention, LAMMPS atom type numbers are assigned alphabetically for
|
|
463
|
+
each chemical species. Positions are rotated to the LAMMPS
|
|
464
|
+
upper-triangular cell frame.
|
|
403
465
|
|
|
466
|
+
Returns:
|
|
467
|
+
str: Complete LAMMPS data file content (header + atoms).
|
|
404
468
|
"""
|
|
405
469
|
species_lammps_id_dict = self.get_lammps_id_dict(self.el_eam_lst)
|
|
406
470
|
atoms = "Atoms\n\n"
|
|
@@ -509,6 +573,29 @@ def write_lammps_datafile(
|
|
|
509
573
|
working_directory: Optional[str] = None,
|
|
510
574
|
atom_type: str = "atomic",
|
|
511
575
|
) -> None:
|
|
576
|
+
"""
|
|
577
|
+
Write an ASE structure to a LAMMPS data file.
|
|
578
|
+
|
|
579
|
+
Convenience wrapper around :class:`LammpsStructure` that handles the full
|
|
580
|
+
workflow: build the data-file string, optionally combine with a
|
|
581
|
+
``working_directory``, and write to disk. This is the file loaded by
|
|
582
|
+
LAMMPS via ``read_data`` in the input script.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
structure (ase.atoms.Atoms): Atomic structure to write.
|
|
586
|
+
potential_elements (numpy.ndarray or list[str]): Ordered list of
|
|
587
|
+
element symbols matching the potential definition. Determines the
|
|
588
|
+
LAMMPS integer type IDs.
|
|
589
|
+
bond_dict (dict, optional): Bond topology used for ``"bond"`` and
|
|
590
|
+
``"full"`` atom styles (pass ``None`` for ``"atomic"`` or
|
|
591
|
+
``"charge"``).
|
|
592
|
+
units (str): LAMMPS unit system (default: ``"metal"``).
|
|
593
|
+
file_name (str): Output filename (default: ``"lammps.data"``).
|
|
594
|
+
working_directory (str, optional): If given, the file is written to
|
|
595
|
+
``<working_directory>/<file_name>``.
|
|
596
|
+
atom_type (str): LAMMPS ``atom_style`` keyword; one of ``"atomic"``
|
|
597
|
+
(default), ``"charge"``, ``"bond"``, or ``"full"``.
|
|
598
|
+
"""
|
|
512
599
|
lammps_str = LammpsStructure(bond_dict=bond_dict, units=units, atom_type=atom_type)
|
|
513
600
|
lammps_str.el_eam_lst = cast(List[str], list(potential_elements))
|
|
514
601
|
lammps_str.structure = structure
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional, Union
|
|
3
|
-
|
|
4
|
-
import numpy as np
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass
|
|
8
|
-
class CalcMDInput:
|
|
9
|
-
temperature: Optional[Union[float, list]] = None
|
|
10
|
-
pressure: Optional[Union[float, list, np.ndarray]] = None
|
|
11
|
-
n_ionic_steps: int = 1
|
|
12
|
-
time_step: float = 1.0
|
|
13
|
-
n_print: int = 100
|
|
14
|
-
temperature_damping_timescale: float = 100.0
|
|
15
|
-
pressure_damping_timescale: float = 1000.0
|
|
16
|
-
seed: int = 80996
|
|
17
|
-
tloop: Optional[int] = None
|
|
18
|
-
initial_temperature: Optional[float] = None
|
|
19
|
-
langevin: bool = False
|
|
20
|
-
delta_temp: Optional[float] = None
|
|
21
|
-
delta_press: Optional[float] = None
|
|
22
|
-
rotation_matrix: Optional[Union[list, np.ndarray]] = None
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class CalcMinimizeInput:
|
|
27
|
-
ionic_energy_tolerance: float = 0.0
|
|
28
|
-
ionic_force_tolerance: float = 1e-4
|
|
29
|
-
max_iter: int = 100000
|
|
30
|
-
pressure: Optional[Union[float, list, np.ndarray]] = None
|
|
31
|
-
n_print: int = 1
|
|
32
|
-
style: str = "cg"
|
|
33
|
-
rotation_matrix: Optional[Union[list, np.ndarray]] = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|