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

Deprecate CifParser.get_structures() in favor of new parse_structures in which primitive defaults to False #3419

Merged
merged 10 commits into from
Nov 8, 2023
7 changes: 5 additions & 2 deletions pymatgen/core/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, Sequence
from pathlib import Path

from ase import Atoms
from ase.calculators.calculator import Calculator
Expand Down Expand Up @@ -2811,15 +2812,17 @@ def from_str(
return cls.from_sites(struct, properties=struct.properties)

@classmethod
def from_file(cls, filename, primitive=False, sort=False, merge_tol=0.0, **kwargs) -> Structure | IStructure:
def from_file(
cls, filename: str | Path, primitive: bool = False, sort: bool = False, merge_tol: float = 0.0, **kwargs
) -> Structure | IStructure:
"""Reads a structure from a file. For example, anything ending in
a "cif" is assumed to be a Crystallographic Information Format file.
Supported formats include CIF, POSCAR/CONTCAR, CHGCAR, LOCPOT,
vasprun.xml, CSSR, Netcdf and pymatgen's JSON-serialized structures.

Args:
filename (str): The filename to read from.
primitive (bool): Whether to convert to a primitive cell. Only available for CIFs. Defaults to False.
primitive (bool): Whether to convert to a primitive cell. Defaults to False.
sort (bool): Whether to sort sites. Default to False.
merge_tol (float): If this is some positive number, sites that are within merge_tol from each other will be
merged. Usually 0.01 should be enough to deal with common numerical issues.
Expand Down
50 changes: 36 additions & 14 deletions pymatgen/io/cif.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import textwrap
import warnings
from collections import deque
from datetime import datetime
from functools import partial
from inspect import getfullargspec as getargspec
from io import StringIO
Expand Down Expand Up @@ -53,7 +54,7 @@ class CifBlock:
attribute.
"""

maxlen = 70 # not quite 80 so we can deal with semicolons and things
max_len = 70 # not quite 80 so we can deal with semicolons and things

def __init__(self, data, loops, header):
"""
Expand Down Expand Up @@ -94,7 +95,7 @@ def __str__(self):
if key not in written:
# k didn't belong to a loop
v = self._format_field(self.data[key])
if len(key) + len(v) + 3 < self.maxlen:
if len(key) + len(v) + 3 < self.max_len:
out.append(f"{key} {v}")
else:
out.extend([key, v])
Expand All @@ -110,7 +111,7 @@ def _loop_to_string(self, loop):
if val[0] == ";":
out += line + "\n" + val
line = "\n"
elif len(line) + len(val) + 2 < self.maxlen:
elif len(line) + len(val) + 2 < self.max_len:
line += " " + val
else:
out += line
Expand All @@ -120,8 +121,8 @@ def _loop_to_string(self, loop):

def _format_field(self, v):
v = str(v).strip()
if len(v) > self.maxlen:
return ";\n" + textwrap.fill(v, self.maxlen) + "\n;"
if len(v) > self.max_len:
return ";\n" + textwrap.fill(v, self.max_len) + "\n;"
# add quotes if necessary
if v == "":
return '""'
Expand Down Expand Up @@ -1137,18 +1138,31 @@ def get_matching_coord(coord):
return struct
return None

def get_structures(
@np.deprecate(
message="get_structures is deprecated and will be removed in 2024. Use parse_structures instead."
"The only difference is that primitive defaults to False in the new parse_structures method."
"So parse_structures(primitive=True) is equivalent to get_structures().",
)
def get_structures(self, *args, **kwargs) -> list[Structure]:
"""
Deprecated. Use parse_structures instead. Only difference between the two methods is the
default primitive=False in parse_structures.
janosh marked this conversation as resolved.
Show resolved Hide resolved
"""
kwargs.setdefault("primitive", True)
return self.parse_structures(*args, **kwargs)

def parse_structures(
self,
primitive: bool = True,
primitive: bool = False,
symmetrized: bool = False,
check_occu: bool = True,
on_error: Literal["ignore", "warn", "raise"] = "warn",
) -> list[Structure]:
"""Return list of structures in CIF file.

Args:
primitive (bool): Set to False to return conventional unit cells.
Defaults to True. With magnetic CIF files, will return primitive
primitive (bool): Set to True to return primitive unit cells.
Defaults to False. With magnetic CIF files, True will return primitive
magnetic cell which may be larger than nuclear primitive cell.
symmetrized (bool): If True, return a SymmetrizedStructure which will
include the equivalent indices and symmetry operations used to
Expand All @@ -1168,6 +1182,14 @@ def get_structures(
Returns:
list[Structure]: All structures in CIF file.
"""
if os.getenv("CI") and datetime.now() > datetime(2024, 3, 1): # March 2024 seems long enough # pragma: no cover
raise RuntimeError("remove the change of default primitive=True to False made on 2023-10-24")
warnings.warn(
"The default value of primitive was changed from True to False in "
"https://github.com/materialsproject/pymatgen/pull/3419. CifParser now returns the cell "
"in the CIF file as is. If you want the primitive cell, please set primitive=True explicitly.",
UserWarning,
)
if not check_occu: # added in https://github.com/materialsproject/pymatgen/pull/2836
warnings.warn("Structures with unphysical site occupancies are not compatible with many pymatgen features.")
if primitive and symmetrized:
Expand Down Expand Up @@ -1331,18 +1353,18 @@ def __init__(
# primitive to conventional structures, the standard for CIF.
struct = sf.get_refined_structure()

latt = struct.lattice
lattice = struct.lattice
comp = struct.composition
no_oxi_comp = comp.element_composition
block["_symmetry_space_group_name_H-M"] = spacegroup[0]
for cell_attr in ["a", "b", "c"]:
block["_cell_length_" + cell_attr] = format_str.format(getattr(latt, cell_attr))
block["_cell_length_" + cell_attr] = format_str.format(getattr(lattice, cell_attr))
for cell_attr in ["alpha", "beta", "gamma"]:
block["_cell_angle_" + cell_attr] = format_str.format(getattr(latt, cell_attr))
block["_cell_angle_" + cell_attr] = format_str.format(getattr(lattice, cell_attr))
block["_symmetry_Int_Tables_number"] = spacegroup[1]
block["_chemical_formula_structural"] = no_oxi_comp.reduced_formula
block["_chemical_formula_sum"] = no_oxi_comp.formula
block["_cell_volume"] = format_str.format(latt.volume)
block["_cell_volume"] = format_str.format(lattice.volume)

reduced_comp, fu = no_oxi_comp.get_reduced_composition_and_factor()
block["_cell_formula_units_Z"] = str(int(fu))
Expand Down Expand Up @@ -1408,7 +1430,7 @@ def __init__(

magmom = Magmom(mag)
if write_magmoms and abs(magmom) > 0:
moment = Magmom.get_moment_relative_to_crystal_axes(magmom, latt)
moment = Magmom.get_moment_relative_to_crystal_axes(magmom, lattice)
atom_site_moment_label.append(f"{sp.symbol}{count}")
atom_site_moment_crystalaxis_x.append(format_str.format(moment[0]))
atom_site_moment_crystalaxis_y.append(format_str.format(moment[1]))
Expand Down
5 changes: 5 additions & 0 deletions tests/core/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)
from pymatgen.electronic_structure.core import Magmom
from pymatgen.io.ase import AseAtomsAdaptor
from pymatgen.io.cif import CifParser
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from pymatgen.util.testing import TEST_FILES_DIR, PymatgenTest

Expand Down Expand Up @@ -780,6 +781,10 @@ def test_to_from_file_string(self):
struct = Structure.from_file(f"{TEST_FILES_DIR}/bad-unicode-gh-2947.mcif")
assert struct.formula == "Ni32 O32"

# make sure CIfParser.get_structures() and Structure.from_file() are consistent
# i.e. uses same merge_tol for site merging, same primitive=False, etc.
assert struct == CifParser(f"{TEST_FILES_DIR}/bad-unicode-gh-2947.mcif").parse_structures()[0]

def test_to_file_alias(self):
out_path = f"{self.tmp_path}/POSCAR"
assert self.struct.to(out_path) == self.struct.to_file(out_path)
Expand Down