Skip to content

Commit 69c3f76

Browse files
improve pack (#75)
* remove unused array keys * allow building rectangular boxes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * review * nitpick comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove comment --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 338f4b3 commit 69c3f76

File tree

2 files changed

+63
-11
lines changed

2 files changed

+63
-11
lines changed

rdkit2ase/pack.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,23 @@ def _run_packmol(
7474
with open(tmpdir / "pack.jl", "w") as f:
7575
f.write("using Packmol \n")
7676
f.write(f'run_packmol("{input_file.name}") \n')
77-
command = f"julia {tmpdir / 'pack.jl'}"
77+
78+
if packmol_executable == "packmol.jl":
79+
subprocess.run(
80+
["julia", str(tmpdir / "pack.jl")],
81+
cwd=tmpdir,
82+
check=True,
83+
capture_output=not verbose,
84+
)
7885
else:
79-
command = f"{packmol_executable} < {input_file.name}"
80-
81-
subprocess.run(
82-
command,
83-
cwd=tmpdir,
84-
shell=True,
85-
check=True,
86-
capture_output=not verbose,
87-
)
86+
with open(input_file, "rb") as fin:
87+
subprocess.run(
88+
[packmol_executable],
89+
cwd=tmpdir,
90+
check=True,
91+
capture_output=not verbose,
92+
stdin=fin,
93+
)
8894

8995

9096
def _write_molecule_files(
@@ -159,6 +165,7 @@ def pack(
159165
packmol: str = "packmol",
160166
pbc: bool = True,
161167
output_format: FORMAT = "pdb",
168+
ratio: tuple[float, float, float] = (1.0, 1.0, 1.0),
162169
) -> ase.Atoms:
163170
"""
164171
Packs the given molecules into a box with the specified density using PACKMOL.
@@ -179,13 +186,16 @@ def pack(
179186
If True, enables logging of the packing process, by default False.
180187
packmol : str, optional
181188
The path to the packmol executable, by default "packmol".
182-
When installing packmol via jula, use "packmol.jl".
189+
When installing Packmol via Julia, use "packmol.jl".
183190
pbc : bool, optional
184191
Ensure tolerance across periodic boundaries, by default True.
185192
output_format : str, optional
186193
The file format used for communication with packmol, by default "pdb".
187194
WARNING: Do not use "xyz". This might cause issues and
188195
is only implemented for debugging purposes.
196+
ratio : tuple[float, float, float], optional
197+
Box aspect ratio (a:b:c). Must be three positive, finite numbers.
198+
Defaults to (1.0, 1.0, 1.0) for a cubic box.
189199
190200
Returns
191201
-------
@@ -204,6 +214,13 @@ def pack(
204214
"""
205215
selected_images = _select_conformers(data, counts, seed)
206216
cell = calculate_box_dimensions(images=selected_images, density=density)
217+
218+
# Adjust cell dimensions according to ratio while keeping volume unchanged
219+
original_volume = np.prod(cell)
220+
ratio_product = np.prod(ratio)
221+
scale = original_volume ** (1 / 3) / (ratio_product ** (1 / 3))
222+
cell = [float(scale * r) for r in ratio]
223+
207224
packmol_input = _generate_packmol_input(
208225
selected_images, cell, tolerance, seed, output_format, pbc
209226
)
@@ -219,6 +236,11 @@ def pack(
219236
_run_packmol(packmol, tmpdir / "pack.inp", tmpdir, verbose)
220237
try:
221238
packed_atoms: ase.Atoms = ase.io.read(tmpdir / f"mixture.{output_format}")
239+
packed_atoms.arrays.pop("atomtypes", None)
240+
packed_atoms.arrays.pop("bfactor", None)
241+
packed_atoms.arrays.pop("occupancy", None)
242+
packed_atoms.arrays.pop("residuenames", None)
243+
packed_atoms.arrays.pop("residuenumbers", None)
222244
except FileNotFoundError as e:
223245
log.error("Packmol Input:\n%s", packmol_input)
224246
if packmol == "packmol.jl":

tests/test_solvate.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@
66
from rdkit2ase import ase2rdkit, pack, smiles2conformers
77

88

9+
@pytest.mark.parametrize("packmol", ["packmol", "packmol.jl"])
10+
def test_pack_clean_info_arrays(packmol):
11+
water = smiles2conformers("O", 1)
12+
atoms = pack([water], [10], 997, 42, tolerance=1.5, packmol=packmol)
13+
# we don't want to have unused info in the atoms object.
14+
assert "atomtypes" not in atoms.arrays
15+
assert "bfactor" not in atoms.arrays
16+
assert "occupancy" not in atoms.arrays
17+
assert "residuenames" not in atoms.arrays
18+
assert "residuenumbers" not in atoms.arrays
19+
20+
921
@pytest.mark.parametrize("packmol", ["packmol", "packmol.jl"])
1022
def test_pack_pbc(packmol):
1123
water = smiles2conformers("O", 1)
@@ -107,3 +119,21 @@ def test_pack_charges(packmol):
107119
# check charges
108120
charges = [atom.GetFormalCharge() for atom in mol.GetAtoms()]
109121
assert charges == [0, 0, 0, -1, 0, 1] + [1, 0, 0, -1, 0, 0, 0, 0, 0, 0]
122+
123+
124+
@pytest.mark.parametrize("packmol", ["packmol", "packmol.jl"])
125+
def test_pack_ratio(packmol):
126+
water = smiles2conformers("O", 1)
127+
# pack a cubic box
128+
box = pack([water], [10], 1000, 42, tolerance=1.5, packmol=packmol)
129+
assert box.cell[0, 0] == pytest.approx(box.cell[1, 1])
130+
assert box.cell[1, 1] == pytest.approx(box.cell[2, 2])
131+
assert box.get_volume() == pytest.approx(300, rel=1e-2)
132+
# npt.assert_allclose(box.get_positions(), box.get_positions(wrap=True))
133+
134+
# pack a rectangular box
135+
box = pack([water], [10], 997, 42, ratio=(1, 2, 3), tolerance=1.5, packmol=packmol)
136+
assert box.cell[0, 0] * 2 == pytest.approx(box.cell[1, 1])
137+
assert box.cell[0, 0] * 3 == pytest.approx(box.cell[2, 2])
138+
assert box.get_volume() == pytest.approx(300, rel=1e-2)
139+
# npt.assert_allclose(box.get_positions(), box.get_positions(wrap=True))

0 commit comments

Comments
 (0)