lammpsparser 0.0.6__tar.gz → 0.0.8__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 (19) hide show
  1. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/PKG-INFO +2 -2
  2. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/pyproject.toml +2 -2
  3. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/_version.py +2 -2
  4. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/compatibility/calculate.py +21 -45
  5. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/compatibility/data.py +11 -10
  6. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/compatibility/file.py +43 -17
  7. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/compatibility/output.py +4 -1
  8. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/compatibility/structure.py +70 -65
  9. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/output.py +36 -38
  10. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/output_raw.py +21 -22
  11. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/potential.py +8 -9
  12. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/structure.py +50 -69
  13. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/units.py +10 -11
  14. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/.gitignore +0 -0
  15. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/LICENSE +0 -0
  16. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/README.md +0 -0
  17. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/__init__.py +0 -0
  18. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/compatibility/__init__.py +0 -0
  19. {lammpsparser-0.0.6 → lammpsparser-0.0.8}/src/lammpsparser/compatibility/constraints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lammpsparser
3
- Version: 0.0.6
3
+ Version: 0.0.8
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
@@ -51,7 +51,7 @@ Classifier: Topic :: Scientific/Engineering :: Physics
51
51
  Requires-Python: <3.15,>=3.9
52
52
  Requires-Dist: ase<=3.29.0,>=3.22.1
53
53
  Requires-Dist: numpy<=2.4.6,>=1.26.0
54
- Requires-Dist: pandas<=3.0.3,>=2.0.3
54
+ Requires-Dist: pandas<=3.0.5,>=2.0.3
55
55
  Requires-Dist: scipy<=1.17.1,>=1.11.1
56
56
  Provides-Extra: compatibility
57
57
  Requires-Dist: structuretoolkit<=0.0.45,>=0.0.30; extra == 'compatibility'
@@ -5,7 +5,7 @@ requires = [
5
5
  "h5py",
6
6
  "pandas",
7
7
  "scipy",
8
- "hatchling==1.30.1",
8
+ "hatchling==1.31.0",
9
9
  "hatch-vcs==0.5.0",
10
10
  ]
11
11
  build-backend = "hatchling.build"
@@ -36,7 +36,7 @@ classifiers = [
36
36
  dependencies = [
37
37
  "ase>=3.22.1,<=3.29.0",
38
38
  "numpy>=1.26.0,<=2.4.6",
39
- "pandas>=2.0.3,<=3.0.3",
39
+ "pandas>=2.0.3,<=3.0.5",
40
40
  "scipy>=1.11.1,<=1.17.1",
41
41
  ]
42
42
  dynamic = ["version"]
@@ -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.6'
22
- __version_tuple__ = version_tuple = (0, 0, 6)
21
+ __version__ = version = '0.0.8'
22
+ __version_tuple__ = version_tuple = (0, 0, 8)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -64,10 +64,10 @@ def calc_md(
64
64
  delta_press (float): Barostat timescale, but in your Lammps time units, whatever those
65
65
  are. (DEPRECATED.)
66
66
  job_name (str): Job name of the job to generate a unique random seed.
67
- rotation_matrix (numpy.ndarray): The rotation matrix from the pyiron to Lammps coordinate
67
+ rotation_matrix (numpy.ndarray): The rotation matrix from the ASE to Lammps coordinate
68
68
  frame.
69
69
  """
70
- if units not in LAMMPS_UNIT_CONVERSIONS.keys():
70
+ if units not in LAMMPS_UNIT_CONVERSIONS:
71
71
  raise NotImplementedError
72
72
  time_units = LAMMPS_UNIT_CONVERSIONS[units]["time"]
73
73
  temperature_units = LAMMPS_UNIT_CONVERSIONS[units]["temperature"]
@@ -103,7 +103,7 @@ def calc_md(
103
103
  raise ValueError(
104
104
  "At most two temperatures can be provided "
105
105
  "(for a linearly ramping target temperature), "
106
- "but got {}".format(len(temperature))
106
+ f"but got {len(temperature)}"
107
107
  )
108
108
  temperature *= temperature_units
109
109
 
@@ -128,35 +128,24 @@ def calc_md(
128
128
  )
129
129
 
130
130
  if np.isscalar(pressure):
131
- pressure_string = " iso {0} {0} {1}".format(
132
- pressure, pressure_damping_timescale
133
- )
131
+ pressure_string = f" iso {pressure} {pressure} {pressure_damping_timescale}"
134
132
  else:
135
133
  pressure_string = ""
136
134
  for ii, (coord, value) in enumerate(
137
135
  zip(["x", "y", "z", "xy", "xz", "yz"], pressure)
138
136
  ):
139
137
  if value is not None:
140
- pressure_string += " {0} {1} {1} {2}".format(
141
- coord, value, pressure_damping_timescale
138
+ pressure_string += (
139
+ f" {coord} {value} {value} {pressure_damping_timescale}"
142
140
  )
143
141
  # if ii > 2:
144
142
  # force_skewed = True
145
143
 
146
144
  if langevin: # NPT(Langevin)
147
145
  fix_ensemble_str = "fix ensemble all nph" + pressure_string
148
- thermo_str = "fix langevin all langevin {0} {1} {2} {3} zero yes".format(
149
- str(temperature[0]),
150
- str(temperature[1]),
151
- str(temperature_damping_timescale),
152
- str(seed),
153
- )
146
+ thermo_str = f"fix langevin all langevin {temperature[0]!s} {temperature[1]!s} {temperature_damping_timescale!s} {seed!s} zero yes"
154
147
  else: # NPT(Nose-Hoover)
155
- fix_ensemble_str = "fix ensemble all npt temp {0} {1} {2}".format(
156
- str(temperature[0]),
157
- str(temperature[1]),
158
- str(temperature_damping_timescale),
159
- )
148
+ fix_ensemble_str = f"fix ensemble all npt temp {temperature[0]!s} {temperature[1]!s} {temperature_damping_timescale!s}"
160
149
  fix_ensemble_str += pressure_string
161
150
  elif temperature is not None: # NVT
162
151
  if temperature.min() <= 0:
@@ -166,18 +155,9 @@ def calc_md(
166
155
 
167
156
  if langevin: # NVT(Langevin)
168
157
  fix_ensemble_str = "fix ensemble all nve"
169
- thermo_str = "fix langevin all langevin {0} {1} {2} {3} zero yes".format(
170
- str(temperature[0]),
171
- str(temperature[1]),
172
- str(temperature_damping_timescale),
173
- str(seed),
174
- )
158
+ thermo_str = f"fix langevin all langevin {temperature[0]!s} {temperature[1]!s} {temperature_damping_timescale!s} {seed!s} zero yes"
175
159
  else: # NVT(Nose-Hoover)
176
- fix_ensemble_str = "fix ensemble all nvt temp {0} {1} {2}".format(
177
- str(temperature[0]),
178
- str(temperature[1]),
179
- str(temperature_damping_timescale),
180
- )
160
+ fix_ensemble_str = f"fix ensemble all nvt temp {temperature[0]!s} {temperature[1]!s} {temperature_damping_timescale!s}"
181
161
  else: # NVE
182
162
  if langevin:
183
163
  warnings.warn("Temperature not set; Langevin ignored.")
@@ -190,8 +170,8 @@ def calc_md(
190
170
  if thermo_str != "":
191
171
  line_lst.append(thermo_str)
192
172
 
193
- line_lst.append("variable thermotime equal {} ".format(n_print))
194
- line_lst.append("timestep {}".format(time_step))
173
+ line_lst.append(f"variable thermotime equal {n_print} ")
174
+ line_lst.append(f"timestep {time_step}")
195
175
  if initial_temperature is not None and initial_temperature > 0:
196
176
  line_lst.append(
197
177
  _set_initial_velocity(
@@ -241,18 +221,14 @@ def calc_minimize(
241
221
  style ('cg'/'sd'/other values from Lammps docs): The style of the numeric minimization, either conjugate
242
222
  gradient, steepest descent, or other keys permissible from the Lammps docs on 'min_style'. (Default
243
223
  is 'cg' -- conjugate gradient.)
244
- rotation_matrix (numpy.ndarray): The rotation matrix from the pyiron to Lammps coordinate frame.
224
+ rotation_matrix (numpy.ndarray): The rotation matrix from the ASE to Lammps coordinate frame.
245
225
  """
246
- # This docstring is a source for the calc_minimize method in pyiron_atomistics.lammps.base.LammpsBase.calc_minimize and
247
- # pyiron_atomistics.lammps.interactive.LammpsInteractive.calc_minimize -- Please ensure that changes to signature or
248
- # defaults stay consistent!
249
-
250
226
  max_evaluations = 100 * max_iter
251
227
  if n_print > max_iter:
252
228
  warnings.warn("n_print larger than max_iter, adjusting to n_print=max_iter")
253
229
  n_print = max_iter
254
230
 
255
- if units not in LAMMPS_UNIT_CONVERSIONS.keys():
231
+ if units not in LAMMPS_UNIT_CONVERSIONS:
256
232
  raise NotImplementedError
257
233
  energy_units = LAMMPS_UNIT_CONVERSIONS[units]["energy"]
258
234
  force_units = LAMMPS_UNIT_CONVERSIONS[units]["force"]
@@ -260,7 +236,7 @@ def calc_minimize(
260
236
  ionic_energy_tolerance *= energy_units
261
237
  ionic_force_tolerance *= force_units
262
238
 
263
- line_lst = ["variable thermotime equal {} ".format(n_print)]
239
+ line_lst = [f"variable thermotime equal {n_print} "]
264
240
  line_lst += _get_thermo()
265
241
  if pressure is not None:
266
242
  if rotation_matrix is None:
@@ -278,14 +254,14 @@ def calc_minimize(
278
254
  pressure=pressure, rotation_matrix=rotation_matrix, units=units
279
255
  )
280
256
  if np.isscalar(pressure):
281
- str_press = " iso {}".format(float(cast(Any, pressure)))
257
+ str_press = f" iso {float(cast(Any, pressure))}"
282
258
  else:
283
259
  str_press = ""
284
260
  for ii, (press, str_axis) in enumerate(
285
261
  zip(pressure, ["x", "y", "z", "xy", "xz", "yz"])
286
262
  ):
287
263
  if press is not None:
288
- str_press += " {} {}".format(str_axis, press)
264
+ str_press += f" {str_axis} {press}"
289
265
  # if ii > 2:
290
266
  # force_skewed = True
291
267
  if len(str_press) > 1:
@@ -407,11 +383,11 @@ def _pressure_to_lammps(pressure, rotation_matrix, units="metal"):
407
383
  In case of a single pressure value, it is again returned as a single pressure value, to be used with the "iso"
408
384
  option (i.e., coupled deformation in x, y, and z).
409
385
 
410
- Finally, we also ensure that the units are converted from pyiron's GPa to whatever Lammps needs.
386
+ Finally, we also ensure that the units are converted from lammpsparser's GPa to whatever Lammps needs.
411
387
 
412
388
  Args:
413
389
  pressure (float/list/tuple/numpy.ndarray): The pressure(s) to convert.
414
- rotation_matrix (numpy.ndarray): The 3x3 matrix rotating from the pyiron to Lammps coordinate frame.
390
+ rotation_matrix (numpy.ndarray): The 3x3 matrix rotating from the ASE to Lammps coordinate frame.
415
391
 
416
392
  Returns:
417
393
  (list): pxx, pyy, pzz, pxy, pxz, and pyz to be passed to Lammps.
@@ -433,7 +409,7 @@ def _pressure_to_lammps(pressure, rotation_matrix, units="metal"):
433
409
  if len(pressure) > 6:
434
410
  raise ValueError(
435
411
  "Pressure can have a maximum of 6 values, (x, y, z, xy, xz, and yz), but got "
436
- + "{}".format(len(pressure))
412
+ + f"{len(pressure)}"
437
413
  )
438
414
  pressure = [float(p) if p is not None else None for p in pressure]
439
415
  pressure += (6 - len(pressure)) * [None]
@@ -451,7 +427,7 @@ def _pressure_to_lammps(pressure, rotation_matrix, units="metal"):
451
427
  "Cells which are not orthorhombic or an upper-triangular cell are incompatible with Lammps "
452
428
  "constant pressure calculations unless the entire pressure tensor is defined. "
453
429
  "The reason is that Lammps demands such cells be represented with an "
454
- "upper-triangular unit cell, thus a rotation between Lammps and pyiron coordinate "
430
+ "upper-triangular unit cell, thus a rotation between Lammps and ASE coordinate "
455
431
  "frames is required; it is not possible to rotate the pressure tensor if any of "
456
432
  "its components is None."
457
433
  )
@@ -1,5 +1,6 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
- from typing import Optional, Union
3
4
 
4
5
  import numpy as np
5
6
 
@@ -41,20 +42,20 @@ class CalcMDInput:
41
42
  frame. Derived automatically when ``None``.
42
43
  """
43
44
 
44
- temperature: Optional[Union[float, list]] = None
45
- pressure: Optional[Union[float, list, np.ndarray]] = None
45
+ temperature: float | list | None = None
46
+ pressure: float | list | np.ndarray | None = None
46
47
  n_ionic_steps: int = 1
47
48
  time_step: float = 1.0
48
49
  n_print: int = 100
49
50
  temperature_damping_timescale: float = 100.0
50
51
  pressure_damping_timescale: float = 1000.0
51
52
  seed: int = 80996
52
- tloop: Optional[int] = None
53
- initial_temperature: Optional[float] = None
53
+ tloop: int | None = None
54
+ initial_temperature: float | None = None
54
55
  langevin: bool = False
55
- delta_temp: Optional[float] = None
56
- delta_press: Optional[float] = None
57
- rotation_matrix: Optional[Union[list, np.ndarray]] = None
56
+ delta_temp: float | None = None
57
+ delta_press: float | None = None
58
+ rotation_matrix: list | np.ndarray | None = None
58
59
 
59
60
 
60
61
  @dataclass
@@ -81,7 +82,7 @@ class CalcMinimizeInput:
81
82
  ionic_energy_tolerance: float = 0.0
82
83
  ionic_force_tolerance: float = 1e-4
83
84
  max_iter: int = 100000
84
- pressure: Optional[Union[float, list, np.ndarray]] = None
85
+ pressure: float | list | np.ndarray | None = None
85
86
  n_print: int = 1
86
87
  style: str = "cg"
87
- rotation_matrix: Optional[Union[list, np.ndarray]] = None
88
+ rotation_matrix: list | np.ndarray | None = None
@@ -1,8 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import shutil
3
5
  import subprocess
4
6
  from dataclasses import asdict
5
- from typing import Optional, Union
6
7
 
7
8
  import pandas
8
9
  from ase.atoms import Atoms
@@ -24,26 +25,27 @@ def lammps_file_interface_function(
24
25
  structure: Atoms,
25
26
  potential: str,
26
27
  calc_mode: str = "static",
27
- calc_kwargs: Optional[dict] = None,
28
- calc_dataclass: Optional[Union[CalcMDInput, CalcMinimizeInput]] = None,
28
+ calc_kwargs: dict | None = None,
29
+ calc_dataclass: CalcMDInput | CalcMinimizeInput | None = None,
29
30
  units: str = "metal",
30
- lmp_command: Optional[str] = None,
31
- resource_path: Optional[str] = None,
32
- input_control_file: Optional[dict] = None,
31
+ lmp_command: str | None = None,
32
+ resource_path: str | None = None,
33
+ input_control_file: dict | None = None,
33
34
  write_restart_file: bool = False,
34
35
  read_restart_file: bool = False,
35
36
  restart_file: str = "restart.out",
37
+ dump_final_structure: bool = False,
36
38
  ):
37
39
  """
38
- A single function to execute a LAMMPS calculation based on the LAMMPS job implemented in pyiron
40
+ A single function to execute a LAMMPS calculation based on the LAMMPS job interface implemented in lammpsparser
39
41
 
40
42
  Examples:
41
43
 
42
44
  >>> import os
43
45
  >>> from ase.build import bulk
44
- >>> from pyiron_atomistics.lammps.lammps import lammps_function
46
+ >>> from lammpsparser.compatibility.file import lammps_file_interface_function
45
47
  >>>
46
- >>> shell_output, parsed_output, job_crashed = lammps_function(
48
+ >>> shell_output, parsed_output, job_crashed = lammps_file_interface_function(
47
49
  ... working_directory=os.path.abspath("lmp_working_directory"),
48
50
  ... structure=bulk("Al", cubic=True),
49
51
  ... potential='2009--Mendelev-M-I--Al-Mg--LAMMPS--ipr1',
@@ -85,6 +87,8 @@ def lammps_file_interface_function(
85
87
  write_restart_file (bool): enable writing the LAMMPS restart file
86
88
  read_restart_file (bool): enable loading the LAMMPS restart file
87
89
  restart_file (str): file name of the LAMMPS restart file to copy
90
+ dump_final_structure (bool): in "md" mode, append a ``write_dump`` command after the ``run`` command to capture
91
+ the final structure in ``dump.out`` if last step is not a regular dump step. Disabled by default.
88
92
  executable_version (str): LAMMPS version to for the execution
89
93
  executable_path (str): path to the LAMMPS executable
90
94
  input_control_file (str|list|dict): Option to modify the LAMMPS input file directly
@@ -141,14 +145,21 @@ def lammps_file_interface_function(
141
145
  else:
142
146
  lmp_str_lst.append(line)
143
147
 
148
+ dump_fields = "id type xsu ysu zsu fx fy fz vx vy vz"
149
+ dump_format = '"%d %d %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g"'
150
+
144
151
  lmp_str_lst += potential_lst
145
152
  lmp_str_lst += ["variable dumptime equal {} ".format(calc_kwargs.get("n_print", 1))]
146
153
  lmp_str_lst += [
147
- "dump 1 all custom ${dumptime} dump.out id type xsu ysu zsu fx fy fz vx vy vz",
148
- 'dump_modify 1 sort id format line "%d %d %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g"',
154
+ "dump 1 all custom ${dumptime} dump.out " + dump_fields,
155
+ "dump_modify 1 sort id format line " + dump_format,
149
156
  ]
150
157
 
151
158
  if calc_mode == "static":
159
+ if dump_final_structure:
160
+ raise ValueError(
161
+ "dump_final_structure is only applicable for molecular dynamics (md) calculations."
162
+ )
152
163
  lmp_str_constraint_lst = [
153
164
  k + " " + v
154
165
  for k, v in set_selective_dynamics(
@@ -168,7 +179,7 @@ def lammps_file_interface_function(
168
179
  structure=structure, calc_md=True
169
180
  ).items()
170
181
  ]
171
- if "n_ionic_steps" in calc_kwargs.keys():
182
+ if "n_ionic_steps" in calc_kwargs:
172
183
  n_ionic_steps = int(calc_kwargs.pop("n_ionic_steps"))
173
184
  else:
174
185
  n_ionic_steps = 1
@@ -182,8 +193,21 @@ def lammps_file_interface_function(
182
193
  )
183
194
  if read_restart_file:
184
195
  lmp_str_lst += ["reset_timestep 0"]
185
- lmp_str_lst += ["run {} ".format(n_ionic_steps)]
196
+ lmp_str_lst += [f"run {n_ionic_steps} "]
197
+ last_step_is_regular_dump = n_ionic_steps % calc_kwargs.get("n_print", 1) == 0
198
+ if dump_final_structure and not last_step_is_regular_dump:
199
+ lmp_str_lst += [
200
+ "write_dump all custom dump.out "
201
+ + dump_fields
202
+ + " modify sort id format line "
203
+ + dump_format
204
+ + " append yes"
205
+ ]
186
206
  elif calc_mode == "minimize":
207
+ if dump_final_structure:
208
+ raise ValueError(
209
+ "dump_final_structure is only applicable for molecular dynamics (md) calculations."
210
+ )
187
211
  calc_kwargs["units"] = units
188
212
  lmp_str_constraint_lst = [
189
213
  k + " " + v
@@ -282,8 +306,8 @@ def lammps_file_initialization(
282
306
 
283
307
 
284
308
  def _modify_input_dict(
285
- input_control_file: Optional[dict] = None,
286
- lmp_str_lst: list[str] = [],
309
+ input_control_file: dict | None = None,
310
+ lmp_str_lst: list[str] | None = None,
287
311
  ):
288
312
  """
289
313
  Apply user-supplied overrides to a LAMMPS input line list.
@@ -302,13 +326,15 @@ def _modify_input_dict(
302
326
  Returns:
303
327
  list[str]: Modified (or unchanged) list of LAMMPS input lines.
304
328
  """
329
+ if lmp_str_lst is None:
330
+ lmp_str_lst = []
305
331
  if input_control_file is not None:
306
332
  lmp_tmp_lst, keys_used = [], []
307
333
  for line in lmp_str_lst:
308
334
  ls = line.split()
309
335
  if len(ls) >= 1: # Remove empty lines
310
336
  key = ls[0]
311
- if key in input_control_file.keys():
337
+ if key in input_control_file:
312
338
  lmp_tmp_lst.append(key + " " + input_control_file[key])
313
339
  keys_used.append(key)
314
340
  else:
@@ -322,7 +348,7 @@ def _modify_input_dict(
322
348
  return lmp_str_lst
323
349
 
324
350
 
325
- def _get_potential(potential, resource_path: Optional[str] = None):
351
+ def _get_potential(potential, resource_path: str | None = None):
326
352
  """
327
353
  Resolve a potential specification to LAMMPS input lines and species list.
328
354
 
@@ -6,6 +6,7 @@ def structure_from_parsed_output(
6
6
  parsed_output: dict,
7
7
  *,
8
8
  wrap: bool = False,
9
+ scale_atoms: bool = True,
9
10
  index: int = -1,
10
11
  ) -> Atoms:
11
12
  """Construct an `Atoms` object from parsed output data.
@@ -27,10 +28,12 @@ def structure_from_parsed_output(
27
28
  """
28
29
  # Take a copy of the initial structure as template and update the relevant properties
29
30
  atoms_copy = initial_structure.copy()
31
+ atoms_copy.set_cell(
32
+ parsed_output["generic"]["cells"][index], scale_atoms=scale_atoms
33
+ )
30
34
  atoms_copy.set_array("indices", parsed_output["generic"]["indices"][index])
31
35
  atoms_copy.set_positions(parsed_output["generic"]["positions"][index])
32
36
  atoms_copy.set_velocities(parsed_output["generic"]["velocities"][index])
33
- atoms_copy.set_cell(parsed_output["generic"]["cells"][index])
34
37
  atoms_copy.set_pbc(True)
35
38
  if wrap:
36
39
  atoms_copy.wrap()
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections import OrderedDict
2
- from typing import Dict, Optional
4
+ from typing import cast
3
5
 
4
6
  import numpy as np
5
7
  from ase.atoms import Atoms
@@ -12,15 +14,15 @@ from lammpsparser.structure import LammpsStructure
12
14
  class LammpsStructureCompatibility(LammpsStructure):
13
15
  def __init__(
14
16
  self,
15
- bond_dict: Optional[Dict] = None,
17
+ bond_dict: dict | None = None,
16
18
  units: str = "metal",
17
19
  atom_type: str = "atomic",
18
20
  ):
19
21
  super().__init__(bond_dict=bond_dict, units=units, atom_type=atom_type)
20
- self._molecule_ids: list[int] = []
22
+ self._molecule_ids: list[int] | None = []
21
23
 
22
24
  @property
23
- def structure(self) -> Optional[Atoms]:
25
+ def structure(self) -> Atoms | None:
24
26
  """The ASE :class:`~ase.atoms.Atoms` object associated with this instance."""
25
27
  return self._structure
26
28
 
@@ -37,17 +39,28 @@ class LammpsStructureCompatibility(LammpsStructure):
37
39
  structure (ase.atoms.Atoms): The structure to serialise.
38
40
  """
39
41
  self._structure = structure
40
- if self.atom_type == "full":
42
+ if self._atom_type == "full":
41
43
  input_str = self.structure_full()
42
- elif self.atom_type == "bond":
44
+ elif self._atom_type == "bond":
43
45
  input_str = self.structure_bond()
44
- elif self.atom_type == "charge":
46
+ elif self._atom_type == "charge":
45
47
  input_str = self.structure_charge()
46
- else: # self.atom_type == 'atomic'
48
+ else: # self._atom_type == 'atomic'
47
49
  input_str = self.structure_atomic()
48
50
 
49
51
  self._string_input = input_str + self._get_velocities_input_string()
50
52
 
53
+ @property
54
+ def molecule_ids(self) -> list[int] | np.ndarray:
55
+ """Per-atom molecule IDs; defaults to all atoms in a single molecule."""
56
+ if self._molecule_ids is None or len(self._molecule_ids) == 0:
57
+ return np.ones(len(cast(Atoms, self._structure)), dtype=int)
58
+ return self._molecule_ids
59
+
60
+ @molecule_ids.setter
61
+ def molecule_ids(self, molecule_ids: list[int] | None):
62
+ self._molecule_ids = molecule_ids
63
+
51
64
  def structure_bond(self):
52
65
  """
53
66
  Serialise the structure for LAMMPS ``atom_style bond``.
@@ -120,7 +133,7 @@ class LammpsStructureCompatibility(LammpsStructure):
120
133
  bond_type[i, j] = count
121
134
  bond_type[j, i] = count
122
135
 
123
- if self.structure.bonds is None:
136
+ if getattr(self.structure, "bonds", None) is None:
124
137
  if self.cutoff_radius is None:
125
138
  bonds_lst = get_bonds(structure=self.structure, max_shells=1)
126
139
  else:
@@ -143,9 +156,7 @@ class LammpsStructureCompatibility(LammpsStructure):
143
156
 
144
157
  bonds_str = "Bonds \n\n"
145
158
  for i_bond, (i_a, i_b, b_type) in enumerate(bonds):
146
- bonds_str += (
147
- "{0:d} {1:d} {2:d} {3:d}".format(i_bond + 1, b_type, i_a, i_b) + "\n"
148
- )
159
+ bonds_str += f"{i_bond + 1:d} {b_type:d} {i_a:d} {i_b:d}" + "\n"
149
160
 
150
161
  return (
151
162
  self.lammps_header(
@@ -182,17 +193,23 @@ class LammpsStructureCompatibility(LammpsStructure):
182
193
  coords = self.rotate_positions(self._structure)
183
194
 
184
195
  # extract electric charges from potential file
185
- q_dict = {
186
- species_name: self.potential.get_charge(species_name)
187
- for species_name in set(self.structure.get_chemical_symbols())
188
- }
196
+ if self.potential is not None:
197
+ q_dict = {
198
+ species_name: self.potential.get_charge(species_name)
199
+ for species_name in set(self.structure.get_chemical_symbols())
200
+ }
201
+ else:
202
+ q_dict = {
203
+ species_name: 0.0
204
+ for species_name in set(self.structure.get_chemical_symbols())
205
+ }
189
206
 
190
207
  bonds_lst, angles_lst = [], []
191
208
  bond_type_lst, angle_type_lst = [], []
192
209
  # Using a cutoff distance to draw the bonds instead of the number of neighbors
193
210
  # Only if any bonds are defined
194
- if len(self._bond_dict.keys()) > 0:
195
- cutoff_list = list()
211
+ if self._bond_dict is not None and len(self._bond_dict.keys()) > 0:
212
+ cutoff_list = []
196
213
  for val in self._bond_dict.values():
197
214
  cutoff_list.append(np.max(val["cutoff_list"]))
198
215
  max_cutoff = np.max(cutoff_list)
@@ -205,45 +222,42 @@ class LammpsStructureCompatibility(LammpsStructure):
205
222
  # Go through all elements for which bonds are defined
206
223
  for element, val in self._bond_dict.items():
207
224
  el_1_list = select_index(structure=self._structure, element=element)
208
- if el_1_list is not None:
209
- if len(el_1_list) > 0:
210
- for i, v in enumerate(val["element_list"]):
211
- el_2_list = select_index(
212
- structure=self._structure, element=v
225
+ if el_1_list is not None and len(el_1_list) > 0:
226
+ for i, v in enumerate(val["element_list"]):
227
+ el_2_list = select_index(structure=self._structure, element=v)
228
+ cutoff_dist = val["cutoff_list"][i]
229
+ for j, ind in enumerate(
230
+ np.array(neighbors.indices, dtype=object)[el_1_list]
231
+ ):
232
+ # Only chose those indices within the cutoff distance and which belong
233
+ # to the species defined in the element_list
234
+ # i is the index of each bond type, and j is the element index
235
+ id_el = el_1_list[j]
236
+ bool_1 = (
237
+ np.array(neighbors.distances, dtype=object)[id_el]
238
+ <= cutoff_dist
213
239
  )
214
- cutoff_dist = val["cutoff_list"][i]
215
- for j, ind in enumerate(
216
- np.array(neighbors.indices, dtype=object)[el_1_list]
240
+ act_ind = ind[bool_1]
241
+ bool_2 = np.isin(act_ind, el_2_list)
242
+ final_ind = act_ind[bool_2]
243
+ # Get the bond and angle type
244
+ bond_type = val["bond_type_list"][i]
245
+ angle_type = val["angle_type_list"][i]
246
+ # Draw only maximum allowed bonds
247
+ final_ind = final_ind[: val["max_bond_list"][i]]
248
+ for fi in final_ind:
249
+ bonds_lst.append([id_el + 1, fi + 1])
250
+ bond_type_lst.append(bond_type)
251
+ # Draw angles if at least 2 bonds are present and if an angle type is defined for this
252
+ # particular set of bonds
253
+ if (
254
+ len(final_ind) >= 2
255
+ and val["angle_type_list"][i] is not None
217
256
  ):
218
- # Only chose those indices within the cutoff distance and which belong
219
- # to the species defined in the element_list
220
- # i is the index of each bond type, and j is the element index
221
- id_el = el_1_list[j]
222
- bool_1 = (
223
- np.array(neighbors.distances, dtype=object)[id_el]
224
- <= cutoff_dist
257
+ angles_lst.append(
258
+ [final_ind[0] + 1, id_el + 1, final_ind[1] + 1]
225
259
  )
226
- act_ind = ind[bool_1]
227
- bool_2 = np.isin(act_ind, el_2_list)
228
- final_ind = act_ind[bool_2]
229
- # Get the bond and angle type
230
- bond_type = val["bond_type_list"][i]
231
- angle_type = val["angle_type_list"][i]
232
- # Draw only maximum allowed bonds
233
- final_ind = final_ind[: val["max_bond_list"][i]]
234
- for fi in final_ind:
235
- bonds_lst.append([id_el + 1, fi + 1])
236
- bond_type_lst.append(bond_type)
237
- # Draw angles if at least 2 bonds are present and if an angle type is defined for this
238
- # particular set of bonds
239
- if (
240
- len(final_ind) >= 2
241
- and val["angle_type_list"][i] is not None
242
- ):
243
- angles_lst.append(
244
- [final_ind[0] + 1, id_el + 1, final_ind[1] + 1]
245
- )
246
- angle_type_lst.append(angle_type)
260
+ angle_type_lst.append(angle_type)
247
261
 
248
262
  if len(bond_type_lst) == 0:
249
263
  num_bond_types = 0
@@ -277,9 +291,7 @@ class LammpsStructureCompatibility(LammpsStructure):
277
291
  bonds_str = "Bonds \n\n"
278
292
  for i_bond, id_vec in enumerate(bonds_lst):
279
293
  bonds_str += (
280
- "{0:d} {1:d} {2:d} {3:d}".format(
281
- i_bond + 1, bond_type_lst[i_bond], id_vec[0], id_vec[1]
282
- )
294
+ f"{i_bond + 1:d} {bond_type_lst[i_bond]:d} {id_vec[0]:d} {id_vec[1]:d}"
283
295
  + "\n"
284
296
  )
285
297
  else:
@@ -289,13 +301,7 @@ class LammpsStructureCompatibility(LammpsStructure):
289
301
  angles_str = "Angles \n\n"
290
302
  for i_angle, id_vec in enumerate(angles_lst):
291
303
  angles_str += (
292
- "{0:d} {1:d} {2:d} {3:d} {4:d}".format(
293
- i_angle + 1,
294
- angle_type_lst[i_angle],
295
- id_vec[0],
296
- id_vec[1],
297
- id_vec[2],
298
- )
304
+ f"{i_angle + 1:d} {angle_type_lst[i_angle]:d} {id_vec[0]:d} {id_vec[1]:d} {id_vec[2]:d}"
299
305
  + "\n"
300
306
  )
301
307
  else:
@@ -350,7 +356,6 @@ def get_bonds(
350
356
  tolerance=2,
351
357
  id_list=None,
352
358
  width_buffer=1.2,
353
- allow_ragged=None,
354
359
  mode="ragged",
355
360
  norm_order=2,
356
361
  )