Skip to content

Commit 4375e34

Browse files
authored
Add Structure.get_symmetry_dataset convenience method (#4268)
* add Structure.get_moyo_dataset -> moyopy.MoyoDataset convenience method * Add Structure.get_symmetry_dataset() method with multiple backend support, currently moyopy and spglib - remove get_moyo_dataset method - implement method overloading for type hints - update tests to cover both backends and error cases * add @due.dcite decorator for citation tracking with DOI for Spglib - also include citation details in method doc string * fix duecredit import from pymatgen.util.due module - update pyproject.toml to install symmetry optional deps in CI * fix ImportError on ase: Try installing dependencies with `pip install moyopy[interface]`
1 parent ed80c87 commit 4375e34

File tree

3 files changed

+90
-3
lines changed

3 files changed

+90
-3
lines changed

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ dependencies = [
6868
# scipy<1.14.1 is incompatible with NumPy 2.0 on Windows
6969
# https://github.com/scipy/scipy/issues/21052
7070
"scipy>=1.14.1; platform_system == 'Windows'",
71-
"spglib>=2.5.0",
71+
"spglib>=2.5",
7272
"sympy>=1.3", # PR #4116
7373
"tabulate>=0.9",
7474
"tqdm>=4.60",
@@ -89,7 +89,7 @@ Pypi = "https://pypi.org/project/pymatgen"
8989
[project.optional-dependencies]
9090
abinit = ["netcdf4>=1.7.2"]
9191
ase = ["ase>=3.23.0"]
92-
ci = ["pytest-cov>=4", "pytest-split>=0.8", "pytest>=8"]
92+
ci = ["pytest-cov>=4", "pytest-split>=0.8", "pytest>=8", "pymatgen[symmetry]"]
9393
docs = ["invoke", "sphinx", "sphinx_markdown_builder", "sphinx_rtd_theme"]
9494
electronic_structure = ["fdint>=2.0.2"]
9595
mlp = ["chgnet>=0.3.8", "matgl>=1.1.3"]
@@ -110,6 +110,8 @@ optional = [
110110
"phonopy>=2.33.3",
111111
"seekpath>=2.0.1",
112112
]
113+
# moyopy[interface] includes ase
114+
symmetry = ["moyopy[interface]>=0.3", "spglib>=2.5"]
113115
# tblite only support Python 3.12+ through conda-forge
114116
# https://github.com/tblite/tblite/issues/175
115117
tblite = [ "tblite[ase]>=0.3.0; platform_system=='Linux' and python_version<'3.12'"]

src/pymatgen/core/structure.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from abc import ABC, abstractmethod
2121
from collections import defaultdict
2222
from fnmatch import fnmatch
23-
from typing import TYPE_CHECKING, Literal, cast, get_args
23+
from typing import TYPE_CHECKING, Literal, cast, get_args, overload
2424

2525
import numpy as np
2626
from monty.dev import deprecated
@@ -43,12 +43,15 @@
4343
from pymatgen.electronic_structure.core import Magmom
4444
from pymatgen.symmetry.maggroups import MagneticSpaceGroup
4545
from pymatgen.util.coord import all_distances, get_angle, lattice_points_in_supercell
46+
from pymatgen.util.due import Doi, due
4647

4748
if TYPE_CHECKING:
4849
from collections.abc import Callable, Iterable, Iterator, Sequence
4950
from typing import Any, ClassVar, SupportsIndex, TypeAlias
5051

52+
import moyopy
5153
import pandas as pd
54+
import spglib
5255
from ase import Atoms
5356
from ase.calculators.calculator import Calculator
5457
from ase.io.trajectory import Trajectory
@@ -3338,6 +3341,61 @@ def to_conventional(self, **kwargs) -> Structure:
33383341
"""
33393342
return self.to_cell("conventional", **kwargs)
33403343

3344+
@overload
3345+
def get_symmetry_dataset(self, backend: Literal["moyopy"], **kwargs) -> moyopy.MoyoDataset: ...
3346+
3347+
@due.dcite(
3348+
Doi("10.1080/27660400.2024.2384822"),
3349+
description="Spglib: a software library for crystal symmetry search",
3350+
)
3351+
@overload
3352+
def get_symmetry_dataset(self, backend: Literal["spglib"], **kwargs) -> spglib.SpglibDataset: ...
3353+
3354+
def get_symmetry_dataset(
3355+
self, backend: Literal["moyopy", "spglib"] = "spglib", **kwargs
3356+
) -> moyopy.MoyoDataset | spglib.SpglibDataset:
3357+
"""Get a symmetry dataset from the structure using either moyopy or spglib backend.
3358+
3359+
If using the spglib backend (default), please cite:
3360+
3361+
Togo, A., Shinohara, K., & Tanaka, I. (2024). Spglib: a software library for crystal
3362+
symmetry search. Science and Technology of Advanced Materials: Methods, 4(1), 2384822-2384836.
3363+
https://doi.org/10.1080/27660400.2024.2384822
3364+
3365+
Args:
3366+
backend ("moyopy" | "spglib"): Which symmetry analysis backend to use.
3367+
Defaults to "spglib".
3368+
**kwargs: Additional arguments passed to the respective backend's constructor.
3369+
For spglib, these are passed to SpacegroupAnalyzer (e.g. symprec, angle_tolerance).
3370+
For moyopy, these are passed to MoyoDataset constructor.
3371+
3372+
Returns:
3373+
MoyoDataset | SpglibDataset: Symmetry dataset from the chosen backend.
3374+
3375+
Raises:
3376+
ImportError: If the requested backend is not installed.
3377+
ValueError: If an invalid backend is specified.
3378+
"""
3379+
if backend == "moyopy":
3380+
try:
3381+
import moyopy
3382+
import moyopy.interface
3383+
except ImportError:
3384+
raise ImportError("moyopy is not installed. Run pip install moyopy.")
3385+
3386+
# Convert structure to MoyoDataset format
3387+
moyo_cell = moyopy.interface.MoyoAdapter.from_structure(self)
3388+
return moyopy.MoyoDataset(cell=moyo_cell, **kwargs)
3389+
3390+
if backend == "spglib":
3391+
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
3392+
3393+
sga = SpacegroupAnalyzer(self, **kwargs)
3394+
return sga.get_symmetry_dataset()
3395+
3396+
valid_backends = ("moyopy", "spglib")
3397+
raise ValueError(f"Invalid {backend=}, must be one of {valid_backends}")
3398+
33413399

33423400
class IMolecule(SiteCollection, MSONable):
33433401
"""Basic immutable Molecule object without periodicity. Essentially a

tests/core/test_structure.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from fractions import Fraction
77
from pathlib import Path
88
from shutil import which
9+
from unittest import mock
910

1011
import numpy as np
1112
import pytest
@@ -965,6 +966,32 @@ def test_sites_setter(self):
965966
struct.sites = new_sites
966967
assert struct.sites == new_sites
967968

969+
def test_get_symmetry_dataset(self):
970+
"""Test getting symmetry dataset from structure using different backends."""
971+
# Test spglib backend
972+
dataset = self.struct.get_symmetry_dataset(backend="spglib")
973+
assert dataset.number == 227 # Fd-3m space group
974+
assert dataset.international == "Fd-3m"
975+
assert len(dataset.rotations) > 0
976+
assert len(dataset.translations) > 0
977+
978+
# Test moyopy backend if available
979+
moyopy = pytest.importorskip("moyopy")
980+
dataset = self.struct.get_symmetry_dataset(backend="moyopy")
981+
assert isinstance(dataset, moyopy.MoyoDataset)
982+
assert dataset.prim_std_cell.numbers == [14, 14] # Si atomic number is 14
983+
984+
# Test import error
985+
with (
986+
mock.patch.dict("sys.modules", {"moyopy": None}),
987+
pytest.raises(ImportError, match="moyopy is not installed. Run pip install moyopy."),
988+
):
989+
self.struct.get_symmetry_dataset(backend="moyopy")
990+
991+
# Test invalid backend
992+
with pytest.raises(ValueError, match="Invalid backend='42'"):
993+
self.struct.get_symmetry_dataset(backend="42")
994+
968995

969996
class TestStructure(PymatgenTest):
970997
def setUp(self):

0 commit comments

Comments
 (0)