mdinterface 1.5.2__tar.gz → 1.5.4__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 (96) hide show
  1. {mdinterface-1.5.2/mdinterface.egg-info → mdinterface-1.5.4}/PKG-INFO +9 -11
  2. {mdinterface-1.5.2 → mdinterface-1.5.4}/README.md +4 -6
  3. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/__init__.py +2 -2
  4. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/build/builder.py +1 -1
  5. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/core/specie.py +3 -2
  6. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/core/topology.py +3 -2
  7. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/io/lammpswriter.py +51 -18
  8. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/simulationbox.py +9 -3
  9. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/map.py +9 -19
  10. {mdinterface-1.5.2 → mdinterface-1.5.4/mdinterface.egg-info}/PKG-INFO +9 -11
  11. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface.egg-info/SOURCES.txt +2 -0
  12. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface.egg-info/top_level.txt +1 -0
  13. {mdinterface-1.5.2 → mdinterface-1.5.4}/pyproject.toml +4 -4
  14. mdinterface-1.5.4/scripts/extract_changelog_summary.py +78 -0
  15. mdinterface-1.5.4/tests/test_changelog_summary.py +91 -0
  16. {mdinterface-1.5.2 → mdinterface-1.5.4}/LICENSE +0 -0
  17. {mdinterface-1.5.2 → mdinterface-1.5.4}/MANIFEST.in +0 -0
  18. {mdinterface-1.5.2 → mdinterface-1.5.4}/assets/mdinterface.png +0 -0
  19. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/api/database.md +0 -0
  20. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/api/externals.md +0 -0
  21. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/api/io.md +0 -0
  22. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/api/polymer.md +0 -0
  23. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/api/simcell.md +0 -0
  24. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/api/specie.md +0 -0
  25. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/assets/mdinterface.png +0 -0
  26. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/guide/database.md +0 -0
  27. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/guide/logging.md +0 -0
  28. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/guide/polymer.md +0 -0
  29. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/guide/simcell.md +0 -0
  30. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/guide/specie.md +0 -0
  31. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/index.md +0 -0
  32. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/installation.md +0 -0
  33. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/quickstart.md +0 -0
  34. {mdinterface-1.5.2 → mdinterface-1.5.4}/docs/requirements.txt +0 -0
  35. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/electrode_interface.py +0 -0
  36. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/legacy/make_POSCAR.py +0 -0
  37. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/legacy/make_box.py +0 -0
  38. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/legacy/make_polymer.py +0 -0
  39. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/legacy/make_solvent_box.py +0 -0
  40. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/legacy/make_specie_ligpargen_resp.py +0 -0
  41. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/multilayer.py +0 -0
  42. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/multisolvent_box.py +0 -0
  43. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/polymer/polymer_piperion.py +0 -0
  44. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/sandwich_from_traj.py +0 -0
  45. {mdinterface-1.5.2 → mdinterface-1.5.4}/examples/solvent_box.py +0 -0
  46. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/build/__init__.py +0 -0
  47. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/build/box.py +0 -0
  48. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/build/continuum2sim.py +0 -0
  49. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/build/polymerize.py +0 -0
  50. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/build/snippets.py +0 -0
  51. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/build/solvent.py +0 -0
  52. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/config.py +0 -0
  53. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/core/__init__.py +0 -0
  54. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/core/polymer.py +0 -0
  55. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/database/__init__.py +0 -0
  56. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/database/graphene.py +0 -0
  57. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/database/ions.py +0 -0
  58. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/database/metals.py +0 -0
  59. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/database/molecules.py +0 -0
  60. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/database/nobles.py +0 -0
  61. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/externals/__init__.py +0 -0
  62. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/externals/aimd.py +0 -0
  63. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/externals/ase.py +0 -0
  64. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/externals/ligpargen.py +0 -0
  65. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/externals/obabel.py +0 -0
  66. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/externals/optimization.py +0 -0
  67. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/externals/pyscf.py +0 -0
  68. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/io/__init__.py +0 -0
  69. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/io/gromacswriter.py +0 -0
  70. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/io/packmol.py +0 -0
  71. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/io/read.py +0 -0
  72. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/read/cp2ktraj.py +0 -0
  73. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/read/lammpstraj.py +0 -0
  74. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/read/read.py +0 -0
  75. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/read/trajectory.py +0 -0
  76. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/read/xyztraj.py +0 -0
  77. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/__init__.py +0 -0
  78. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/auxiliary.py +0 -0
  79. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/draw.py +0 -0
  80. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/graphs.py +0 -0
  81. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/logger.py +0 -0
  82. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/poisson.py +0 -0
  83. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/rings.py +0 -0
  84. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface/utils/units.py +0 -0
  85. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface.egg-info/dependency_links.txt +0 -0
  86. {mdinterface-1.5.2 → mdinterface-1.5.4}/mdinterface.egg-info/requires.txt +0 -0
  87. {mdinterface-1.5.2 → mdinterface-1.5.4}/requirements.txt +0 -0
  88. {mdinterface-1.5.2 → mdinterface-1.5.4}/setup.cfg +0 -0
  89. {mdinterface-1.5.2 → mdinterface-1.5.4}/setup.py +0 -0
  90. {mdinterface-1.5.2 → mdinterface-1.5.4}/tests/__init__.py +0 -0
  91. {mdinterface-1.5.2 → mdinterface-1.5.4}/tests/test_auxiliary.py +0 -0
  92. {mdinterface-1.5.2 → mdinterface-1.5.4}/tests/test_builder.py +0 -0
  93. {mdinterface-1.5.2 → mdinterface-1.5.4}/tests/test_database.py +0 -0
  94. {mdinterface-1.5.2 → mdinterface-1.5.4}/tests/test_solvent.py +0 -0
  95. {mdinterface-1.5.2 → mdinterface-1.5.4}/tests/test_specie.py +0 -0
  96. {mdinterface-1.5.2 → mdinterface-1.5.4}/tests/test_topology.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdinterface
3
- Version: 1.5.2
3
+ Version: 1.5.4
4
4
  Summary: Build Interface Systems for Molecular Dynamics Simulations
5
5
  Author-email: Fabrice Roncoroni <fabrice.roncoroni@gmail.com>
6
6
  License-Expression: Apache-2.0
7
- Project-URL: Homepage, https://gitlab.com/roncofaber/mdinterface
8
- Project-URL: Repository, https://gitlab.com/roncofaber/mdinterface.git
9
- Project-URL: Documentation, https://gitlab.com/roncofaber/mdinterface
10
- Project-URL: Bug Tracker, https://gitlab.com/roncofaber/mdinterface/-/issues
7
+ Project-URL: Homepage, https://github.com/roncofaber/mdinterface
8
+ Project-URL: Repository, https://github.com/roncofaber/mdinterface.git
9
+ Project-URL: Documentation, https://roncofaber.github.io/mdinterface
10
+ Project-URL: Bug Tracker, https://github.com/roncofaber/mdinterface/issues
11
11
  Keywords: molecular dynamics,simulation,interface,chemistry,materials science
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Science/Research
@@ -41,12 +41,10 @@ Requires-Dist: pymbxas; extra == "all"
41
41
  Requires-Dist: fairchem-core; extra == "all"
42
42
  Dynamic: license-file
43
43
 
44
- <div style="display: flex; align-items: center;">
45
- <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
46
- <div style="display: flex; flex-direction: column;">
47
- <h1 style="margin: 0;">mdinterface: Build Interface Systems for Molecular Dynamics Simulations</h1>
48
- </div>
49
- </div>
44
+ <h1>
45
+ <img src="./assets/mdinterface.png" alt="Logo" width="60" style="vertical-align: middle; margin-right: 10px;">
46
+ mdinterface: Build Interface Systems for Molecular Dynamics Simulations
47
+ </h1>
50
48
 
51
49
  [![PyPI version](https://badge.fury.io/py/mdinterface.svg?icon=si%3Apython)](https://pypi.org/project/mdinterface/) [![GitHub version](https://badge.fury.io/gh/roncofaber%2Fmdinterface.svg?icon=si%3Agithub)](https://github.com/roncofaber/mdinterface) [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://roncofaber.github.io/mdinterface)
52
50
 
@@ -1,9 +1,7 @@
1
- <div style="display: flex; align-items: center;">
2
- <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
3
- <div style="display: flex; flex-direction: column;">
4
- <h1 style="margin: 0;">mdinterface: Build Interface Systems for Molecular Dynamics Simulations</h1>
5
- </div>
6
- </div>
1
+ <h1>
2
+ <img src="./assets/mdinterface.png" alt="Logo" width="60" style="vertical-align: middle; margin-right: 10px;">
3
+ mdinterface: Build Interface Systems for Molecular Dynamics Simulations
4
+ </h1>
7
5
 
8
6
  [![PyPI version](https://badge.fury.io/py/mdinterface.svg?icon=si%3Apython)](https://pypi.org/project/mdinterface/) [![GitHub version](https://badge.fury.io/gh/roncofaber%2Fmdinterface.svg?icon=si%3Agithub)](https://github.com/roncofaber/mdinterface) [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://roncofaber.github.io/mdinterface)
9
7
 
@@ -6,8 +6,8 @@ Initially developed to construct electrolyte/electrode interfaces, it is also we
6
6
 
7
7
  """
8
8
 
9
- __version__ = '1.5.2'
10
- __date__ = '28 Apr. 2026'
9
+ __version__ = '1.5.4'
10
+ __date__ = '12 Aug. 2026'
11
11
  __author__ = 'Fabrice Roncoroni'
12
12
  __all__ = ['SimulationBox', 'SimCell', 'BoxBuilder', "Specie", "Polymer"]
13
13
 
@@ -669,7 +669,7 @@ class SimCell:
669
669
  padding: float,
670
670
  layered: bool,
671
671
  do_match: bool,
672
- ) -> Tuple[mda.Universe, float]:
672
+ ) -> Tuple[mda.Universe, float, float]:
673
673
  """
674
674
  Second pass: assemble all layers into a single Universe.
675
675
 
@@ -857,9 +857,10 @@ class Specie(object):
857
857
  charges, atoms = calculate_RESP_charges(self, **respargs)
858
858
 
859
859
  if assign:
860
- self.atoms.set_positions(atoms.get_positions())
861
860
  self.atoms.set_initial_charges(charges)
862
-
861
+ if method == "resp":
862
+ self.atoms.set_positions(atoms.get_positions())
863
+
863
864
  return charges
864
865
 
865
866
  def estimate_OPLSAA_parameters(self, charge=None):
@@ -249,13 +249,14 @@ class Dihedral(Topology):
249
249
  return (self.values == other.values and
250
250
  (self.symbols == other.symbols or self.symbols == other.symbols[::-1]))
251
251
 
252
- def write(self, fout):
252
+ def write(self, fout, idx=None):
253
253
  atype = "{}-{}-{}-{}".format(*self.symbols)
254
254
  if self._values[-1] is not None:
255
255
  value = "{:>7.4f} {:>7.4f} {:>7.4f} {:>7.4f} {:>7.4f}".format(*self.values)
256
256
  else:
257
257
  value = "{:>7.4f} {:>7.4f} {:>7.4f} {:>7.4f}".format(*self.values[:-1])
258
- fout.write("{:>5} {} # {:<8} | {}\n".format(self.id, value, atype, self.resname))
258
+ fout.write("{:>5} {} # {:<8} | {}\n".format(
259
+ idx if idx is not None else self.id, value, atype, self.resname))
259
260
 
260
261
  def __bool__(self):
261
262
  return any([bool(val) for val in self.values])
@@ -25,6 +25,16 @@ btype_sections = {'bond':'Bonds', 'angle':'Angles',
25
25
  'dihedral':'Dihedrals', 'improper':'Impropers'}
26
26
 
27
27
 
28
+ def _renumber_bonded_types(raw_types):
29
+ """
30
+ Map raw per-item bond/angle/dihedral/improper type ids -- which may be
31
+ sparse or non-contiguous (e.g. when a Specie template is reused across
32
+ several layers) -- to a contiguous 1..N range ordered by numeric value.
33
+ """
34
+ unique_sorted = sorted({int(t) for t in raw_types})
35
+ return {t: i + 1 for i, t in enumerate(unique_sorted)}
36
+
37
+
28
38
  class DATAWriter(base.WriterBase):
29
39
  """Write out the current time step as a LAMMPS DATA file.
30
40
 
@@ -96,9 +106,10 @@ class DATAWriter(base.WriterBase):
96
106
  moltags = atoms.resindices
97
107
  m_l = len(str(moltags.max()))+1
98
108
 
109
+ coordinates = atoms.positions.copy()
99
110
  if self.convert_units:
100
- coordinates = self.convert_pos_to_native(atoms.positions, inplace=False)
101
-
111
+ coordinates = self.convert_pos_to_native(coordinates, inplace=False)
112
+
102
113
  b_l = len(str(int(coordinates.max()))) + 1
103
114
  b_t = b_l + 6
104
115
 
@@ -160,9 +171,14 @@ class DATAWriter(base.WriterBase):
160
171
  self.f.write('\n')
161
172
  self.f.write('{}\n'.format(btype_sections[bonds.btype]))
162
173
  self.f.write('\n')
174
+ try:
175
+ remap = _renumber_bonded_types(bond.type for bond in bonds)
176
+ except (TypeError, ValueError):
177
+ remap = None
163
178
  for bond, i in zip(bonds, range(1, len(bonds)+1)):
164
179
  try:
165
- self.f.write('{:d} {:d} '.format(i, int(bond.type))+\
180
+ btype_id = remap[int(bond.type)] if remap is not None else int(bond.type)
181
+ self.f.write('{:d} {:d} '.format(i, btype_id)+\
166
182
  ' '.join((bond.atoms.indices + 1).astype(str))+'\n')
167
183
  except TypeError:
168
184
  errmsg = (f"LAMMPS DATAWriter: Trying to write bond, but bond "
@@ -173,9 +189,9 @@ class DATAWriter(base.WriterBase):
173
189
  """Convert dimensions to triclinic vectors, convert lengths to native
174
190
  units and then write the dimensions section
175
191
  """
192
+ triv = mdamath.triclinic_vectors(dimensions)
176
193
  if self.convert_units:
177
- triv = self.convert_pos_to_native(mdamath.triclinic_vectors(
178
- dimensions),inplace=False)
194
+ triv = self.convert_pos_to_native(triv, inplace=False)
179
195
  self.f.write('\n')
180
196
  self.f.write('{:f} {:f} xlo xhi\n'.format(0., triv[0][0]))
181
197
  self.f.write('{:f} {:f} ylo yhi\n'.format(0., triv[1][1]))
@@ -336,18 +352,20 @@ def write_lammps_coefficients(
336
352
  # Write Pair Coefficients
337
353
  fout.write("Pair Coeffs\n\n")
338
354
 
339
- idx = 1
355
+ present_types = list(np.unique(system.atoms.types))
340
356
  atom_types = sorted_attributes.get("atoms", [])
357
+ written = set()
341
358
  for atom in atom_types:
342
- if atom.extended_label not in np.unique(system.atoms.types):
359
+ if atom.extended_label not in present_types or atom.extended_label in written:
343
360
  continue
361
+ written.add(atom.extended_label)
362
+ idx = present_types.index(atom.extended_label) + 1
344
363
 
345
364
  eps = atom.eps if atom.eps is not None else 0
346
365
  sig = atom.sig if atom.sig is not None else 0
347
366
 
348
367
  fout.write("{:>5} {:>12.8f} {:>12.8f} # {}\n".format(
349
368
  idx, eps, sig, atom.extended_label))
350
- idx += 1
351
369
 
352
370
  # Write Bond Coefficients
353
371
  bond_types = sorted_attributes.get("bonds", [])
@@ -355,9 +373,13 @@ def write_lammps_coefficients(
355
373
  fout.write("\n")
356
374
  fout.write("Bond Coeffs\n\n")
357
375
 
376
+ bond_remap = _renumber_bonded_types(system.bonds.types()) if len(system.bonds) else {}
377
+ written_bonds = set()
358
378
  for bond in bond_types:
359
- if bond.id not in np.array(system.bonds.types(), dtype=int):
379
+ if int(bond.id) not in bond_remap or bond.id in written_bonds:
360
380
  continue
381
+ written_bonds.add(bond.id)
382
+ idx = bond_remap[int(bond.id)]
361
383
 
362
384
  kr = bond.kr if bond.kr is not None else 0
363
385
  r0 = bond.r0 if bond.r0 is not None else 0
@@ -365,7 +387,7 @@ def write_lammps_coefficients(
365
387
  btype = "{}-{}".format(*bond.symbols)
366
388
 
367
389
  fout.write("{:>5} {:>10.6f} {:>10.6f} # {:<5} | {}\n".format(
368
- bond.id, kr, r0, btype, bond.resname))
390
+ idx, kr, r0, btype, bond.resname))
369
391
 
370
392
  # Write Angle Coefficients
371
393
  angle_types = sorted_attributes.get("angles", [])
@@ -373,9 +395,13 @@ def write_lammps_coefficients(
373
395
  fout.write("\n")
374
396
  fout.write("Angle Coeffs\n\n")
375
397
 
398
+ angle_remap = _renumber_bonded_types(system.angles.types()) if len(system.angles) else {}
399
+ written_angles = set()
376
400
  for angle in angle_types:
377
- if angle.id not in np.array(system.angles.types(), dtype=int):
401
+ if int(angle.id) not in angle_remap or angle.id in written_angles:
378
402
  continue
403
+ written_angles.add(angle.id)
404
+ idx = angle_remap[int(angle.id)]
379
405
 
380
406
  kr = angle.kr if angle.kr is not None else 0
381
407
  theta0 = angle.theta0 if angle.theta0 is not None else 0
@@ -383,7 +409,7 @@ def write_lammps_coefficients(
383
409
  atype = "{}-{}-{}".format(*angle.symbols)
384
410
 
385
411
  fout.write("{:>5} {:>10.6f} {:>10.6f} # {:<8} | {}\n".format(
386
- angle.id, kr, theta0, atype, angle.resname))
412
+ idx, kr, theta0, atype, angle.resname))
387
413
 
388
414
  # Write Dihedral Coefficients
389
415
  dihedral_types = sorted_attributes.get("dihedrals", [])
@@ -391,26 +417,33 @@ def write_lammps_coefficients(
391
417
  fout.write("\n")
392
418
  fout.write("Dihedral Coeffs\n\n")
393
419
 
420
+ dihedral_remap = _renumber_bonded_types(system.dihedrals.types()) if len(system.dihedrals) else {}
421
+ written_dihedrals = set()
394
422
  for dihedral in dihedral_types:
395
- if dihedral.id not in np.array(system.dihedrals.types(), dtype=int):
423
+ if int(dihedral.id) not in dihedral_remap or dihedral.id in written_dihedrals:
396
424
  continue
425
+ written_dihedrals.add(dihedral.id)
397
426
 
398
- dihedral.write(fout)
427
+ dihedral.write(fout, idx=dihedral_remap[int(dihedral.id)])
399
428
 
400
429
  # Write Improper Coefficients
401
430
  improper_types = sorted_attributes.get("impropers", [])
402
431
  if improper_types and system.impropers.types():
403
432
  fout.write("\n")
404
433
  fout.write("Improper Coeffs\n\n")
405
-
434
+
435
+ improper_remap = _renumber_bonded_types(system.impropers.types()) if len(system.impropers) else {}
436
+ written_impropers = set()
406
437
  for improper in improper_types:
407
- if improper.id not in np.array(system.impropers.types(), dtype=int):
438
+ if int(improper.id) not in improper_remap or improper.id in written_impropers:
408
439
  continue
440
+ written_impropers.add(improper.id)
441
+ idx = improper_remap[int(improper.id)]
409
442
 
410
- atype = "{}".format(*improper.symbols)
443
+ atype = "{}-{}-{}-{}".format(*improper.symbols)
411
444
  value = "{:>7.4f} {:>2d} {:>2d}".format(*improper.values)
412
445
 
413
- fout.write("{:>5} {} # {:<2} | {}\n".format(improper.id, value, atype, improper.resname))
446
+ fout.write("{:>5} {} # {:<2} | {}\n".format(idx, value, atype, improper.resname))
414
447
 
415
448
  fout.write("\n")
416
449
 
@@ -22,7 +22,6 @@ import numpy as np
22
22
 
23
23
  import shutil
24
24
  import warnings
25
- warnings.filterwarnings('ignore')
26
25
 
27
26
  #%%
28
27
 
@@ -56,7 +55,14 @@ class SimulationBox():
56
55
  def __init__(self, solvent: Optional[Any] = None, solute: Optional[Union[Any, List[Any]]] = None,
57
56
  interface: Optional[Any] = None, enderface: Optional[Any] = None,
58
57
  miderface: Optional[Any] = None) -> None:
59
-
58
+
59
+ warnings.warn(
60
+ "SimulationBox is deprecated and will be removed in a future version. "
61
+ "Use SimCell instead.",
62
+ DeprecationWarning,
63
+ stacklevel=2,
64
+ )
65
+
60
66
  # start species
61
67
  self._setup_species(solvent, solute, interface, enderface, miderface)
62
68
 
@@ -367,7 +373,7 @@ class SimulationBox():
367
373
  ion_pos = layer["ion_pos"]
368
374
 
369
375
  # make solvent box
370
- solvent = make_solvent_box(self.species, self.solvent, self._solute,
376
+ solvent = make_solvent_box(self.species, self._solvent, self._solute,
371
377
  [xsize, ysize, zsize], solv_rho,
372
378
  nions, concentration, conmodel, ion_pos, nsolvent)
373
379
 
@@ -140,7 +140,7 @@ def map_dihedrals(dihedrals):
140
140
  def map_impropers(impropers):
141
141
 
142
142
  if impropers is None:
143
- return None, None
143
+ return [], {}, np.array([])
144
144
 
145
145
  improper_type_ids = []
146
146
  type_id = 0
@@ -267,49 +267,39 @@ def generate_missing_interactions(nas, interaction_type):
267
267
  continue
268
268
 
269
269
  for mss_interaction in mss_interactions:
270
- found_interaction = False
271
270
  stypes = tuple(nas._smap[ii] for ii in mss_interaction)
272
271
  stypes_rev = stypes[::-1]
273
-
272
+
274
273
  # topology attribute already existing in map
275
274
  if stypes in interaction_type_map or stypes_rev in interaction_type_map:
276
- if found_interaction:
277
- raise ValueError(f"More than one possible {interaction_type[:-1]} found, abort!")
278
-
279
275
  if stypes in interaction_type_map:
280
276
  ninteraction = interaction_type_map[stypes].copy()
281
-
282
277
  else:
283
278
  ninteraction = interaction_type_map[stypes_rev].copy()
284
279
  mss_interaction = mss_interaction[::-1]
285
-
280
+
286
281
  a1, a2, *rest = mss_interaction
287
-
282
+
288
283
  ninteraction._a1 = a1
289
284
  ninteraction._a2 = a2
290
-
285
+
291
286
  if num_atoms > 2:
292
287
  ninteraction._a3 = rest[0]
293
288
  if num_atoms > 3:
294
289
  ninteraction._a4 = rest[1]
295
290
  new_interactions.append(ninteraction)
296
- found_interaction = True
297
-
298
- # new topology attribute
291
+
292
+ # new topology attribute — type not yet defined, create bare object
299
293
  else:
300
- if found_interaction:
301
- raise ValueError(f"More than one possible {interaction_type[:-1]} found, abort!")
302
-
303
294
  a1, a2, *rest = mss_interaction
304
-
295
+
305
296
  if num_atoms == 2:
306
297
  ninteraction = Bond(a1, a2)
307
298
  elif num_atoms == 3:
308
299
  ninteraction = Angle(a1, a2, rest[0])
309
300
  elif num_atoms == 4:
310
301
  ninteraction = Dihedral(a1, a2, rest[0], rest[1])
311
-
302
+
312
303
  new_interactions.append(ninteraction)
313
- found_interaction = True
314
304
 
315
305
  return new_interactions
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdinterface
3
- Version: 1.5.2
3
+ Version: 1.5.4
4
4
  Summary: Build Interface Systems for Molecular Dynamics Simulations
5
5
  Author-email: Fabrice Roncoroni <fabrice.roncoroni@gmail.com>
6
6
  License-Expression: Apache-2.0
7
- Project-URL: Homepage, https://gitlab.com/roncofaber/mdinterface
8
- Project-URL: Repository, https://gitlab.com/roncofaber/mdinterface.git
9
- Project-URL: Documentation, https://gitlab.com/roncofaber/mdinterface
10
- Project-URL: Bug Tracker, https://gitlab.com/roncofaber/mdinterface/-/issues
7
+ Project-URL: Homepage, https://github.com/roncofaber/mdinterface
8
+ Project-URL: Repository, https://github.com/roncofaber/mdinterface.git
9
+ Project-URL: Documentation, https://roncofaber.github.io/mdinterface
10
+ Project-URL: Bug Tracker, https://github.com/roncofaber/mdinterface/issues
11
11
  Keywords: molecular dynamics,simulation,interface,chemistry,materials science
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Science/Research
@@ -41,12 +41,10 @@ Requires-Dist: pymbxas; extra == "all"
41
41
  Requires-Dist: fairchem-core; extra == "all"
42
42
  Dynamic: license-file
43
43
 
44
- <div style="display: flex; align-items: center;">
45
- <img src="./assets/mdinterface.png" alt="Logo" width="80" style="margin-right: 10px;">
46
- <div style="display: flex; flex-direction: column;">
47
- <h1 style="margin: 0;">mdinterface: Build Interface Systems for Molecular Dynamics Simulations</h1>
48
- </div>
49
- </div>
44
+ <h1>
45
+ <img src="./assets/mdinterface.png" alt="Logo" width="60" style="vertical-align: middle; margin-right: 10px;">
46
+ mdinterface: Build Interface Systems for Molecular Dynamics Simulations
47
+ </h1>
50
48
 
51
49
  [![PyPI version](https://badge.fury.io/py/mdinterface.svg?icon=si%3Apython)](https://pypi.org/project/mdinterface/) [![GitHub version](https://badge.fury.io/gh/roncofaber%2Fmdinterface.svg?icon=si%3Agithub)](https://github.com/roncofaber/mdinterface) [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://roncofaber.github.io/mdinterface)
52
50
 
@@ -83,9 +83,11 @@ mdinterface/utils/map.py
83
83
  mdinterface/utils/poisson.py
84
84
  mdinterface/utils/rings.py
85
85
  mdinterface/utils/units.py
86
+ scripts/extract_changelog_summary.py
86
87
  tests/__init__.py
87
88
  tests/test_auxiliary.py
88
89
  tests/test_builder.py
90
+ tests/test_changelog_summary.py
89
91
  tests/test_database.py
90
92
  tests/test_solvent.py
91
93
  tests/test_specie.py
@@ -3,4 +3,5 @@ dist
3
3
  docs
4
4
  examples
5
5
  mdinterface
6
+ scripts
6
7
  tests
@@ -38,10 +38,10 @@ dependencies = [
38
38
  ]
39
39
 
40
40
  [project.urls]
41
- Homepage = "https://gitlab.com/roncofaber/mdinterface"
42
- Repository = "https://gitlab.com/roncofaber/mdinterface.git"
43
- Documentation = "https://gitlab.com/roncofaber/mdinterface"
44
- "Bug Tracker" = "https://gitlab.com/roncofaber/mdinterface/-/issues"
41
+ Homepage = "https://github.com/roncofaber/mdinterface"
42
+ Repository = "https://github.com/roncofaber/mdinterface.git"
43
+ Documentation = "https://roncofaber.github.io/mdinterface"
44
+ "Bug Tracker" = "https://github.com/roncofaber/mdinterface/issues"
45
45
 
46
46
  [project.optional-dependencies]
47
47
  resp = [
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+ """Extract a version's summary paragraph from CHANGELOG.md.
3
+
4
+ Usage: python scripts/extract_changelog_summary.py VERSION
5
+
6
+ Prints the summary paragraph to stdout and exits 0 on success. Exits 1 with
7
+ an error on stderr if the version's changelog header, or its summary
8
+ paragraph, is missing.
9
+ """
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def extract_summary(changelog_text: str, version: str) -> str:
15
+ """Return the summary paragraph for `## [version]` in changelog_text.
16
+
17
+ The summary is every non-blank line immediately following the header
18
+ (after skipping any blank lines right after it), stopping at the next
19
+ blank line or the next '## '/'### ' header — i.e. exactly one paragraph.
20
+ """
21
+ header = f"## [{version}]"
22
+ lines = changelog_text.splitlines()
23
+
24
+ header_idx = None
25
+ for i, line in enumerate(lines):
26
+ if line.startswith(header):
27
+ header_idx = i
28
+ break
29
+
30
+ if header_idx is None:
31
+ raise ValueError(
32
+ f"No changelog header found for version {version!r} "
33
+ f"(expected a line starting with {header!r})"
34
+ )
35
+
36
+ i = header_idx + 1
37
+ while i < len(lines) and lines[i].strip() == "":
38
+ i += 1
39
+
40
+ summary_lines = []
41
+ while i < len(lines):
42
+ line = lines[i]
43
+ if line.strip() == "" or line.startswith("## ") or line.startswith("### "):
44
+ break
45
+ summary_lines.append(line)
46
+ i += 1
47
+
48
+ summary = "\n".join(summary_lines).strip()
49
+ if not summary:
50
+ raise ValueError(
51
+ f"No summary paragraph found under {header!r} in CHANGELOG.md - "
52
+ "add one before the first '### ' group"
53
+ )
54
+
55
+ return summary
56
+
57
+
58
+ def main(argv):
59
+ if len(argv) != 2:
60
+ print("Usage: extract_changelog_summary.py VERSION", file=sys.stderr)
61
+ return 1
62
+
63
+ version = argv[1]
64
+ changelog_path = Path(__file__).resolve().parent.parent / "CHANGELOG.md"
65
+ changelog_text = changelog_path.read_text()
66
+
67
+ try:
68
+ summary = extract_summary(changelog_text, version)
69
+ except ValueError as exc:
70
+ print(f"ERROR: {exc}", file=sys.stderr)
71
+ return 1
72
+
73
+ print(summary)
74
+ return 0
75
+
76
+
77
+ if __name__ == "__main__":
78
+ sys.exit(main(sys.argv))
@@ -0,0 +1,91 @@
1
+ import importlib.util
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+
6
+ _SCRIPT_PATH = Path(__file__).resolve().parent.parent / "scripts" / "extract_changelog_summary.py"
7
+ _spec = importlib.util.spec_from_file_location("extract_changelog_summary", _SCRIPT_PATH)
8
+ _module = importlib.util.module_from_spec(_spec)
9
+ _spec.loader.exec_module(_module)
10
+ extract_summary = _module.extract_summary
11
+
12
+
13
+ def test_extract_summary_found():
14
+ changelog = """# Changelog
15
+
16
+ ## [Unreleased]
17
+
18
+ ---
19
+
20
+ ## [1.5.4] - 2026-08-11
21
+
22
+ Fixes a silent LAMMPS output corruption bug.
23
+
24
+ ### Fixed
25
+ - Something
26
+ """
27
+ assert extract_summary(changelog, "1.5.4") == "Fixes a silent LAMMPS output corruption bug."
28
+
29
+
30
+ def test_extract_summary_multiline():
31
+ changelog = """## [1.5.4] - 2026-08-11
32
+
33
+ Fixes a silent LAMMPS output corruption bug affecting systems that reuse
34
+ the same Specie template across multiple layers.
35
+
36
+ ### Fixed
37
+ - Something
38
+ """
39
+ expected = (
40
+ "Fixes a silent LAMMPS output corruption bug affecting systems that reuse\n"
41
+ "the same Specie template across multiple layers."
42
+ )
43
+ assert extract_summary(changelog, "1.5.4") == expected
44
+
45
+
46
+ def test_missing_version_header_raises():
47
+ changelog = """## [1.5.3] - 2026-07-09
48
+
49
+ Some summary.
50
+
51
+ ### Fixed
52
+ - Something
53
+ """
54
+ with pytest.raises(ValueError, match="No changelog header found"):
55
+ extract_summary(changelog, "1.5.4")
56
+
57
+
58
+ def test_missing_summary_paragraph_raises():
59
+ changelog = """## [1.5.4] - 2026-08-11
60
+
61
+ ### Fixed
62
+ - Something
63
+ """
64
+ with pytest.raises(ValueError, match="No summary paragraph found"):
65
+ extract_summary(changelog, "1.5.4")
66
+
67
+
68
+ def test_second_paragraph_not_included():
69
+ changelog = """## [1.5.4] - 2026-08-11
70
+
71
+ First paragraph only.
72
+
73
+ Second paragraph should not be included.
74
+
75
+ ### Fixed
76
+ - Something
77
+ """
78
+ assert extract_summary(changelog, "1.5.4") == "First paragraph only."
79
+
80
+
81
+ def test_header_prefix_does_not_match_longer_version():
82
+ # "## [1.5.4]" must not match when looking for "1.5" or "1.5.40"
83
+ changelog = """## [1.5.40] - 2026-08-11
84
+
85
+ Wrong version's summary.
86
+
87
+ ### Fixed
88
+ - Something
89
+ """
90
+ with pytest.raises(ValueError, match="No changelog header found"):
91
+ extract_summary(changelog, "1.5.4")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes