diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 62d4b81009f..aa53a2660ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ permissions: jobs: pytest: # prevent this action from running on forks - if: github.repository == 'materialsproject/pymatgen' + if: ${{ !github.event.pull_request.head.repo.fork }} strategy: fail-fast: false matrix: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30941302a3f..da8bfe1bde6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ ci: autoupdate_commit_msg: pre-commit autoupdate repos: - - repo: https://github.com/charliermarsh/ruff-pre-commit + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.0.275 hooks: - id: ruff diff --git a/pymatgen/analysis/elasticity/tests/test_elastic.py b/pymatgen/analysis/elasticity/tests/test_elastic.py index fa793096aa0..b56676b6fa0 100644 --- a/pymatgen/analysis/elasticity/tests/test_elastic.py +++ b/pymatgen/analysis/elasticity/tests/test_elastic.py @@ -233,11 +233,7 @@ def test_energy_density(self): [ [0.99774738, 0.11520994, -0], [-0.11520994, 0.99774738, 0], - [ - -0, - -0, - 1, - ], + [-0, -0, 1], ] ) ) @@ -268,9 +264,9 @@ def tearDown(self): warnings.simplefilter("default") def test_init(self): - cijkl = Tensor.from_voigt(self.c2) - cijklmn = Tensor.from_voigt(self.c3) - exp = ElasticTensorExpansion([cijkl, cijklmn]) + c_ijkl = Tensor.from_voigt(self.c2) + c_ijklmn = Tensor.from_voigt(self.c3) + exp = ElasticTensorExpansion([c_ijkl, c_ijklmn]) ElasticTensorExpansion.from_voigt([self.c2, self.c3]) assert exp.order == 3 diff --git a/pymatgen/analysis/tests/test_surface_analysis.py b/pymatgen/analysis/tests/test_surface_analysis.py index 04b870d1e03..f7871eee255 100644 --- a/pymatgen/analysis/tests/test_surface_analysis.py +++ b/pymatgen/analysis/tests/test_surface_analysis.py @@ -2,7 +2,6 @@ import json import os -import warnings from pytest import approx from sympy import Number, Symbol @@ -25,9 +24,6 @@ def get_path(path_str): class SlabEntryTest(PymatgenTest): def setUp(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - with open(os.path.join(get_path(""), "ucell_entries.txt")) as ucell_entries: ucell_entries = json.loads(ucell_entries.read()) self.ucell_entries = ucell_entries diff --git a/pymatgen/apps/borg/tests/test_hive.py b/pymatgen/apps/borg/tests/test_hive.py index 4518579be41..3402a7a1267 100644 --- a/pymatgen/apps/borg/tests/test_hive.py +++ b/pymatgen/apps/borg/tests/test_hive.py @@ -26,20 +26,18 @@ def test_get_valid_paths(self): assert len(self.drone.get_valid_paths(path)) > 0 def test_assimilate(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - entry = self.drone.assimilate(PymatgenTest.TEST_FILES_DIR) - for p in ["hubbards", "is_hubbard", "potcar_spec", "run_type"]: - assert p in entry.parameters - assert entry.data["efermi"] == approx(-6.62148548) - assert entry.composition.reduced_formula == "Xe" - assert entry.energy == approx(0.5559329) - entry = self.structure_drone.assimilate(PymatgenTest.TEST_FILES_DIR) - assert entry.composition.reduced_formula == "Xe" - assert entry.energy == approx(0.5559329) - assert isinstance(entry, ComputedStructureEntry) - assert entry.structure is not None - # assert len(entry.parameters["history"]) == 2 + entry = self.drone.assimilate(PymatgenTest.TEST_FILES_DIR) + for p in ["hubbards", "is_hubbard", "potcar_spec", "run_type"]: + assert p in entry.parameters + assert entry.data["efermi"] == approx(-6.62148548) + assert entry.composition.reduced_formula == "Xe" + assert entry.energy == approx(0.5559329) + entry = self.structure_drone.assimilate(PymatgenTest.TEST_FILES_DIR) + assert entry.composition.reduced_formula == "Xe" + assert entry.energy == approx(0.5559329) + assert isinstance(entry, ComputedStructureEntry) + assert entry.structure is not None + # assert len(entry.parameters["history"]) == 2 def tearDown(self): warnings.simplefilter("default") diff --git a/pymatgen/command_line/mcsqs_caller.py b/pymatgen/command_line/mcsqs_caller.py index 7726ae5682e..74b1f9923af 100644 --- a/pymatgen/command_line/mcsqs_caller.py +++ b/pymatgen/command_line/mcsqs_caller.py @@ -209,9 +209,7 @@ def _parse_sqs_path(path) -> Sqs: corr_out = f"bestcorr{i + 1}.out" with Popen(f"str2cif < {sqs_out} > {sqs_cif}", shell=True, cwd=path) as p: p.communicate() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - sqs = Structure.from_file(path / sqs_out) + sqs = Structure.from_file(path / sqs_out) with open(path / corr_out) as f: lines = f.readlines() diff --git a/pymatgen/command_line/tests/test_enumlib_caller.py b/pymatgen/command_line/tests/test_enumlib_caller.py index 41ad1cefd66..0fc0abc1d02 100644 --- a/pymatgen/command_line/tests/test_enumlib_caller.py +++ b/pymatgen/command_line/tests/test_enumlib_caller.py @@ -2,7 +2,6 @@ import os import unittest -import warnings from shutil import which import pytest @@ -26,62 +25,60 @@ class EnumlibAdaptorTest(PymatgenTest): _multiprocess_shared_ = True def test_init(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - struct = self.get_structure("LiFePO4") - subtrans = SubstitutionTransformation({"Li": {"Li": 0.5}}) - adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2) - adaptor.run() - structures = adaptor.structures - assert len(structures) == 86 - for s in structures: - assert s.composition.get_atomic_fraction(Element("Li")) == approx(0.5 / 6.5) - adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2, refine_structure=True) - adaptor.run() - structures = adaptor.structures - assert len(structures) == 52 + struct = self.get_structure("LiFePO4") + subtrans = SubstitutionTransformation({"Li": {"Li": 0.5}}) + adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2) + adaptor.run() + structures = adaptor.structures + assert len(structures) == 86 + for s in structures: + assert s.composition.get_atomic_fraction(Element("Li")) == approx(0.5 / 6.5) + adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 2, refine_structure=True) + adaptor.run() + structures = adaptor.structures + assert len(structures) == 52 - subtrans = SubstitutionTransformation({"Li": {"Li": 0.25}}) - adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 1, refine_structure=True) - adaptor.run() - structures = adaptor.structures - assert len(structures) == 1 - for s in structures: - assert s.composition.get_atomic_fraction(Element("Li")) == approx(0.25 / 6.25) - - # Make sure it works for completely disordered structures. - struct = Structure([[10, 0, 0], [0, 10, 0], [0, 0, 10]], [{"Fe": 0.5}], [[0, 0, 0]]) - adaptor = EnumlibAdaptor(struct, 1, 2) - adaptor.run() - assert len(adaptor.structures) == 3 - - # Make sure it works properly when symmetry is broken by ordered sites. - struct = self.get_structure("LiFePO4") - subtrans = SubstitutionTransformation({"Li": {"Li": 0.25}}) - s = subtrans.apply_transformation(struct) - # REmove some ordered sites to break symmetry. - removetrans = RemoveSitesTransformation([4, 7]) - s = removetrans.apply_transformation(s) - adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01) - adaptor.run() - structures = adaptor.structures - assert len(structures) == 4 - - struct = Structure( - [[3, 0, 0], [0, 3, 0], [0, 0, 3]], - [{"Si": 0.5}] * 2, - [[0, 0, 0], [0.5, 0.5, 0.5]], - ) - adaptor = EnumlibAdaptor(struct, 1, 3, enum_precision_parameter=0.01) - adaptor.run() - structures = adaptor.structures - assert len(structures) == 10 + subtrans = SubstitutionTransformation({"Li": {"Li": 0.25}}) + adaptor = EnumlibAdaptor(subtrans.apply_transformation(struct), 1, 1, refine_structure=True) + adaptor.run() + structures = adaptor.structures + assert len(structures) == 1 + for s in structures: + assert s.composition.get_atomic_fraction(Element("Li")) == approx(0.25 / 6.25) - struct = Structure.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "EnumerateTest.json")) - adaptor = EnumlibAdaptor(struct, 1, 1) - adaptor.run() - structures = adaptor.structures - assert len(structures) == 2 + # Make sure it works for completely disordered structures. + struct = Structure([[10, 0, 0], [0, 10, 0], [0, 0, 10]], [{"Fe": 0.5}], [[0, 0, 0]]) + adaptor = EnumlibAdaptor(struct, 1, 2) + adaptor.run() + assert len(adaptor.structures) == 3 + + # Make sure it works properly when symmetry is broken by ordered sites. + struct = self.get_structure("LiFePO4") + subtrans = SubstitutionTransformation({"Li": {"Li": 0.25}}) + s = subtrans.apply_transformation(struct) + # REmove some ordered sites to break symmetry. + removetrans = RemoveSitesTransformation([4, 7]) + s = removetrans.apply_transformation(s) + adaptor = EnumlibAdaptor(s, 1, 1, enum_precision_parameter=0.01) + adaptor.run() + structures = adaptor.structures + assert len(structures) == 4 + + struct = Structure( + [[3, 0, 0], [0, 3, 0], [0, 0, 3]], + [{"Si": 0.5}] * 2, + [[0, 0, 0], [0.5, 0.5, 0.5]], + ) + adaptor = EnumlibAdaptor(struct, 1, 3, enum_precision_parameter=0.01) + adaptor.run() + structures = adaptor.structures + assert len(structures) == 10 + + struct = Structure.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "EnumerateTest.json")) + adaptor = EnumlibAdaptor(struct, 1, 1) + adaptor.run() + structures = adaptor.structures + assert len(structures) == 2 def test_rounding_errors(self): # It used to be that a rounding issue would result in this structure diff --git a/pymatgen/core/structure.py b/pymatgen/core/structure.py index 60ea5e586e8..d567a5fd2c7 100644 --- a/pymatgen/core/structure.py +++ b/pymatgen/core/structure.py @@ -1013,8 +1013,8 @@ def from_spacegroup( from pymatgen.symmetry.groups import SpaceGroup try: - i = int(sg) - spg = SpaceGroup.from_int_number(i) + num = int(sg) + spg = SpaceGroup.from_int_number(num) except ValueError: spg = SpaceGroup(sg) # type: ignore @@ -1038,12 +1038,12 @@ def from_spacegroup( all_sp: list[str | Element | Species | DummySpecies | Composition] = [] all_coords: list[list[float]] = [] all_site_properties: dict[str, list] = collections.defaultdict(list) - for i, (sp, c) in enumerate(zip(species, frac_coords)): + for idx, (sp, c) in enumerate(zip(species, frac_coords)): cc = spg.get_orbit(c, tol=tol) all_sp.extend([sp] * len(cc)) all_coords.extend(cc) # type: ignore for k, v in props.items(): - all_site_properties[k].extend([v[i]] * len(cc)) + all_site_properties[k].extend([v[idx]] * len(cc)) return cls(latt, all_sp, all_coords, site_properties=all_site_properties) diff --git a/pymatgen/io/tests/test_cif.py b/pymatgen/io/tests/test_cif.py index 133f85901f7..6f7bf55d84a 100644 --- a/pymatgen/io/tests/test_cif.py +++ b/pymatgen/io/tests/test_cif.py @@ -277,9 +277,7 @@ def test_CifParser(self): assert struct.lattice.beta == approx(92) assert struct.lattice.gamma == approx(93) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - parser = CifParser(self.TEST_FILES_DIR / "srycoo.cif") + parser = CifParser(self.TEST_FILES_DIR / "srycoo.cif") assert parser.get_structures()[0].formula == "Sr5.6 Y2.4 Co8 O21" # Test with a decimal Xyz. This should parse as two atoms in @@ -301,110 +299,104 @@ def test_site_symbol_preference(self): assert parser.get_structures()[0].formula == "Ge0.4 Sb0.4 Te1" def test_implicit_hydrogen(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - parser = CifParser(self.TEST_FILES_DIR / "Senegalite_implicit_hydrogen.cif") - for s in parser.get_structures(): - assert s.formula == "Al8 P4 O32" - assert sum(s.site_properties["implicit_hydrogens"]) == 20 - assert ( - "Structure has implicit hydrogens defined, " - "parsed structure unlikely to be suitable for use " - "in calculations unless hydrogens added." in parser.warnings - ) - parser = CifParser(self.TEST_FILES_DIR / "cif_implicit_hydrogens_cod_1011130.cif") - s = parser.get_structures()[0] - assert ( - "Structure has implicit hydrogens defined, " - "parsed structure unlikely to be suitable for use " - "in calculations unless hydrogens added." in parser.warnings - ) + parser = CifParser(self.TEST_FILES_DIR / "Senegalite_implicit_hydrogen.cif") + for s in parser.get_structures(): + assert s.formula == "Al8 P4 O32" + assert sum(s.site_properties["implicit_hydrogens"]) == 20 + assert ( + "Structure has implicit hydrogens defined, " + "parsed structure unlikely to be suitable for use " + "in calculations unless hydrogens added." in parser.warnings + ) + parser = CifParser(self.TEST_FILES_DIR / "cif_implicit_hydrogens_cod_1011130.cif") + s = parser.get_structures()[0] + assert ( + "Structure has implicit hydrogens defined, " + "parsed structure unlikely to be suitable for use " + "in calculations unless hydrogens added." in parser.warnings + ) def test_CifParserSpringerPauling(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - # Below are 10 tests for CIFs from the Springer Materials/Pauling file DBs. - - # Partial occupancy on sites, incorrect label, previously unparsable - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1928405.cif") - for s in parser.get_structures(True): - assert s.formula == "Er1 Mn3.888 Fe2.112 Sn6" - assert parser.has_errors - - # Partial occupancy on sites, previously parsed as an ordered structure - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1011081.cif") - for s in parser.get_structures(True): - assert s.formula == "Zr0.2 Nb0.8" - assert parser.has_errors - - # Partial occupancy on sites, incorrect label, previously unparsable - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1615854.cif") - for s in parser.get_structures(True): - assert s.formula == "Na2 Al2 Si6 O16" - assert parser.has_errors - - # Partial occupancy on sites, incorrect label, previously unparsable - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1622133.cif") - for s in parser.get_structures(True): - assert s.formula == "Ca0.184 Mg13.016 Fe2.8 Si16 O48" - assert parser.has_errors - - # Partial occupancy on sites, previously parsed as an ordered structure - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1908491.cif") - for s in parser.get_structures(True): - assert s.formula == "Mn0.48 Zn0.52 Ga2 Se4" - assert parser.has_errors - - # Partial occupancy on sites, incorrect label, previously unparsable - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1811457.cif") - for s in parser.get_structures(True): - assert s.formula == "Ba2 Mg0.6 Zr0.2 Ta1.2 O6" - assert parser.has_errors - - # Incomplete powder diffraction data, previously unparsable - # This CIF file contains the molecular species "NH3" which is - # parsed as "N" because the label is "N{x}" (x = 1,2,..) and the - # corresponding symbol is "NH3". Since, the label and symbol are switched - # in CIFs from Springer Materials/Pauling file DBs, CifParser parses the - # element as "Nh" (Nihonium). - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1002871.cif") - assert parser.get_structures(True)[0].formula == "Cu1 Br2 Nh6" - assert parser.get_structures(True)[1].formula == "Cu1 Br4 Nh6" - assert parser.has_errors - - # Incomplete powder diffraction data, previously unparsable - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1704003.cif") - for s in parser.get_structures(): - assert s.formula == "Rb4 Mn2 F12" - assert parser.has_errors - - # Unparsable species 'OH/OH2', previously parsed as "O" - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1500382.cif") - for s in parser.get_structures(): - assert s.formula == "Mg6 B2 O6 F1.764" - assert parser.has_errors - - # Unparsable species 'OH/OH2', previously parsed as "O" - parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1601634.cif") - for s in parser.get_structures(): - assert s.formula == "Zn1.29 Fe0.69 As2 Pb1.02 O8" + # Below are 10 tests for CIFs from the Springer Materials/Pauling file DBs. + + # Partial occupancy on sites, incorrect label, previously unparsable + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1928405.cif") + for s in parser.get_structures(True): + assert s.formula == "Er1 Mn3.888 Fe2.112 Sn6" + assert parser.has_errors + + # Partial occupancy on sites, previously parsed as an ordered structure + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1011081.cif") + for s in parser.get_structures(True): + assert s.formula == "Zr0.2 Nb0.8" + assert parser.has_errors + + # Partial occupancy on sites, incorrect label, previously unparsable + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1615854.cif") + for s in parser.get_structures(True): + assert s.formula == "Na2 Al2 Si6 O16" + assert parser.has_errors + + # Partial occupancy on sites, incorrect label, previously unparsable + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1622133.cif") + for s in parser.get_structures(True): + assert s.formula == "Ca0.184 Mg13.016 Fe2.8 Si16 O48" + assert parser.has_errors + + # Partial occupancy on sites, previously parsed as an ordered structure + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1908491.cif") + for s in parser.get_structures(True): + assert s.formula == "Mn0.48 Zn0.52 Ga2 Se4" + assert parser.has_errors + + # Partial occupancy on sites, incorrect label, previously unparsable + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1811457.cif") + for s in parser.get_structures(True): + assert s.formula == "Ba2 Mg0.6 Zr0.2 Ta1.2 O6" + assert parser.has_errors + + # Incomplete powder diffraction data, previously unparsable + # This CIF file contains the molecular species "NH3" which is + # parsed as "N" because the label is "N{x}" (x = 1,2,..) and the + # corresponding symbol is "NH3". Since, the label and symbol are switched + # in CIFs from Springer Materials/Pauling file DBs, CifParser parses the + # element as "Nh" (Nihonium). + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1002871.cif") + assert parser.get_structures(True)[0].formula == "Cu1 Br2 Nh6" + assert parser.get_structures(True)[1].formula == "Cu1 Br4 Nh6" + assert parser.has_errors + + # Incomplete powder diffraction data, previously unparsable + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1704003.cif") + for s in parser.get_structures(): + assert s.formula == "Rb4 Mn2 F12" + assert parser.has_errors + + # Unparsable species 'OH/OH2', previously parsed as "O" + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1500382.cif") + for s in parser.get_structures(): + assert s.formula == "Mg6 B2 O6 F1.764" + assert parser.has_errors + + # Unparsable species 'OH/OH2', previously parsed as "O" + parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1601634.cif") + for s in parser.get_structures(): + assert s.formula == "Zn1.29 Fe0.69 As2 Pb1.02 O8" def test_CifParserCod(self): """ Parsing problematic cif files from the COD database """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - # Symbol in capital letters - parser = CifParser(self.TEST_FILES_DIR / "Cod_2100513.cif") - for s in parser.get_structures(True): - assert s.formula == "Ca4 Nb2.0 Al2 O12" + # Symbol in capital letters + parser = CifParser(self.TEST_FILES_DIR / "Cod_2100513.cif") + for s in parser.get_structures(True): + assert s.formula == "Ca4 Nb2.0 Al2 O12" - # Label in capital letters - parser = CifParser(self.TEST_FILES_DIR / "Cod_4115344.cif") - for s in parser.get_structures(True): - assert s.formula == "Mo4 P2 H60 C60 I4 O4" + # Label in capital letters + parser = CifParser(self.TEST_FILES_DIR / "Cod_4115344.cif") + for s in parser.get_structures(True): + assert s.formula == "Mo4 P2 H60 C60 I4 O4" def test_parse_symbol(self): """ @@ -460,11 +452,9 @@ def test_parse_symbol(self): special = {"Hw": "H", "Ow": "O", "Wat": "O", "wat": "O", "OH": "", "OH2": ""} test_cases.update(special) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - parser = CifParser(self.TEST_FILES_DIR / "LiFePO4.cif") - for sym, expected_symbol in test_cases.items(): - assert parser._parse_symbol(sym) == expected_symbol + parser = CifParser(self.TEST_FILES_DIR / "LiFePO4.cif") + for sym, expected_symbol in test_cases.items(): + assert parser._parse_symbol(sym) == expected_symbol def test_CifWriter(self): filepath = self.TEST_FILES_DIR / "POSCAR" @@ -663,24 +653,18 @@ def test_specie_cifwriter(self): assert struct.composition == s2.composition def test_primes(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - parser = CifParser(self.TEST_FILES_DIR / "C26H16BeN2O2S2.cif") - for s in parser.get_structures(False): - assert s.composition == 8 * Composition("C26H16BeN2O2S2") + parser = CifParser(self.TEST_FILES_DIR / "C26H16BeN2O2S2.cif") + for s in parser.get_structures(False): + assert s.composition == 8 * Composition("C26H16BeN2O2S2") def test_missing_atom_site_type_with_oxistates(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - parser = CifParser(self.TEST_FILES_DIR / "P24Ru4H252C296S24N16.cif") - c = Composition({"S0+": 24, "Ru0+": 4, "H0+": 252, "C0+": 296, "N0+": 16, "P0+": 24}) - for s in parser.get_structures(False): - assert s.composition == c + parser = CifParser(self.TEST_FILES_DIR / "P24Ru4H252C296S24N16.cif") + c = Composition({"S0+": 24, "Ru0+": 4, "H0+": 252, "C0+": 296, "N0+": 16, "P0+": 24}) + for s in parser.get_structures(False): + assert s.composition == c def test_no_coords_or_species(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - string = """#generated using pymatgen + string = """#generated using pymatgen data_Si1.5N1.5 _symmetry_space_group_name_H-M 'P 1' _cell_length_a 3.84019793 @@ -714,9 +698,9 @@ def test_no_coords_or_species(self): _atom_site_occupancy ? ? ? ? ? ? ? """ - parser = CifParser.from_string(string) - with pytest.raises(ValueError): - parser.get_structures() + parser = CifParser.from_string(string) + with pytest.raises(ValueError): + parser.get_structures() def test_get_lattice_from_lattice_type(self): cif_structure = """#generated using pymatgen @@ -790,31 +774,25 @@ def test_empty(self): assert cb == cb2 def test_bad_cif(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - f = self.TEST_FILES_DIR / "bad_occu.cif" - p = CifParser(f) - with pytest.raises(ValueError): - p.get_structures() - p = CifParser(f, occupancy_tolerance=2) - s = p.get_structures()[0] - assert s[0].species["Al3+"] == approx(0.5) + f = self.TEST_FILES_DIR / "bad_occu.cif" + p = CifParser(f) + with pytest.raises(ValueError): + p.get_structures() + p = CifParser(f, occupancy_tolerance=2) + s = p.get_structures()[0] + assert s[0].species["Al3+"] == approx(0.5) def test_one_line_symm(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - f = self.TEST_FILES_DIR / "OneLineSymmP1.cif" - p = CifParser(f) - s = p.get_structures()[0] - assert s.formula == "Ga4 Pb2 O8" + f = self.TEST_FILES_DIR / "OneLineSymmP1.cif" + p = CifParser(f) + s = p.get_structures()[0] + assert s.formula == "Ga4 Pb2 O8" def test_no_symmops(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - f = self.TEST_FILES_DIR / "nosymm.cif" - p = CifParser(f) - s = p.get_structures()[0] - assert s.formula == "H96 C60 O8" + f = self.TEST_FILES_DIR / "nosymm.cif" + p = CifParser(f) + s = p.get_structures()[0] + assert s.formula == "H96 C60 O8" def test_dot_positions(self): f = self.TEST_FILES_DIR / "ICSD59959.cif" diff --git a/pymatgen/io/vasp/tests/test_inputs.py b/pymatgen/io/vasp/tests/test_inputs.py index 9e7e393d11d..2b3b4453c66 100644 --- a/pymatgen/io/vasp/tests/test_inputs.py +++ b/pymatgen/io/vasp/tests/test_inputs.py @@ -2,7 +2,6 @@ import os import pickle -import warnings from pathlib import Path import numpy as np @@ -65,9 +64,7 @@ def test_init(self): 0.000000 0.000000 0.000000 0.750000 0.500000 0.750000 """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - poscar = Poscar.from_string(poscar_string) + poscar = Poscar.from_string(poscar_string) assert poscar.structure.composition == Composition("HHe") # VASP 4 style file with default names, i.e. no element symbol found. poscar_string = """Test3 diff --git a/pymatgen/io/vasp/tests/test_outputs.py b/pymatgen/io/vasp/tests/test_outputs.py index 1f3032450e9..1ec38bdfc3b 100644 --- a/pymatgen/io/vasp/tests/test_outputs.py +++ b/pymatgen/io/vasp/tests/test_outputs.py @@ -473,91 +473,89 @@ def test_as_dict(self): assert vasprun.as_dict()["input"]["nkpoints"] == 24 def test_get_band_structure(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - filepath = self.TEST_FILES_DIR / "vasprun_Si_bands.xml" - vasprun = Vasprun(filepath, parse_projected_eigen=True, parse_potcar_file=False) - bs = vasprun.get_band_structure(kpoints_filename=self.TEST_FILES_DIR / "KPOINTS_Si_bands") - cbm = bs.get_cbm() - vbm = bs.get_vbm() - assert cbm["kpoint_index"] == [13], "wrong cbm kpoint index" - assert cbm["energy"] == approx(6.2301), "wrong cbm energy" - assert cbm["band_index"] == {Spin.up: [4], Spin.down: [4]}, "wrong cbm bands" - assert vbm["kpoint_index"] == [0, 63, 64] - assert vbm["energy"] == approx(5.6158), "wrong vbm energy" - assert vbm["band_index"] == {Spin.up: [1, 2, 3], Spin.down: [1, 2, 3]}, "wrong vbm bands" - assert vbm["kpoint"].label == "\\Gamma", "wrong vbm label" - assert cbm["kpoint"].label is None, "wrong cbm label" - - projected = bs.get_projection_on_elements() - assert projected[Spin.up][0][0]["Si"] == approx(0.4238) - projected = bs.get_projections_on_elements_and_orbitals({"Si": ["s"]}) - assert projected[Spin.up][0][0]["Si"]["s"] == approx(0.4238) - - # Test compressed files case 1: compressed KPOINTS in current dir - copyfile(self.TEST_FILES_DIR / "vasprun_Si_bands.xml", "vasprun.xml") - - # Check for error if no KPOINTS file - vasprun = Vasprun("vasprun.xml", parse_projected_eigen=True, parse_potcar_file=False) - with pytest.raises( - VaspParserError, match="KPOINTS not found but needed to obtain band structure along symmetry lines" - ): - _ = vasprun.get_band_structure(line_mode=True) - - # Check KPOINTS.gz successfully inferred and used if present - with open(self.TEST_FILES_DIR / "KPOINTS_Si_bands", "rb") as f_in, gzip.open("KPOINTS.gz", "wb") as f_out: - copyfileobj(f_in, f_out) - bs_kpts_gzip = vasprun.get_band_structure() - assert bs.efermi == bs_kpts_gzip.efermi - assert bs.as_dict() == bs_kpts_gzip.as_dict() - - # Test compressed files case 2: compressed vasprun in another dir - os.mkdir("deeper") - copyfile(self.TEST_FILES_DIR / "KPOINTS_Si_bands", Path("deeper") / "KPOINTS") - with open(self.TEST_FILES_DIR / "vasprun_Si_bands.xml", "rb") as f_in, gzip.open( - os.path.join("deeper", "vasprun.xml.gz"), "wb" - ) as f_out: - copyfileobj(f_in, f_out) - vasprun = Vasprun( - os.path.join("deeper", "vasprun.xml.gz"), - parse_projected_eigen=True, - parse_potcar_file=False, - ) - bs_vasprun_gzip = vasprun.get_band_structure(line_mode=True) - assert bs.efermi == bs_vasprun_gzip.efermi - assert bs.as_dict() == bs_vasprun_gzip.as_dict() - - # test hybrid band structures - vasprun.actual_kpoints_weights[-1] = 0.0 - bs = vasprun.get_band_structure(kpoints_filename=self.TEST_FILES_DIR / "KPOINTS_Si_bands") - cbm = bs.get_cbm() - vbm = bs.get_vbm() - assert cbm["kpoint_index"] == [0] - assert cbm["energy"] == approx(6.3676) - assert cbm["kpoint"].label is None - assert vbm["kpoint_index"] == [0] - assert vbm["energy"] == approx(2.8218) - assert vbm["kpoint"].label is None - - # test self-consistent band structure calculation for non-hybrid functionals - vasprun = Vasprun( - self.TEST_FILES_DIR / "vasprun.xml.forcehybridlikecalc", - parse_projected_eigen=True, - parse_potcar_file=False, - ) - bs = vasprun.get_band_structure( - kpoints_filename=self.TEST_FILES_DIR / "KPOINTS.forcehybridlikecalc", - force_hybrid_mode=True, - line_mode=True, - ) + filepath = self.TEST_FILES_DIR / "vasprun_Si_bands.xml" + vasprun = Vasprun(filepath, parse_projected_eigen=True, parse_potcar_file=False) + bs = vasprun.get_band_structure(kpoints_filename=self.TEST_FILES_DIR / "KPOINTS_Si_bands") + cbm = bs.get_cbm() + vbm = bs.get_vbm() + assert cbm["kpoint_index"] == [13], "wrong cbm kpoint index" + assert cbm["energy"] == approx(6.2301), "wrong cbm energy" + assert cbm["band_index"] == {Spin.up: [4], Spin.down: [4]}, "wrong cbm bands" + assert vbm["kpoint_index"] == [0, 63, 64] + assert vbm["energy"] == approx(5.6158), "wrong vbm energy" + assert vbm["band_index"] == {Spin.up: [1, 2, 3], Spin.down: [1, 2, 3]}, "wrong vbm bands" + assert vbm["kpoint"].label == "\\Gamma", "wrong vbm label" + assert cbm["kpoint"].label is None, "wrong cbm label" - dict_to_test = bs.get_band_gap() + projected = bs.get_projection_on_elements() + assert projected[Spin.up][0][0]["Si"] == approx(0.4238) + projected = bs.get_projections_on_elements_and_orbitals({"Si": ["s"]}) + assert projected[Spin.up][0][0]["Si"]["s"] == approx(0.4238) - assert dict_to_test["direct"] - assert dict_to_test["energy"] == approx(6.007899999999999) - assert dict_to_test["transition"] == "\\Gamma-\\Gamma" - assert bs.get_branch(0)[0]["start_index"] == 0 - assert bs.get_branch(0)[0]["end_index"] == 0 + # Test compressed files case 1: compressed KPOINTS in current dir + copyfile(self.TEST_FILES_DIR / "vasprun_Si_bands.xml", "vasprun.xml") + + # Check for error if no KPOINTS file + vasprun = Vasprun("vasprun.xml", parse_projected_eigen=True, parse_potcar_file=False) + with pytest.raises( + VaspParserError, match="KPOINTS not found but needed to obtain band structure along symmetry lines" + ): + _ = vasprun.get_band_structure(line_mode=True) + + # Check KPOINTS.gz successfully inferred and used if present + with open(self.TEST_FILES_DIR / "KPOINTS_Si_bands", "rb") as f_in, gzip.open("KPOINTS.gz", "wb") as f_out: + copyfileobj(f_in, f_out) + bs_kpts_gzip = vasprun.get_band_structure() + assert bs.efermi == bs_kpts_gzip.efermi + assert bs.as_dict() == bs_kpts_gzip.as_dict() + + # Test compressed files case 2: compressed vasprun in another dir + os.mkdir("deeper") + copyfile(self.TEST_FILES_DIR / "KPOINTS_Si_bands", Path("deeper") / "KPOINTS") + with open(self.TEST_FILES_DIR / "vasprun_Si_bands.xml", "rb") as f_in, gzip.open( + os.path.join("deeper", "vasprun.xml.gz"), "wb" + ) as f_out: + copyfileobj(f_in, f_out) + vasprun = Vasprun( + os.path.join("deeper", "vasprun.xml.gz"), + parse_projected_eigen=True, + parse_potcar_file=False, + ) + bs_vasprun_gzip = vasprun.get_band_structure(line_mode=True) + assert bs.efermi == bs_vasprun_gzip.efermi + assert bs.as_dict() == bs_vasprun_gzip.as_dict() + + # test hybrid band structures + vasprun.actual_kpoints_weights[-1] = 0.0 + bs = vasprun.get_band_structure(kpoints_filename=self.TEST_FILES_DIR / "KPOINTS_Si_bands") + cbm = bs.get_cbm() + vbm = bs.get_vbm() + assert cbm["kpoint_index"] == [0] + assert cbm["energy"] == approx(6.3676) + assert cbm["kpoint"].label is None + assert vbm["kpoint_index"] == [0] + assert vbm["energy"] == approx(2.8218) + assert vbm["kpoint"].label is None + + # test self-consistent band structure calculation for non-hybrid functionals + vasprun = Vasprun( + self.TEST_FILES_DIR / "vasprun.xml.forcehybridlikecalc", + parse_projected_eigen=True, + parse_potcar_file=False, + ) + bs = vasprun.get_band_structure( + kpoints_filename=self.TEST_FILES_DIR / "KPOINTS.forcehybridlikecalc", + force_hybrid_mode=True, + line_mode=True, + ) + + dict_to_test = bs.get_band_gap() + + assert dict_to_test["direct"] + assert dict_to_test["energy"] == approx(6.007899999999999) + assert dict_to_test["transition"] == "\\Gamma-\\Gamma" + assert bs.get_branch(0)[0]["start_index"] == 0 + assert bs.get_branch(0)[0]["end_index"] == 0 def test_projected_magnetisation(self): filepath = self.TEST_FILES_DIR / "vasprun.lvel.Si2H.xml" @@ -673,22 +671,18 @@ def test_potcar_not_found(self): ] def test_parsing_chemical_shift_calculations(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - filepath = self.TEST_FILES_DIR / "nmr" / "cs" / "basic" / "vasprun.xml.chemical_shift.scstep" - vasprun = Vasprun(filepath) - nestep = len(vasprun.ionic_steps[-1]["electronic_steps"]) - assert nestep == 10 - assert vasprun.converged + filepath = self.TEST_FILES_DIR / "nmr" / "cs" / "basic" / "vasprun.xml.chemical_shift.scstep" + vasprun = Vasprun(filepath) + nestep = len(vasprun.ionic_steps[-1]["electronic_steps"]) + assert nestep == 10 + assert vasprun.converged def test_parsing_efg_calcs(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - filepath = self.TEST_FILES_DIR / "nmr" / "efg" / "AlPO4" / "vasprun.xml" - vasprun = Vasprun(filepath) - nestep = len(vasprun.ionic_steps[-1]["electronic_steps"]) - assert nestep == 18 - assert vasprun.converged + filepath = self.TEST_FILES_DIR / "nmr" / "efg" / "AlPO4" / "vasprun.xml" + vasprun = Vasprun(filepath) + nestep = len(vasprun.ionic_steps[-1]["electronic_steps"]) + assert nestep == 18 + assert vasprun.converged def test_charged_structure(self): vpath = self.TEST_FILES_DIR / "vasprun.charged.xml" diff --git a/pymatgen/symmetry/groups.py b/pymatgen/symmetry/groups.py index 82d8f124073..f5540e8f5ad 100644 --- a/pymatgen/symmetry/groups.py +++ b/pymatgen/symmetry/groups.py @@ -131,7 +131,9 @@ def __init__(self, int_symbol: str) -> None: from pymatgen.core.operations import SymmOp self.symbol = int_symbol - self.generators = [SYMM_DATA["generator_matrices"][c] for c in SYMM_DATA["point_group_encoding"][int_symbol]] + self.generators = [ + SYMM_DATA["generator_matrices"][enc] for enc in SYMM_DATA["point_group_encoding"][int_symbol] + ] self._symmetry_ops = {SymmOp.from_rotation_and_translation(m) for m in self._generate_full_symmetry_ops()} self.order = len(self._symmetry_ops) @@ -210,7 +212,7 @@ class SpaceGroup(SymmetryGroup): gen_matrices = SYMM_DATA["generator_matrices"] # POINT_GROUP_ENC = SYMM_DATA["point_group_encoding"] - sgencoding = SYMM_DATA["space_group_encoding"] + sg_encoding = SYMM_DATA["space_group_encoding"] abbrev_sg_mapping = SYMM_DATA["abbreviated_spacegroup_symbols"] translations = frozendict({k: Fraction(v) for k, v in SYMM_DATA["translations"].items()}) full_sg_mapping = frozendict({v["full_symbol"]: k for k, v in SYMM_DATA["space_group_encoding"].items()}) @@ -246,9 +248,9 @@ def __init__(self, int_symbol: str) -> None: if int_symbol in [spg["hermann_mauguin"], spg["universal_h_m"]]: ops = [SymmOp.from_xyz_string(s) for s in spg["symops"]] self.symbol = re.sub(r":", "", re.sub(r" ", "", spg["universal_h_m"])) - if int_symbol in SpaceGroup.sgencoding: - self.full_symbol = SpaceGroup.sgencoding[int_symbol]["full_symbol"] - self.point_group = SpaceGroup.sgencoding[int_symbol]["point_group"] + 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"] else: self.full_symbol = re.sub(r" ", "", spg["universal_h_m"]) self.point_group = spg["schoenflies"] @@ -257,10 +259,10 @@ def __init__(self, int_symbol: str) -> None: self._symmetry_ops = {*ops} break else: - if int_symbol not in SpaceGroup.sgencoding: + if int_symbol not in SpaceGroup.sg_encoding: raise ValueError(f"Bad international symbol {int_symbol!r}") - data = SpaceGroup.sgencoding[int_symbol] + data = SpaceGroup.sg_encoding[int_symbol] self.symbol = int_symbol # TODO: Support different origin choices. @@ -322,10 +324,10 @@ def get_settings(cls, int_symbol: str) -> set[str]: symbols = [] if int_symbol in SpaceGroup.abbrev_sg_mapping: symbols.append(SpaceGroup.abbrev_sg_mapping[int_symbol]) - int_number = SpaceGroup.sgencoding[int_symbol]["int_number"] + int_number = SpaceGroup.sg_encoding[int_symbol]["int_number"] elif int_symbol in SpaceGroup.full_sg_mapping: symbols.append(SpaceGroup.full_sg_mapping[int_symbol]) - int_number = SpaceGroup.sgencoding[int_symbol]["int_number"] + int_number = SpaceGroup.sg_encoding[int_symbol]["int_number"] else: for spg in SpaceGroup.SYMM_OPS: if int_symbol in [ @@ -452,18 +454,18 @@ def crystal_system( Returns: str: Crystal system of the space group, e.g., cubic, hexagonal, etc. """ - i = self.int_number - if i <= 2: + num = self.int_number + if num <= 2: return "triclinic" - if i <= 15: + if num <= 15: return "monoclinic" - if i <= 74: + if num <= 74: return "orthorhombic" - if i <= 142: + if num <= 142: return "tetragonal" - if i <= 167: + if num <= 167: return "trigonal" - if i <= 194: + if num <= 194: return "hexagonal" return "cubic" @@ -521,13 +523,22 @@ def from_int_number(cls, int_number: int, hexagonal: bool = True) -> SpaceGroup: hexagonal (bool): For rhombohedral groups, whether to return the hexagonal setting (default) or rhombohedral setting. + Raises: + ValueError: If the international number is not valid, i.e. not between 1 and 230 inclusive. + Returns: - (SpaceGroup) - """ - sym = sg_symbol_from_int_number(int_number, hexagonal=hexagonal) - if not hexagonal and int_number in [146, 148, 155, 160, 161, 166, 167]: - sym += ":R" - return SpaceGroup(sym) + SpaceGroup: object with the given international number. + """ + if int_number not in range(1, 231): + raise ValueError(f"International number must be between 1 and 230, got {int_number}") + symbol = sg_symbol_from_int_number(int_number, hexagonal=hexagonal) + if not hexagonal and int_number in (146, 148, 155, 160, 161, 166, 167): + symbol += ":R" + return SpaceGroup(symbol) + + def __repr__(self) -> str: + symbol = self.symbol + return f"{type(self).__name__}({symbol=})" def __str__(self) -> str: return ( diff --git a/pymatgen/symmetry/tests/test_groups.py b/pymatgen/symmetry/tests/test_groups.py index 66ea513d3fd..e8043809c84 100644 --- a/pymatgen/symmetry/tests/test_groups.py +++ b/pymatgen/symmetry/tests/test_groups.py @@ -1,7 +1,6 @@ from __future__ import annotations import unittest -import warnings import numpy as np import pytest @@ -18,51 +17,58 @@ __email__ = "ongsp@ucsd.edu" __date__ = "4/10/14" +ORDERED_SYMBOLS = ( + "P1 P-1 P121 P12_11 C121 P1m1 P1c1 C1m1 C1c1 P12/m1 P12_1/m1 C12/m1 P12/c1 P12_1/c1 C12/c1 P222 P222_1" + " P2_12_12 P2_12_121 C222_1 C222 F222 I222 I2_12_121 Pmm2 Pmc2_1 Pcc2 Pma2 Pca2_1 Pnc2 Pmn2_1 Pba2 Pna2_1 Pnn2 " + "Cmm2 Cmc2_1 Ccc2 Amm2 Aem2 Ama2 Aea2 Fmm2 Fdd2 Imm2 Iba2 Ima2 Pmmm Pnnn1 Pccm Pban1 Pmma Pnna Pmna Pcca Pbam " + "Pccn Pbcm Pnnm Pmmn1 Pbcn Pbca Pnma Cmcm Cmce Cmmm Cccm Cmme Ccce1 Fmmm Fddd1 Immm Ibam Ibca Imma P4 P4_1 P4_2 " + "P4_3 I4 I4_1 P-4 I-4 P4/m P4_2/m P4/n1 P4_2/n I4/m I4_1/a P422 P42_12 P4_122 P4_12_12 P4_222 P4_22_12 P4_322 " + "P4_32_12 I422 I4_122 P4mm P4bm P4_2cm P4_2nm P4cc P4nc P4_2mc P4_2bc I4mm I4cm I4_1md I4_1cd P-42m P-42c P-42_1m " + "P-42_1c P-4m2 P-4c2 P-4b2 P-4n2 I-4m2 I-4c2 I-42m I-42d P4/mmm P4/mcc P4/nbm1 P4/nnc1 P4/mbm P4/mnc P4/nmm1 " + "P4/ncc1 P4_2/mmc P4_2/mcm P4_2/nbc P4_2/nnm P4_2/mbc P4_2/mnm P4_2/nmc P4_2/ncm I4/mmm I4/mcm I4_1/amd I4_1/acd " + "P3 P3_1 P3_2 R3H P-3 R-3H P312 P321 P3_112 P3_121 P3_212 P3_221 R32H P3m1 P31m P3c1 P31c R3mH R3cH P-31m P-31c " + "P-3m1 P-3c1 R-3mH R-3cH P6 P6_1 P6_5 P6_2 P6_4 P6_3 P-6 P6/m P6_3/m P622 P6_122 P6_522 P6_222 P6_422 P6_322 " + "P6mm P6cc P6_3cm P6_3mc P-6m2 P-6c2 P-62m P-62c P6/mmm P6/mcc P6_3/mcm P6_3/mmc P23 F23 I23 P2_13 I2_13 Pm-3 " + "Pn-31 Fm-3 Fd-31 Im-3 Pa-3 Ia-3 P432 P4_232 F432 F4_132 I432 P4_332 P4_132 I4_132 P-43m F-43m I-43m P-43n F-43c " + "I-43d Pm-3m Pn-3n1 Pm-3n Pn-3m1 Fm-3m Fm-3c Fd-3m1 Fd-3c1 Im-3m Ia-3d" +).split() + class PointGroupTest(unittest.TestCase): def test_order(self): - order = {"mmm": 8, "432": 24, "-6m2": 12} - for k, v in order.items(): - pg = PointGroup(k) - assert v == len(pg.symmetry_ops) + orders = {"mmm": 8, "432": 24, "-6m2": 12} + for key, val in orders.items(): + assert len(PointGroup(key).symmetry_ops) == val def test_get_orbit(self): - pg = PointGroup("mmm") - assert len(pg.get_orbit([0.1, 0.1, 0.1])) == 8 - assert len(pg.get_orbit([0, 0, 0.1])) == 2 - assert len(pg.get_orbit([1.2, 1.2, 1])) == 8 + pg_mmm = PointGroup("mmm") + assert len(pg_mmm.get_orbit([0.1, 0.1, 0.1])) == 8 + assert len(pg_mmm.get_orbit([0, 0, 0.1])) == 2 + assert len(pg_mmm.get_orbit([1.2, 1.2, 1])) == 8 def test_is_sub_super_group(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - pgmmm = PointGroup("mmm") - pgmm2 = PointGroup("mm2") - pg222 = PointGroup("222") - pg4 = PointGroup("4") - assert pgmmm.is_supergroup(pgmm2) - assert pgmm2.is_subgroup(pgmmm) - assert pgmmm.is_supergroup(pg222) - assert not pgmmm.is_supergroup(pg4) - pgm3m = PointGroup("m-3m") - pg6mmm = PointGroup("6/mmm") - pg3m = PointGroup("-3m") - # TODO: Fix the test below. - # assert pg3m.is_subgroup(pgm3m) - assert pg3m.is_subgroup(pg6mmm) - assert not pgm3m.is_supergroup(pg6mmm) + pg_mmm = PointGroup("mmm") + pg_mm2 = PointGroup("mm2") + pg_222 = PointGroup("222") + pg_4 = PointGroup("4") + assert pg_mmm.is_supergroup(pg_mm2) + assert pg_mm2.is_subgroup(pg_mmm) + assert pg_mmm.is_supergroup(pg_222) + assert not pg_mmm.is_supergroup(pg_4) + pg_m3m = PointGroup("m-3m") + pg_6mmm = PointGroup("6/mmm") + pg_3m = PointGroup("-3m") + # TODO: Fix the test below. + # assert pg3m.is_subgroup(pgm3m) + assert pg_3m.is_subgroup(pg_6mmm) + assert not pg_m3m.is_supergroup(pg_6mmm) class SpaceGroupTest(unittest.TestCase): def test_renamed_e_symbols(self): - sg = SpaceGroup.from_int_number(64) - assert sg.symbol == "Cmce" - for sym, num in ( - ("Aem2", 39), - ("Aea2", 41), - ("Cmce", 64), - ("Cmme", 67), - ("Ccce", 68), - ): + assert SpaceGroup.from_int_number(64).symbol == "Cmce" + + for sym, num in (("Aem2", 39), ("Aea2", 41), ("Cmce", 64), ("Cmme", 67), ("Ccce", 68)): assert SpaceGroup(sym).int_number == num def test_abbrev_symbols(self): @@ -77,8 +83,8 @@ def test_attr(self): assert sg.point_group == "m-3m" def test_point_group_is_set(self): - for i in range(1, 231): - sg = SpaceGroup.from_int_number(i) + for num in range(1, 231): + sg = SpaceGroup.from_int_number(num) assert hasattr(sg, "point_group") for symbol in SYMM_DATA["space_group_encoding"]: @@ -95,8 +101,8 @@ def test_order_symm_ops(self): assert len(sg.symmetry_ops) == sg.order def test_get_settings(self): - assert {"Fm-3m(a-1/4,b-1/4,c-1/4)", "Fm-3m"} == SpaceGroup.get_settings("Fm-3m") - assert { + assert SpaceGroup.get_settings("Fm-3m") == {"Fm-3m(a-1/4,b-1/4,c-1/4)", "Fm-3m"} + assert SpaceGroup.get_settings("Pmmn") == { "Pmmn", "Pmnm:1", "Pnmm:2", @@ -106,8 +112,8 @@ def test_get_settings(self): "Pmmn:1", "Pmnm", "Pmmn:2", - } == SpaceGroup.get_settings("Pmmn") - assert {"Pnmb", "Pman", "Pncm", "Pmna", "Pcnm", "Pbmn"} == SpaceGroup.get_settings("Pmna") + } + assert SpaceGroup.get_settings("Pmna") == {"Pnmb", "Pman", "Pncm", "Pmna", "Pcnm", "Pbmn"} def test_crystal_system(self): sg = SpaceGroup("R-3c") @@ -191,16 +197,13 @@ def test_other_settings(self): SpaceGroup("hello") def test_subgroup_supergroup(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - assert SpaceGroup("Pma2").is_subgroup(SpaceGroup("Pccm")) - assert not SpaceGroup.from_int_number(229).is_subgroup(SpaceGroup.from_int_number(230)) + assert SpaceGroup("Pma2").is_subgroup(SpaceGroup("Pccm")) + assert not SpaceGroup.from_int_number(229).is_subgroup(SpaceGroup.from_int_number(230)) def test_hexagonal(self): - sgs = [146, 148, 155, 160, 161, 166, 167] - for sg in sgs: - s = SpaceGroup.from_int_number(sg, hexagonal=False) - assert not s.symbol.endswith("H") + for num in (146, 148, 155, 160, 161, 166, 167): + sg = SpaceGroup.from_int_number(num, hexagonal=False) + assert not sg.symbol.endswith("H") def test_string(self): sg = SpaceGroup("R-3c") @@ -209,3 +212,14 @@ def test_string(self): assert sg.to_latex_string() == "P6/mmm" sg = SpaceGroup("P4_1") assert sg.to_unicode_string() == "P4₁" + + def test_repr(self): + for num in range(1, 231): + sg = SpaceGroup.from_int_number(num) + symbol = ORDERED_SYMBOLS[num - 1] + assert repr(sg) in f"SpaceGroup({symbol=})" + + def test_raises_on_bad_int_number(self): + for num in (-5, 0, 231, 1000): + with pytest.raises(ValueError, match=f"International number must be between 1 and 230, got {num}"): + SpaceGroup.from_int_number(num) diff --git a/pymatgen/transformations/tests/test_advanced_transformations.py b/pymatgen/transformations/tests/test_advanced_transformations.py index 51f5e4b989c..98b3918ce58 100644 --- a/pymatgen/transformations/tests/test_advanced_transformations.py +++ b/pymatgen/transformations/tests/test_advanced_transformations.py @@ -624,24 +624,22 @@ def test_apply_transformation(self): class GrainBoundaryTransformationTest(PymatgenTest): def test_apply_transformation(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - Al_bulk = Structure.from_spacegroup("Fm-3m", Lattice.cubic(2.8575585), ["Al"], [[0, 0, 0]]) - gb_gen_params_s5 = { - "rotation_axis": [1, 0, 0], - "rotation_angle": 53.13010235415599, - "expand_times": 3, - "vacuum_thickness": 0.0, - "normal": True, - "plane": [0, -1, -3], - "rm_ratio": 0.6, - } - gbg = GrainBoundaryGenerator(Al_bulk) - gb_from_generator = gbg.gb_from_parameters(**gb_gen_params_s5) - gbt_s5 = GrainBoundaryTransformation(**gb_gen_params_s5) - gb_from_trans = gbt_s5.apply_transformation(Al_bulk) - self.assert_all_close(gb_from_generator.lattice.matrix, gb_from_trans.lattice.matrix) - self.assert_all_close(gb_from_generator.cart_coords, gb_from_trans.cart_coords) + Al_bulk = Structure.from_spacegroup("Fm-3m", Lattice.cubic(2.8575585), ["Al"], [[0, 0, 0]]) + gb_gen_params_s5 = { + "rotation_axis": [1, 0, 0], + "rotation_angle": 53.13010235415599, + "expand_times": 3, + "vacuum_thickness": 0.0, + "normal": True, + "plane": [0, -1, -3], + "rm_ratio": 0.6, + } + gbg = GrainBoundaryGenerator(Al_bulk) + gb_from_generator = gbg.gb_from_parameters(**gb_gen_params_s5) + gbt_s5 = GrainBoundaryTransformation(**gb_gen_params_s5) + gb_from_trans = gbt_s5.apply_transformation(Al_bulk) + self.assert_all_close(gb_from_generator.lattice.matrix, gb_from_trans.lattice.matrix) + self.assert_all_close(gb_from_generator.cart_coords, gb_from_trans.cart_coords) class DisorderedOrderedTransformationTest(PymatgenTest): diff --git a/pymatgen/util/tests/test_graph_hashing.py b/pymatgen/util/tests/test_graph_hashing.py index 8ac374a9e05..062109d9926 100644 --- a/pymatgen/util/tests/test_graph_hashing.py +++ b/pymatgen/util/tests/test_graph_hashing.py @@ -38,31 +38,18 @@ import networkx as nx import pytest -try: - from emmet.core.graph_hashing import weisfeiler_lehman_graph_hash, weisfeiler_lehman_subgraph_hashes -except (ImportError, ModuleNotFoundError): - pytest.skip("emmet not installed", allow_module_level=True) - def test_graph_hash(): + pytest.importorskip("emmet.core.graph_hashing") + + from emmet.core.graph_hashing import weisfeiler_lehman_graph_hash + G1 = nx.Graph() - G1.add_edges_from( - [ - (1, 2, {"label": "A"}), - (2, 3, {"label": "A"}), - (3, 1, {"label": "A"}), - (1, 4, {"label": "B"}), - ] - ) + edges = [(1, 2, {"label": "A"}), (2, 3, {"label": "A"}), (3, 1, {"label": "A"}), (1, 4, {"label": "B"})] + G1.add_edges_from(edges) G2 = nx.Graph() - G2.add_edges_from( - [ - (5, 6, {"label": "B"}), - (6, 7, {"label": "A"}), - (7, 5, {"label": "A"}), - (7, 8, {"label": "A"}), - ] - ) + edges = [(5, 6, {"label": "B"}), (6, 7, {"label": "A"}), (7, 5, {"label": "A"}), (7, 8, {"label": "A"})] + G2.add_edges_from(edges) assert weisfeiler_lehman_graph_hash(G1) == weisfeiler_lehman_graph_hash(G2) assert weisfeiler_lehman_graph_hash(G1, edge_attr="label") == "c653d85538bcf041d88c011f4f905f10" @@ -70,6 +57,10 @@ def test_graph_hash(): def test_subgraph_hashes(): + pytest.importorskip("emmet.core.graph_hashing") + + from emmet.core.graph_hashing import weisfeiler_lehman_subgraph_hashes + G1 = nx.Graph() G1.add_edges_from([(1, 2), (2, 3), (2, 4), (3, 5), (4, 6), (5, 7), (6, 7)]) G2 = nx.Graph() diff --git a/setup.py b/setup.py index 6d423580d3e..cc2aa24695c 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,8 @@ "pandas", "plotly>=4.5.0", "pybtex", + # TODO remove after https://github.com/materialsproject/emmet/issues/768 is fixed + "pydantic<2.0.0", "requests", "ruamel.yaml>=0.17.0", "scipy>=1.5.0",