Skip to content

Commit 7cdd288

Browse files
wolearycpre-commit-ci[bot]Andrew-S-Rosen
authored
Bugfix: Structure/ase.Atoms interconversion side effects (#4297)
* deepcopy atoms.info/structure.properties during ase/pymatgen conversion this fixes bug where atoms/structure objects were themselves modified when converted * added tests to check spacegroup conversions * pre-commit auto-fixes * selective import for copy.deepcopy in keeping with style of ase.py * pre-commit auto-fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Andrew S. Rosen <[email protected]>
1 parent b5d03d7 commit 7cdd288

File tree

2 files changed

+38
-5
lines changed

2 files changed

+38
-5
lines changed

src/pymatgen/io/ase.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations
77

88
import warnings
9+
from copy import deepcopy
910
from importlib.metadata import PackageNotFoundError
1011
from typing import TYPE_CHECKING
1112

@@ -204,7 +205,7 @@ def get_atoms(structure: SiteCollection, msonable: bool = True, **kwargs) -> MSO
204205

205206
# Atoms.info <---> Structure.properties
206207
if properties := structure.properties:
207-
atoms.info = properties
208+
atoms.info = deepcopy(properties)
208209

209210
# Regenerate Spacegroup object from `.todict()` representation
210211
if isinstance(atoms.info.get("spacegroup"), dict):
@@ -298,10 +299,10 @@ def get_structure(atoms: Atoms, cls: type[Structure] = Structure, **cls_kwargs)
298299
sel_dyn = None
299300

300301
# Atoms.info <---> Structure.properties
301-
# But first make sure `spacegroup` is JSON serializable
302-
if atoms.info.get("spacegroup") and isinstance(atoms.info["spacegroup"], Spacegroup):
303-
atoms.info["spacegroup"] = atoms.info["spacegroup"].todict()
304-
properties = getattr(atoms, "info", {})
302+
properties = deepcopy(getattr(atoms, "info", {}))
303+
# If present, convert Spacegroup object to JSON-serializable dict
304+
if properties.get("spacegroup") and isinstance(properties["spacegroup"], Spacegroup):
305+
properties["spacegroup"] = properties["spacegroup"].todict()
305306

306307
# Return a Molecule object if that was specifically requested;
307308
# otherwise return a Structure object as expected

tests/io/test_ase.py

+32
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,21 @@ def test_get_atoms_from_structure_dyn():
105105
assert len(c.index) == len([mask for mask in sel_dyn if np.array_equal(mask, ~np.array(ase_mask))])
106106

107107

108+
def test_get_atoms_from_structure_spacegroup():
109+
# Get structure with space group dictionary in properties
110+
space_group_info = STRUCTURE.get_space_group_info()
111+
STRUCTURE.properties["spacegroup"] = {"number": space_group_info[1], "setting": 1}
112+
113+
# Convert to atoms and check that structure was not modified
114+
atoms = AseAtomsAdaptor.get_atoms(STRUCTURE)
115+
assert isinstance(STRUCTURE.properties["spacegroup"], dict)
116+
assert isinstance(atoms.info["spacegroup"], ase.spacegroup.Spacegroup)
117+
118+
# Check that space group matches
119+
assert atoms.info["spacegroup"].no == STRUCTURE.properties["spacegroup"]["number"]
120+
assert atoms.info["spacegroup"].setting == STRUCTURE.properties["spacegroup"]["setting"]
121+
122+
108123
def test_get_atoms_from_molecule():
109124
mol = Molecule.from_file(XYZ_STRUCTURE)
110125
atoms = AseAtomsAdaptor.get_atoms(mol)
@@ -217,6 +232,23 @@ def test_get_structure_dyn(select_dyn):
217232
assert len(ase_atoms) == len(structure)
218233

219234

235+
def test_get_structure_spacegroup():
236+
# set up Atoms object with spacegroup information
237+
a = 4.05
238+
atoms = ase.spacegroup.crystal("Al", [(0, 0, 0)], spacegroup=225, cellpar=[a, a, a, 90, 90, 90])
239+
assert "spacegroup" in atoms.info
240+
assert isinstance(atoms.info["spacegroup"], ase.spacegroup.Spacegroup)
241+
242+
# Test that get_structure does not mutate atoms
243+
structure = AseAtomsAdaptor.get_structure(atoms)
244+
assert isinstance(atoms.info["spacegroup"], ase.spacegroup.Spacegroup)
245+
assert isinstance(structure.properties["spacegroup"], dict)
246+
247+
# Test that spacegroup info matches
248+
assert atoms.info["spacegroup"].no == structure.properties["spacegroup"]["number"]
249+
assert atoms.info["spacegroup"].setting == structure.properties["spacegroup"]["setting"]
250+
251+
220252
def test_get_molecule():
221253
atoms = ase.io.read(XYZ_STRUCTURE)
222254
molecule = AseAtomsAdaptor.get_molecule(atoms)

0 commit comments

Comments
 (0)