lammpsparser 0.0.10__tar.gz → 0.1.1__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.10 → lammpsparser-0.1.1}/PKG-INFO +7 -11
  2. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/pyproject.toml +7 -10
  3. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/_version.py +2 -2
  4. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/compatibility/file.py +19 -2
  5. lammpsparser-0.1.1/src/lammpsparser/compatibility/neighbors.py +232 -0
  6. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/compatibility/structure.py +3 -8
  7. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/structure.py +2 -0
  8. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/.gitignore +0 -0
  9. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/LICENSE +0 -0
  10. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/README.md +0 -0
  11. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/__init__.py +0 -0
  12. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/compatibility/__init__.py +0 -0
  13. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/compatibility/calculate.py +0 -0
  14. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/compatibility/constraints.py +0 -0
  15. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/compatibility/data.py +0 -0
  16. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/compatibility/output.py +0 -0
  17. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/output.py +0 -0
  18. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/output_raw.py +0 -0
  19. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/potential.py +0 -0
  20. {lammpsparser-0.0.10 → lammpsparser-0.1.1}/src/lammpsparser/units.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lammpsparser
3
- Version: 0.0.10
3
+ Version: 0.1.1
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
@@ -41,22 +41,18 @@ Classifier: Development Status :: 5 - Production/Stable
41
41
  Classifier: Intended Audience :: Science/Research
42
42
  Classifier: License :: OSI Approved :: BSD License
43
43
  Classifier: Operating System :: OS Independent
44
- Classifier: Programming Language :: Python :: 3.9
45
- Classifier: Programming Language :: Python :: 3.10
46
44
  Classifier: Programming Language :: Python :: 3.11
47
45
  Classifier: Programming Language :: Python :: 3.12
48
46
  Classifier: Programming Language :: Python :: 3.13
49
47
  Classifier: Programming Language :: Python :: 3.14
50
48
  Classifier: Topic :: Scientific/Engineering :: Physics
51
- Requires-Python: <3.15,>=3.9
52
- Requires-Dist: ase<=3.29.0,>=3.22.1
53
- Requires-Dist: numpy<=2.5.3,>=1.26.0
54
- Requires-Dist: pandas<=3.0.6,>=2.0.3
55
- Requires-Dist: scipy<=1.18.1,>=1.11.1
56
- Provides-Extra: compatibility
57
- Requires-Dist: structuretoolkit<=0.0.47,>=0.0.30; extra == 'compatibility'
49
+ Requires-Python: <3.15,>=3.11
50
+ Requires-Dist: ase==3.29.0
51
+ Requires-Dist: numpy<2.6.0,>=1.26.0
52
+ Requires-Dist: pandas<3.2.0,>=2.0.3
53
+ Requires-Dist: scipy<1.19.0,>=1.11.1
58
54
  Provides-Extra: h5md
59
- Requires-Dist: h5py<=3.16.0,>=3.9.0; extra == 'h5md'
55
+ Requires-Dist: h5py<3.17.0,>=3.9.0; extra == 'h5md'
60
56
  Description-Content-Type: text/markdown
61
57
 
62
58
  # lammpsparser
@@ -5,7 +5,7 @@ requires = [
5
5
  "h5py",
6
6
  "pandas",
7
7
  "scipy",
8
- "hatchling==1.32.4",
8
+ "hatchling==1.33.0",
9
9
  "hatch-vcs==0.5.0",
10
10
  ]
11
11
  build-backend = "hatchling.build"
@@ -19,25 +19,23 @@ authors = [
19
19
  readme = "README.md"
20
20
  license = { file = "LICENSE" }
21
21
  keywords = ["pyiron"]
22
- requires-python = ">=3.9, <3.15"
22
+ requires-python = ">=3.11, <3.15"
23
23
  classifiers = [
24
24
  "Development Status :: 5 - Production/Stable",
25
25
  "Topic :: Scientific/Engineering :: Physics",
26
26
  "License :: OSI Approved :: BSD License",
27
27
  "Intended Audience :: Science/Research",
28
28
  "Operating System :: OS Independent",
29
- "Programming Language :: Python :: 3.9",
30
- "Programming Language :: Python :: 3.10",
31
29
  "Programming Language :: Python :: 3.11",
32
30
  "Programming Language :: Python :: 3.12",
33
31
  "Programming Language :: Python :: 3.13",
34
32
  "Programming Language :: Python :: 3.14",
35
33
  ]
36
34
  dependencies = [
37
- "ase>=3.22.1,<=3.29.0",
38
- "numpy>=1.26.0,<=2.5.3",
39
- "pandas>=2.0.3,<=3.0.6",
40
- "scipy>=1.11.1,<=1.18.1",
35
+ "ase==3.29.0",
36
+ "numpy>=1.26.0,<2.6.0",
37
+ "pandas>=2.0.3,<3.2.0",
38
+ "scipy>=1.11.1,<1.19.0",
41
39
  ]
42
40
  dynamic = ["version"]
43
41
 
@@ -47,8 +45,7 @@ Documentation = "https://github.com/pyiron/lammpsparser"
47
45
  Repository = "https://github.com/pyiron/lammpsparser"
48
46
 
49
47
  [project.optional-dependencies]
50
- h5md = ["h5py>=3.9.0,<=3.16.0"]
51
- compatibility = ["structuretoolkit>=0.0.30,<=0.0.47"]
48
+ h5md = ["h5py>=3.9.0,<3.17.0"]
52
49
 
53
50
  [tool.hatch.build]
54
51
  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.10'
22
- __version_tuple__ = version_tuple = (0, 0, 10)
21
+ __version__ = version = '0.1.1'
22
+ __version_tuple__ = version_tuple = (0, 1, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -36,6 +36,9 @@ def lammps_file_interface_function(
36
36
  read_restart_file: bool = False,
37
37
  restart_file: str = "restart.out",
38
38
  dump_final_structure: bool = False,
39
+ n_print_dump: int | None = None,
40
+ n_print_thermo: int | None = None,
41
+ force_skewed: bool = False,
39
42
  ):
40
43
  """
41
44
  A single function to execute a LAMMPS calculation based on the LAMMPS job interface implemented in lammpsparser
@@ -90,6 +93,13 @@ def lammps_file_interface_function(
90
93
  restart_file (str): file name of the LAMMPS restart file to copy
91
94
  dump_final_structure (bool): in "md" mode, append a ``write_dump`` command after the ``run`` command to capture
92
95
  the final structure in ``dump.out`` if last step is not a regular dump step. Disabled by default.
96
+ n_print_dump (int, optional): Override the dump-file write interval (steps between successive
97
+ ``dump`` writes), independently of ``calc_kwargs["n_print"]``. Defaults to ``None``, which
98
+ falls back to ``calc_kwargs.get("n_print", 1)`` (the pre-existing behavior).
99
+ n_print_thermo (int, optional): Override the thermodynamic print interval (steps between
100
+ successive ``thermo`` output lines), independently of ``calc_kwargs["n_print"]``. Defaults
101
+ to ``None``, which leaves ``calc_kwargs["n_print"]`` untouched (the pre-existing behavior).
102
+ Has no effect in ``"static"`` mode, since that mode only ever performs a single step.
93
103
  executable_version (str): LAMMPS version to for the execution
94
104
  executable_path (str): path to the LAMMPS executable
95
105
  input_control_file (str|list|dict): Option to modify the LAMMPS input file directly
@@ -115,6 +125,12 @@ def lammps_file_interface_function(
115
125
  else:
116
126
  calc_kwargs = calc_kwargs.copy()
117
127
 
128
+ dump_interval = (
129
+ n_print_dump if n_print_dump is not None else calc_kwargs.get("n_print", 1)
130
+ )
131
+ if n_print_thermo is not None:
132
+ calc_kwargs["n_print"] = n_print_thermo
133
+
118
134
  if lmp_command is None:
119
135
  lmp_command = (
120
136
  os.getenv("ASE_LAMMPSRUN_COMMAND", "mpiexec -n 1 --oversubscribe lmp_mpi")
@@ -150,7 +166,7 @@ def lammps_file_interface_function(
150
166
  dump_format = '"%d %d %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g"'
151
167
 
152
168
  lmp_str_lst += potential_lst
153
- lmp_str_lst += ["variable dumptime equal {} ".format(calc_kwargs.get("n_print", 1))]
169
+ lmp_str_lst += [f"variable dumptime equal {dump_interval} "]
154
170
  lmp_str_lst += [
155
171
  "dump 1 all custom ${dumptime} dump.out " + dump_fields,
156
172
  "dump_modify 1 sort id format line " + dump_format,
@@ -195,7 +211,7 @@ def lammps_file_interface_function(
195
211
  if read_restart_file:
196
212
  lmp_str_lst += ["reset_timestep 0"]
197
213
  lmp_str_lst += [f"run {n_ionic_steps} "]
198
- last_step_is_regular_dump = n_ionic_steps % calc_kwargs.get("n_print", 1) == 0
214
+ last_step_is_regular_dump = n_ionic_steps % dump_interval == 0
199
215
  if dump_final_structure and not last_step_is_regular_dump:
200
216
  lmp_str_lst += [
201
217
  "write_dump all custom dump.out "
@@ -245,6 +261,7 @@ def lammps_file_interface_function(
245
261
  file_name="lammps.data",
246
262
  working_directory=working_directory,
247
263
  atom_type=atom_type,
264
+ force_skewed=force_skewed,
248
265
  )
249
266
 
250
267
  shell = subprocess.check_output(
@@ -0,0 +1,232 @@
1
+ """
2
+ Minimal neighbour analysis for the compatibility layer.
3
+
4
+ This is a reduced port of :mod:`structuretoolkit.analyse.neighbors` (originally by
5
+ Joerg Neugebauer and Sam Waseda, Max-Planck-Institut für Eisenforschung GmbH,
6
+ distributed under the "New BSD License"). Only the functionality required to
7
+ derive bonds and angles for the LAMMPS ``bond`` and ``full`` atom styles is
8
+ included.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import warnings
14
+
15
+ import numpy as np
16
+ from ase.atoms import Atoms
17
+ from scipy.spatial import cKDTree
18
+
19
+
20
+ def select_index(structure: Atoms, element: str) -> np.ndarray:
21
+ """
22
+ Select the indices of atoms in the structure that belong to a specific element.
23
+
24
+ Args:
25
+ structure (ase.atoms.Atoms): Atomic structure.
26
+ element (str): Chemical symbol.
27
+
28
+ Returns:
29
+ numpy.ndarray: Indices of the atoms of the given element.
30
+ """
31
+ return structure.symbols.indices()[element]
32
+
33
+
34
+ def get_vertical_length(structure: Atoms) -> np.ndarray:
35
+ """
36
+ Length of each cell vector projected on the normal of the plane spanned by the
37
+ other two cell vectors.
38
+
39
+ Args:
40
+ structure (ase.atoms.Atoms): Atomic structure.
41
+
42
+ Returns:
43
+ numpy.ndarray: Projected cell lengths.
44
+ """
45
+ return np.linalg.det(structure.cell) / np.linalg.norm(
46
+ np.cross(
47
+ np.roll(structure.cell, -1, axis=0), np.roll(structure.cell, 1, axis=0)
48
+ ),
49
+ axis=-1,
50
+ )
51
+
52
+
53
+ def get_extended_positions(
54
+ structure: Atoms, width: float
55
+ ) -> tuple[np.ndarray, np.ndarray]:
56
+ """
57
+ Get the positions of all atoms in the supercell plus all periodic images within
58
+ ``width`` of the cell boundary.
59
+
60
+ Args:
61
+ structure (ase.atoms.Atoms): Atomic structure.
62
+ width (float): Width of the buffer layer on every periodic side of the box.
63
+
64
+ Returns:
65
+ tuple: Extended positions and the indices of the corresponding atoms in the
66
+ original structure.
67
+ """
68
+ if width < 0:
69
+ raise ValueError("Invalid width")
70
+ positions = structure.positions
71
+ if width == 0:
72
+ return positions, np.arange(len(positions))
73
+ scaled_width = width / get_vertical_length(structure=structure)
74
+ rep = 2 * np.ceil(scaled_width).astype(int) * structure.pbc + 1
75
+ rep = [np.arange(r) - int(r / 2) for r in rep]
76
+ meshgrid = np.stack(np.meshgrid(rep[0], rep[1], rep[2]), axis=-1).reshape(-1, 3)
77
+ v_repeated = np.einsum("ni,ij->nj", meshgrid, structure.cell)
78
+ v_repeated = v_repeated[:, np.newaxis, :] + positions[np.newaxis, :, :]
79
+ v_repeated = v_repeated.reshape(-1, 3)
80
+ indices = np.tile(np.arange(len(positions)), len(meshgrid))
81
+ dist = v_repeated - np.sum(structure.cell * 0.5, axis=0)
82
+ dist = (
83
+ np.absolute(np.einsum("ni,ij->nj", dist + 1e-8, np.linalg.inv(structure.cell)))
84
+ - 0.5
85
+ )
86
+ check_dist = np.all(dist - scaled_width < 0, axis=-1)
87
+ return v_repeated[check_dist], indices[check_dist] % len(positions)
88
+
89
+
90
+ class Neighbors:
91
+ """
92
+ Neighbourhood information of each atom in a structure.
93
+
94
+ Attributes:
95
+ distances (numpy.ndarray): Distances to the neighbours of each atom. Missing
96
+ entries (beyond the cutoff radius) are filled with ``numpy.inf``.
97
+ indices (numpy.ndarray): Indices of the neighbours of each atom. Missing
98
+ entries are filled with ``numpy.iinfo(numpy.int32).max``.
99
+ """
100
+
101
+ def __init__(self, structure: Atoms, distances: np.ndarray, indices: np.ndarray):
102
+ self._structure = structure
103
+ self.distances = distances
104
+ self.indices = indices
105
+
106
+ def get_bonds(
107
+ self,
108
+ radius: float = np.inf,
109
+ max_shells: int | None = None,
110
+ prec: float = 0.1,
111
+ ) -> list[dict[str, list[list[int]]]]:
112
+ """
113
+ Group the neighbours of each atom into shells and chemical species.
114
+
115
+ Args:
116
+ radius (float): Maximum distance for a bond to be considered.
117
+ max_shells (int, optional): Maximum number of shells per species.
118
+ prec (float): Minimum distance gap between two shells; shells closer
119
+ than ``prec`` are merged into one.
120
+
121
+ Returns:
122
+ list: For each atom a dictionary mapping the chemical symbol of the
123
+ neighbours to a list of shells, each shell being a list of atom indices.
124
+ """
125
+ el_list = self._structure.get_chemical_symbols()
126
+ ind_shell = []
127
+ for d, i in zip(self.distances, self.indices):
128
+ cond = d < radius
129
+ dist_vec, ind_vec = d[cond], i[cond]
130
+ ind_where = np.where(np.diff(dist_vec) > prec)[0] + 1
131
+ ia_shells_dict: dict[str, list[list[int]]] = {}
132
+ for i_shell_list in [np.sort(g) for g in np.split(ind_vec, ind_where)]:
133
+ ia_shell_dict: dict[str, list[int]] = {}
134
+ for i_s in i_shell_list:
135
+ ia_shell_dict.setdefault(el_list[i_s], []).append(i_s)
136
+ for el, ia_lst in ia_shell_dict.items():
137
+ shells = ia_shells_dict.setdefault(el, [])
138
+ if max_shells is not None and len(shells) + 1 > max_shells:
139
+ continue
140
+ shells.append(ia_lst)
141
+ ind_shell.append(ia_shells_dict)
142
+ return ind_shell
143
+
144
+
145
+ def _estimate_width(
146
+ structure: Atoms,
147
+ num_neighbors: int,
148
+ cutoff_radius: float = np.inf,
149
+ width_buffer: float = 1.2,
150
+ ) -> float:
151
+ """
152
+ Estimate the width of the periodic buffer layer required to find all neighbours.
153
+
154
+ Args:
155
+ structure (ase.atoms.Atoms): Atomic structure.
156
+ num_neighbors (int): Number of neighbours.
157
+ cutoff_radius (float): Cutoff radius.
158
+ width_buffer (float): Relative safety margin.
159
+
160
+ Returns:
161
+ float: Width of the buffer layer.
162
+ """
163
+ if not any(structure.pbc):
164
+ return 0
165
+ elif cutoff_radius != np.inf:
166
+ return cutoff_radius
167
+ width = np.prod(np.linalg.norm(structure.cell, axis=-1))
168
+ width *= 4 / 3 * np.pi * np.max([num_neighbors, 8]) / len(structure)
169
+ return width_buffer * width ** (1 / 3)
170
+
171
+
172
+ def get_neighbors(
173
+ structure: Atoms,
174
+ num_neighbors: int = 12,
175
+ cutoff_radius: float = np.inf,
176
+ width_buffer: float = 1.2,
177
+ ) -> Neighbors:
178
+ """
179
+ Get the neighbours of each atom in a structure, taking periodic boundary
180
+ conditions into account.
181
+
182
+ Args:
183
+ structure (ase.atoms.Atoms): Atomic structure.
184
+ num_neighbors (int): Maximum number of neighbours per atom.
185
+ cutoff_radius (float): Upper bound of the neighbour distance.
186
+ width_buffer (float): Relative safety margin of the periodic buffer layer.
187
+
188
+ Returns:
189
+ Neighbors: Neighbour distances and indices.
190
+ """
191
+ if num_neighbors <= 0:
192
+ raise ValueError("invalid number of neighbors")
193
+ if width_buffer < 0:
194
+ raise ValueError("width_buffer must be a positive float")
195
+ width = _estimate_width(
196
+ structure=structure,
197
+ num_neighbors=num_neighbors,
198
+ cutoff_radius=cutoff_radius,
199
+ width_buffer=width_buffer,
200
+ )
201
+ extended_positions, wrapped_indices = get_extended_positions(
202
+ structure=structure, width=width
203
+ )
204
+ if len(extended_positions) < num_neighbors + 1 and cutoff_radius == np.inf:
205
+ raise ValueError(
206
+ "num_neighbors too large - make width_buffer larger and/or make "
207
+ + "num_neighbors smaller"
208
+ )
209
+ distances, indices = cKDTree(extended_positions).query(
210
+ structure.positions,
211
+ k=num_neighbors + 1,
212
+ distance_upper_bound=cutoff_radius,
213
+ )
214
+ distances = np.asarray(distances).reshape(len(structure), num_neighbors + 1)
215
+ indices = np.asarray(indices).reshape(len(structure), num_neighbors + 1)
216
+ if cutoff_radius < np.inf and np.any(distances[:, -1] < np.inf):
217
+ warnings.warn(
218
+ "Number of neighbors found within the cutoff_radius is equal to "
219
+ + "num_neighbors. Increase num_neighbors to find all neighbors within "
220
+ + "cutoff_radius.",
221
+ stacklevel=2,
222
+ )
223
+ valid = distances < np.inf
224
+ indices[valid] = wrapped_indices[indices[valid]]
225
+ indices[~valid] = np.iinfo(np.int32).max
226
+ # The first column is the atom itself
227
+ max_column = np.sum(valid, axis=-1).max()
228
+ return Neighbors(
229
+ structure=structure,
230
+ distances=distances[:, 1:max_column],
231
+ indices=indices[:, 1:max_column],
232
+ )
@@ -5,9 +5,8 @@ from typing import cast
5
5
 
6
6
  import numpy as np
7
7
  from ase.atoms import Atoms
8
- from structuretoolkit.analyse import get_neighbors
9
- from structuretoolkit.common import select_index
10
8
 
9
+ from lammpsparser.compatibility.neighbors import get_neighbors, select_index
11
10
  from lammpsparser.structure import LammpsStructure
12
11
 
13
12
 
@@ -329,7 +328,7 @@ def get_bonds(
329
328
  """
330
329
  Identify bonds in a structure based on neighbour distances.
331
330
 
332
- Wraps :func:`structuretoolkit.analyse.get_neighbors` and its
331
+ Wraps :func:`lammpsparser.compatibility.neighbors.get_neighbors` and its
333
332
  ``get_bonds`` method to return a shell-based bond list for each atom.
334
333
 
335
334
  Args:
@@ -344,16 +343,12 @@ def get_bonds(
344
343
 
345
344
  Returns:
346
345
  list: Nested bond list as returned by
347
- :meth:`structuretoolkit.analyse.neighbors.get_bonds`.
346
+ :meth:`lammpsparser.compatibility.neighbors.Neighbors.get_bonds`.
348
347
  """
349
348
  neighbors = get_neighbors(
350
349
  structure=structure,
351
350
  cutoff_radius=radius,
352
351
  num_neighbors=num_neighbors,
353
- tolerance=2,
354
- id_list=None,
355
352
  width_buffer=1.2,
356
- mode="ragged",
357
- norm_order=2,
358
353
  )
359
354
  return neighbors.get_bonds(radius=radius, max_shells=max_shells, prec=prec)
@@ -554,6 +554,7 @@ def write_lammps_datafile(
554
554
  file_name: str = "lammps.data",
555
555
  working_directory: str | None = None,
556
556
  atom_type: str = "atomic",
557
+ force_skewed: bool = False,
557
558
  ) -> None:
558
559
  """
559
560
  Write an ASE structure to a LAMMPS data file.
@@ -579,6 +580,7 @@ def write_lammps_datafile(
579
580
  (default), ``"charge"``, ``"bond"``, or ``"full"``.
580
581
  """
581
582
  lammps_str = LammpsStructure(bond_dict=bond_dict, units=units, atom_type=atom_type)
583
+ lammps_str._force_skewed = force_skewed
582
584
  lammps_str.el_eam_lst = cast(list[str], list(potential_elements))
583
585
  lammps_str.structure = structure
584
586
  lammps_str.write_file(file_name=file_name, cwd=working_directory)
File without changes
File without changes
File without changes