lammpsparser 0.0.4__tar.gz → 0.0.5__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.
Files changed (20) hide show
  1. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/PKG-INFO +4 -4
  2. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/pyproject.toml +4 -4
  3. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/_version.py +2 -2
  4. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/compatibility/calculate.py +47 -2
  5. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/compatibility/constraints.py +52 -0
  6. lammpsparser-0.0.5/src/lammpsparser/compatibility/data.py +87 -0
  7. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/compatibility/file.py +69 -6
  8. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/compatibility/structure.py +39 -18
  9. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/output.py +114 -2
  10. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/output_raw.py +91 -6
  11. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/potential.py +149 -3
  12. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/structure.py +111 -24
  13. lammpsparser-0.0.4/src/lammpsparser/compatibility/data.py +0 -33
  14. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/.gitignore +0 -0
  15. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/LICENSE +0 -0
  16. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/README.md +0 -0
  17. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/__init__.py +0 -0
  18. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/compatibility/__init__.py +0 -0
  19. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/compatibility/output.py +0 -0
  20. {lammpsparser-0.0.4 → lammpsparser-0.0.5}/src/lammpsparser/units.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lammpsparser
3
- Version: 0.0.4
3
+ Version: 0.0.5
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
@@ -50,11 +50,11 @@ Classifier: Programming Language :: Python :: 3.14
50
50
  Classifier: Topic :: Scientific/Engineering :: Physics
51
51
  Requires-Python: <3.15,>=3.9
52
52
  Requires-Dist: ase<=3.28.0,>=3.22.1
53
- Requires-Dist: numpy<=2.4.3,>=1.26.0
54
- Requires-Dist: pandas<=3.0.2,>=2.0.3
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.42,>=0.0.30; extra == 'compatibility'
57
+ Requires-Dist: structuretoolkit<=0.0.44,>=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.29.0",
8
+ "hatchling==1.30.1",
9
9
  "hatch-vcs==0.5.0",
10
10
  ]
11
11
  build-backend = "hatchling.build"
@@ -35,8 +35,8 @@ classifiers = [
35
35
  ]
36
36
  dependencies = [
37
37
  "ase>=3.22.1,<=3.28.0",
38
- "numpy>=1.26.0,<=2.4.3",
39
- "pandas>=2.0.3,<=3.0.2",
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.42"]
51
+ compatibility = ["structuretoolkit>=0.0.30,<=0.0.44"]
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.4'
22
- __version_tuple__ = version_tuple = (0, 0, 4)
21
+ __version__ = version = '0.0.5'
22
+ __version_tuple__ = version_tuple = (0, 0, 5)
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
- pressure:
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
- lmp_str_lst += [
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
- lmp_str_lst += [
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
- lmp_str_lst += [
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
- Args:
36
- structure:
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
- Returns:
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
- Write routine to create atom structure static file for atom_type='full' that can be loaded by LAMMPS
167
+ Serialise the structure for LAMMPS ``atom_style full``.
163
168
 
164
- Returns:
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
- prec: minimum distance between any two clusters (if smaller considered to be single cluster)
320
- num_neighbors:
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
- general purpose routine to extract static from a lammps dump file
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
- general purpose routine to extract static from a lammps log file
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
- Docstring for _parse_dump_from_text
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): The path to the lammps dump file.
147
+ file_name (str): Path to the LAMMPS text dump file (typically ``dump.out``).
93
148
 
94
149
  Returns:
95
- Dict: Parsed dump data.
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
- Docstring for _parse_lammps_log
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): The path to the lammps log file.
297
+ file_name (str): Path to the LAMMPS log file (typically ``log.lammps``).
217
298
 
218
299
  Returns:
219
- pd.DataFrame: Dataframe containing the parsed log data.
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
- resource_path (str):
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
- input_file_name:
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
- Args:
243
- structure:
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
- Returns:
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
- Write routine to create atom structure static file that can be loaded by LAMMPS
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
- Create atom structure including the atom charges.
457
+ Serialise the structure for LAMMPS ``atom_style charge``.
399
458
 
400
- By convention the LAMMPS atom type numbers are chose alphabetically for the chemical species.
401
-
402
- Returns: LAMMPS readable structure.
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