Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SpaceGroup changes #3859

Merged
merged 25 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ebc35f0
Replaced SpaceGroup symbol attribute with its Hermann-Mauguin symbol,…
kaueltzen Jun 4, 2024
fe7363d
Removed crystal class key from symm_ops.json.
kaueltzen Jun 4, 2024
ebe1246
Merge branch 'materialsproject:master' into space_group
kaueltzen Jun 4, 2024
561dfef
Test for correct setting of hexagonal attribute when instantiating fr…
kaueltzen Jun 5, 2024
25f90e7
Noted change and replacement option for SpaceGroup symbol in compatib…
kaueltzen Jun 5, 2024
d0c50ba
Added from_space_group class method to PointGroup, added tests
kaueltzen Jun 6, 2024
10b5a14
Added mapping to standard setting in PointGroup.from_space_group(), m…
kaueltzen Jun 6, 2024
0eea0bd
Merge branch 'master' into space_group
kaueltzen Jun 6, 2024
c008de9
Updated core/test_surface.py to assign lattice as in SpaceGroup is_co…
kaueltzen Jun 6, 2024
0b1dd3c
Modified databases and SpaceGroup init to ensure compatibility with n…
kaueltzen Jun 6, 2024
39d3683
Added tests for issue #3862, modified full_symbol and point_group att…
kaueltzen Jun 6, 2024
b6d6c25
Modified PointGroup.from_space_group() to also handle symbols with id…
kaueltzen Jun 7, 2024
6a089c7
Added test for warning if SpaceGroup.full_symbol is not available.
kaueltzen Jun 7, 2024
ccc4cc8
Removed warning test.
kaueltzen Jun 7, 2024
828cf5d
Merge branch 'materialsproject:master' into space_group
kaueltzen Jun 10, 2024
dd60f90
tweak incompat warning
janosh Jun 10, 2024
1d744b9
add test_full_symbol_warning
janosh Jun 10, 2024
15acd36
add author + date to dev_scripts/update_spacegroup_data.py
janosh Jun 10, 2024
6439fed
typos
janosh Jun 10, 2024
a33cc3c
warning occurs only once, move test_full_symbol_warning up as workaro…
janosh Jun 10, 2024
81f76f4
Updated compatibility.md to also handle old symbol replacement of P2_…
kaueltzen Jun 12, 2024
26e7728
Merge branch 'master' into space_group
kaueltzen Jul 5, 2024
56416f2
pre-commit auto-fixes
pre-commit-ci[bot] Jul 5, 2024
21777c7
Updated dev script path to new src layout.
kaueltzen Jul 5, 2024
adcff10
Merge branch 'master' into space_group
JaGeo Jul 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions dev_scripts/update_spacegroup_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Script to update symm_ops.json and symm_data.yaml in symmetry module due to issues #3845 and #3862.
symm_ops.json:
- adds Hermann_mauguin point group key and short Hermann Mauguin space group symbol
- converts screw axis notation to symm_data standard
symm_data.json
- removes mapping of rhombohedral space group types onto symbol + appended H
- replaces I/P2_12_121 key with I/P2_12_12_1
"""

from __future__ import annotations

import sys

from monty.serialization import dumpfn, loadfn
from pymatgen.symmetry.groups import PointGroup

__author__ = "Katharina Ueltzen @kaueltzen"
__date__ = "2024-06-06"

SYMM_OPS = loadfn("../src/pymatgen/symmetry/symm_ops.json")
SYMM_DATA = loadfn("../src/pymatgen/symmetry/symm_data.json")


def convert_symmops_to_sg_encoding(symbol: str) -> str:
"""
Utility function to convert SYMMOPS space group type symbol notation
into SYMM_DATA["space_group_encoding"] key notation with underscores before
translational part of screw axes.
Args:
symbol (str): "hermann_mauguin" or "universal_h_m" key of symmops.json
Returns:
symbol in the format of SYMM_DATA["space_group_encoding"] keys
"""
symbol_representation = symbol.split(":")
representation = ":" + "".join(symbol_representation[1].split(" ")) if len(symbol_representation) > 1 else ""

blickrichtungen = symbol_representation[0].split(" ")
blickrichtungen_new = []
for br in blickrichtungen:
if len(br) > 1 and br[0].isdigit() and br[1].isdigit():
blickrichtungen_new.append(br[0] + "_" + br[1:])
else:
blickrichtungen_new.append(br)
return "".join(blickrichtungen_new) + representation


def remove_identity_from_full_hermann_mauguin(symbol: str) -> str:
"""
Utility function to remove identity along blickrichtung (except in P1).
Args:
symbol (str): "hermann_mauguin" key of symmops.json
Returns:
short "hermann_mauguin" key
"""
if symbol in ("P 1", "C 1", "P 1 "):
return symbol
blickrichtungen = symbol.split(" ")
blickrichtungen_new = []
for br in blickrichtungen:
if br != "1":
blickrichtungen_new.append(br + " ")
return "".join(blickrichtungen_new)


new_symm_data = {}
for k, v in SYMM_DATA["space_group_encoding"].items():
if k.endswith("H"):
new_symm_data[k.removesuffix("H")] = v
elif k == "I2_12_121":
new_symm_data["I2_12_12_1"] = v
elif k == "P2_12_121":
new_symm_data["P2_12_12_1"] = v
else:
new_symm_data[k] = v

SYMM_DATA["space_group_encoding"] = new_symm_data

for spg_idx, spg in enumerate(SYMM_OPS):
if "(" in spg["hermann_mauguin"]:
SYMM_OPS[spg_idx]["hermann_mauguin"] = spg["hermann_mauguin"].split("(")[0]

short_h_m = remove_identity_from_full_hermann_mauguin(SYMM_OPS[spg_idx]["hermann_mauguin"])
SYMM_OPS[spg_idx]["short_h_m"] = convert_symmops_to_sg_encoding(short_h_m)
SYMM_OPS[spg_idx]["hermann_mauguin_u"] = convert_symmops_to_sg_encoding(spg["hermann_mauguin"])

for spg_idx, spg in enumerate(SYMM_OPS):
try:
pg = PointGroup.from_space_group(spg["short_h_m"])
except AssertionError as e:
print(spg, str(e))
sys.exit(1)
SYMM_OPS[spg_idx]["point_group"] = pg.symbol

dumpfn(SYMM_DATA, "../src/pymatgen/symmetry/symm_data.json")
dumpfn(SYMM_OPS, "../src/pymatgen/symmetry/symm_ops.json")
18 changes: 18 additions & 0 deletions docs/compatibility.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 81 additions & 7 deletions src/pymatgen/symmetry/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class PointGroup(SymmetryGroup):

def __init__(self, int_symbol: str) -> None:
"""Initialize a Point Group from its international symbol.
Please note that only the 32 crystal classes are supported right now.

Args:
int_symbol (str): International or Hermann-Mauguin Symbol.
Expand Down Expand Up @@ -165,6 +166,54 @@ def get_orbit(self, p: ArrayLike, tol: float = 1e-5) -> list[np.ndarray]:
orbit.append(pp)
return orbit

@classmethod
def from_space_group(cls, sg_symbol: str) -> PointGroup:
"""Instantiate one of the 32 crystal classes from a space group symbol in
Hermann Mauguin notation (int symbol or full symbol).

Args:
sg_symbol: space group symbol in Hermann Mauguin notation.

Raises:
AssertionError if a valid crystal class cannot be created
Returns:
crystal class in Hermann-Mauguin notation.
"""
abbrev_map = {
"2/m2/m2/m": "mmm",
"4/m2/m2/m": "4/mmm",
"-32/m": "-3m",
"6/m2/m2/m": "6/mmm",
"2/m-3": "m-3",
"4/m-32/m": "m-3m",
}
non_standard_map = {
"m2m": "mm2",
"2mm": "mm2",
"-4m2": "-42m", # technically not non-standard
"-62m": "-6m2", # technically not non-standard
}
symbol = re.sub(r" ", "", sg_symbol)

symm_ops = loadfn(os.path.join(os.path.dirname(__file__), "symm_ops.json")) # get short symbol if possible
for spg in symm_ops:
if symbol in [spg["hermann_mauguin"], spg["universal_h_m"], spg["hermann_mauguin_u"]]:
symbol = spg["short_h_m"]

assert symbol[0].isupper(), f"Invalid sg_symbol {sg_symbol}"
assert not symbol[1:].isupper(), f"Invalid sg_symbol {sg_symbol}"

symbol = symbol[1:] # Remove centering
symbol = symbol.translate(str.maketrans("abcden", "mmmmmm")) # Remove translation from glide planes
symbol = re.sub(r"_.", "", symbol) # Remove translation from screw axes
symbol = abbrev_map.get(symbol, symbol)
symbol = non_standard_map.get(symbol, symbol)

assert (
symbol in SYMM_DATA["point_group_encoding"]
), f"Could not create a valid crystal class ({symbol}) from sg_symbol {sg_symbol}"
return cls(symbol)


@cached_class
class SpaceGroup(SymmetryGroup):
Expand Down Expand Up @@ -194,7 +243,7 @@ class SpaceGroup(SymmetryGroup):
v["full_symbol"]: k for k, v in SYMM_DATA["space_group_encoding"].items()
}

def __init__(self, int_symbol: str) -> None:
def __init__(self, int_symbol: str, hexagonal: bool = True) -> None:
"""Initialize a Space Group from its full or abbreviated international
symbol. Only standard settings are supported.

Expand All @@ -209,9 +258,26 @@ def __init__(self, int_symbol: str) -> None:
possible settings for a spacegroup, use the get_settings()
classmethod. Alternative origin choices can be indicated by a
translation vector, e.g. 'Fm-3m(a-1/4,b-1/4,c-1/4)'.
hexagonal (bool): For rhombohedral groups, whether to handle as in
hexagonal setting (default) or rhombohedral setting.
If the int_symbol of a rhombohedral spacegroup is given with the
setting ("(:)H"/"(:)R"), this parameter is overwritten accordingly
(please note that the setting is not contained in the symbol
attribute anymore).
"""
from pymatgen.core.operations import SymmOp

if int_symbol.endswith("H"):
self.hexagonal = True
if not int_symbol.endswith(":H"):
int_symbol = int_symbol[:-1] + ":H"
elif int_symbol.endswith("R"):
self.hexagonal = False
if not int_symbol.endswith(":R"):
int_symbol = int_symbol[:-1] + ":R"
else:
self.hexagonal = hexagonal

int_symbol = re.sub(r" ", "", int_symbol)
if int_symbol in SpaceGroup.abbrev_sg_mapping:
int_symbol = SpaceGroup.abbrev_sg_mapping[int_symbol]
Expand All @@ -221,15 +287,22 @@ def __init__(self, int_symbol: str) -> None:
self._symmetry_ops: set[SymmOp] | None

for spg in SpaceGroup.SYMM_OPS:
if int_symbol in [spg["hermann_mauguin"], spg["universal_h_m"]]:
if int_symbol in [spg["hermann_mauguin"], spg["universal_h_m"], spg["hermann_mauguin_u"]]:
ops = [SymmOp.from_xyz_str(s) for s in spg["symops"]]
self.symbol = re.sub(r":", "", re.sub(r" ", "", spg["universal_h_m"]))
self.symbol = spg["hermann_mauguin_u"]
if int_symbol in SpaceGroup.sg_encoding:
self.full_symbol = SpaceGroup.sg_encoding[int_symbol]["full_symbol"]
self.point_group = SpaceGroup.sg_encoding[int_symbol]["point_group"]
elif self.symbol in SpaceGroup.sg_encoding:
self.full_symbol = SpaceGroup.sg_encoding[self.symbol]["full_symbol"]
self.point_group = SpaceGroup.sg_encoding[self.symbol]["point_group"]
else:
self.full_symbol = re.sub(r" ", "", spg["universal_h_m"])
self.point_group = spg["schoenflies"]
self.full_symbol = spg["hermann_mauguin_u"]
warnings.warn(
f"Full symbol not available, falling back to short Hermann Mauguin symbol "
f"{self.symbol} instead"
)
self.point_group = spg["point_group"]
self.int_number = spg["number"]
self.order = len(ops)
self._symmetry_ops = {*ops}
Expand Down Expand Up @@ -399,7 +472,7 @@ def check(param, ref, tolerance):
if crys_system == "hexagonal" or (
crys_system == "trigonal"
and (
self.symbol.endswith("H")
self.hexagonal
or self.int_number
in [143, 144, 145, 147, 149, 150, 151, 152, 153, 154, 156, 157, 158, 159, 162, 163, 164, 165]
)
Expand Down Expand Up @@ -531,7 +604,8 @@ def sg_symbol_from_int_number(int_number: int, hexagonal: bool = True) -> str:
hexagonal setting (default) or rhombohedral setting.

Returns:
str: Spacegroup symbol
str: Spacegroup symbol / Space group symbol + "H" if group is
rhombohedral and hexagonal=True
"""
syms = []
for n, v in SYMM_DATA["space_group_encoding"].items():
Expand Down
2 changes: 1 addition & 1 deletion src/pymatgen/symmetry/symm_data.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/pymatgen/symmetry/symm_ops.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/analysis/test_piezo.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

class TestPiezo(PymatgenTest):
def setUp(self):
self.piezo_struc = self.get_structure("BaNiO3")
self.piezo_struct = self.get_structure("BaNiO3")
self.voigt_matrix = np.array(
[
[0.0, 0.0, 0.0, 0.0, 0.03839, 0.0],
Expand Down
4 changes: 2 additions & 2 deletions tests/core/test_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,9 @@ def test_get_slab(self):
if sg.crystal_system == "hexagonal" or (
sg.crystal_system == "trigonal"
and (
sg.symbol.endswith("H")
sg.hexagonal
or sg.int_number
in [143, 144, 145, 147, 149, 150, 151, 152, 153, 154, 156, 157, 158, 159, 162, 163, 164, 165]
in (143, 144, 145, 147, 149, 150, 151, 152, 153, 154, 156, 157, 158, 159, 162, 163, 164, 165)
)
):
lattice = Lattice.hexagonal(5, 10)
Expand Down
2 changes: 1 addition & 1 deletion tests/files/.pytest-split-durations
Original file line number Diff line number Diff line change
Expand Up @@ -2017,7 +2017,7 @@
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_get_nn_info": 0.23225333401933312,
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_get_plot_label": 0.2305897069745697,
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_get_structure_environments": 0.2838197909295559,
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_get_strucuture_environments_further_tests": 0.2533352089812979,
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_get_structure_environments_further_tests": 0.2533352089812979,
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_get_sum_icohps_between_neighbors_of_atom": 0.24758987501263618,
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_molecules_allowed": 0.23277270805556327,
"tests/io/lobster/test_inputsenv.py::TestLobsterNeighbors::test_order_parameter": 0.2319688760326244,
Expand Down
2 changes: 1 addition & 1 deletion tests/io/lobster/test_lobsterenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ def test_get_structure_environments(self):
lse2 = self.chem_env_lobster1.get_light_structure_environment()
assert lse2.coordination_environments[0][0]["ce_symbol"] == "O:6"

def test_get_strucuture_environments_further_tests(self):
def test_get_structure_environments_further_tests(self):
lse = self.chem_env_lobster1_second.get_light_structure_environment()
lse.as_dict()
lse.get_statistics()
Expand Down
2 changes: 1 addition & 1 deletion tests/io/test_pwscf.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ def test_read_str(self):
assert pw_in.sections["system"]["smearing"] == "cold"


class TestPWOuput(PymatgenTest):
class TestPWOutput(PymatgenTest):
def setUp(self):
self.pw_out = PWOutput(f"{TEST_DIR}/Si.pwscf.out")

Expand Down
4 changes: 2 additions & 2 deletions tests/io/vasp/test_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,12 +728,12 @@ def test_init(self):
vis = self.set(vis.structure, user_incar_settings={"LUSE_VDW": True, "LASPH": False})
vis.incar.items()
with pytest.warns(BadInputSetWarning, match=r"LASPH"):
dummy_struc = Structure(
dummy_struct = Structure(
lattice=[[0, 2, 2], [2, 0, 2], [2, 2, 0]],
species=["Fe", "O"],
coords=[[0, 0, 0], [0.5, 0.5, 0.5]],
)
vis = self.set(dummy_struc, user_incar_settings={"LDAU": True, "LASPH": False})
vis = self.set(dummy_struct, user_incar_settings={"LDAU": True, "LASPH": False})
vis.incar.items()

def test_user_incar_kspacing(self):
Expand Down
Loading
Loading