Skip to content

Commit

Permalink
Merge branch 'master' into test-monty-reverse-read
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielYang59 authored Jan 29, 2025
2 parents d9906fa + bf6c108 commit dcd92e5
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 158 deletions.
61 changes: 27 additions & 34 deletions docs/CHANGES.md

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

8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ dependencies = [
"networkx>=2.7", # PR4116
"palettable>=3.3.3",
"pandas>=2",
"plotly>=4.5.0",
"plotly>=4.5.0,<6.0.0",
"pybtex>=0.24.0",
"requests>=2.32",
"ruamel.yaml>=0.17.0",
"scipy>=1.13.0",
# scipy<1.14.1 is incompatible with NumPy 2.0 on Windows
# https://github.com/scipy/scipy/issues/21052
"scipy>=1.14.1; platform_system == 'Windows'",
"spglib>=2.5.0",
"spglib>=2.5",
"sympy>=1.3", # PR #4116
"tabulate>=0.9",
"tqdm>=4.60",
Expand All @@ -89,7 +89,7 @@ Pypi = "https://pypi.org/project/pymatgen"
[project.optional-dependencies]
abinit = ["netcdf4>=1.7.2"]
ase = ["ase>=3.23.0"]
ci = ["pytest-cov>=4", "pytest-split>=0.8", "pytest>=8"]
ci = ["pytest-cov>=4", "pytest-split>=0.8", "pytest>=8", "pymatgen[symmetry]"]
docs = ["invoke", "sphinx", "sphinx_markdown_builder", "sphinx_rtd_theme"]
electronic_structure = ["fdint>=2.0.2"]
mlp = ["chgnet>=0.3.8", "matgl>=1.1.3"]
Expand All @@ -110,6 +110,8 @@ optional = [
"phonopy>=2.33.3",
"seekpath>=2.0.1",
]
# moyopy[interface] includes ase
symmetry = ["moyopy[interface]>=0.3", "spglib>=2.5"]
# tblite only support Python 3.12+ through conda-forge
# https://github.com/tblite/tblite/issues/175
tblite = [ "tblite[ase]>=0.3.0; platform_system=='Linux' and python_version<'3.12'"]
Expand Down
97 changes: 53 additions & 44 deletions src/pymatgen/core/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from pymatgen.util.string import Stringify, formula_double_format

if TYPE_CHECKING:
from collections.abc import Generator, ItemsView, Iterator
from collections.abc import Generator, ItemsView, Iterator, Mapping
from typing import Any, ClassVar, Literal

from typing_extensions import Self
Expand Down Expand Up @@ -115,11 +115,11 @@ class Composition(collections.abc.Hashable, collections.abc.Mapping, MSONable, S
# Tolerance in distinguishing different composition amounts.
# 1e-8 is fairly tight, but should cut out most floating point arithmetic
# errors.
amount_tolerance = 1e-8
charge_balanced_tolerance = 1e-8
amount_tolerance: ClassVar[float] = 1e-8
charge_balanced_tolerance: ClassVar[float] = 1e-8

# Special formula handling for peroxides and certain elements. This is so
# that formula output does not write LiO instead of Li2O2 for example.
# that formula output does not write "LiO" instead of "Li2O2" for example.
special_formulas: ClassVar[dict[str, str]] = {
"LiO": "Li2O2",
"NaO": "Na2O2",
Expand All @@ -134,7 +134,8 @@ class Composition(collections.abc.Hashable, collections.abc.Mapping, MSONable, S
"H": "H2",
}

oxi_prob = None # prior probability of oxidation used by oxi_state_guesses
# Prior probability of oxidation used by oxi_state_guesses
oxi_prob: ClassVar[dict | None] = None

def __init__(self, *args, strict: bool = False, **kwargs) -> None:
"""Very flexible Composition construction, similar to the built-in Python
Expand Down Expand Up @@ -417,35 +418,38 @@ def get_reduced_composition_and_factor(self) -> tuple[Self, float]:
"""Calculate a reduced composition and factor.
Returns:
A normalized composition and a multiplicative factor, i.e.,
Li4Fe4P4O16 returns (Composition("LiFePO4"), 4).
tuple[Composition, float]: Normalized Composition and multiplicative factor,
i.e. "Li4Fe4P4O16" returns (Composition("LiFePO4"), 4).
"""
factor = self.get_reduced_formula_and_factor()[1]
factor: float = self.get_reduced_formula_and_factor()[1]
return self / factor, factor

def get_reduced_formula_and_factor(self, iupac_ordering: bool = False) -> tuple[str, float]:
"""Calculate a reduced formula and factor.
Args:
iupac_ordering (bool, optional): Whether to order the
formula by the iupac "electronegativity" series, defined in
formula by the IUPAC "electronegativity" series, defined in
Table VI of "Nomenclature of Inorganic Chemistry (IUPAC
Recommendations 2005)". This ordering effectively follows
the groups and rows of the periodic table, except the
the groups and rows of the periodic table, except for
Lanthanides, Actinides and hydrogen. Note that polyanions
will still be determined based on the true electronegativity of
the elements.
Returns:
A pretty normalized formula and a multiplicative factor, i.e.,
Li4Fe4P4O16 returns (LiFePO4, 4).
tuple[str, float]: Normalized formula and multiplicative factor,
i.e., "Li4Fe4P4O16" returns (LiFePO4, 4).
"""
all_int = all(abs(val - round(val)) < type(self).amount_tolerance for val in self.values())
all_int: bool = all(abs(val - round(val)) < type(self).amount_tolerance for val in self.values())
if not all_int:
return self.formula.replace(" ", ""), 1
el_amt_dict = {key: round(val) for key, val in self.get_el_amt_dict().items()}

el_amt_dict: dict[str, int] = {key: round(val) for key, val in self.get_el_amt_dict().items()}
factor: float
formula, factor = reduce_formula(el_amt_dict, iupac_ordering=iupac_ordering)

# Do not "completely reduce" certain formulas
if formula in type(self).special_formulas:
formula = type(self).special_formulas[formula]
factor /= 2
Expand All @@ -458,10 +462,10 @@ def get_integer_formula_and_factor(
"""Calculate an integer formula and factor.
Args:
max_denominator (int): all amounts in the el:amt dict are
first converted to a Fraction with this maximum denominator
max_denominator (int): all amounts in the el_amt dict are
first converted to a Fraction with this maximum denominator.
iupac_ordering (bool, optional): Whether to order the
formula by the iupac "electronegativity" series, defined in
formula by the IUPAC "electronegativity" series, defined in
Table VI of "Nomenclature of Inorganic Chemistry (IUPAC
Recommendations 2005)". This ordering effectively follows
the groups and rows of the periodic table, except the
Expand All @@ -470,13 +474,14 @@ def get_integer_formula_and_factor(
the elements.
Returns:
A pretty normalized formula and a multiplicative factor, i.e.,
Li0.5O0.25 returns (Li2O, 0.25). O0.25 returns (O2, 0.125)
A normalized formula and a multiplicative factor, i.e.,
"Li0.5O0.25" returns (Li2O, 0.25). "O0.25" returns (O2, 0.125)
"""
el_amt = self.get_el_amt_dict()
_gcd = gcd_float(list(el_amt.values()), 1 / max_denominator)
el_amt: dict[str, float] = self.get_el_amt_dict()
_gcd: float = gcd_float(list(el_amt.values()), 1 / max_denominator)

dct = {key: round(val / _gcd) for key, val in el_amt.items()}
dct: dict[str, int] = {key: round(val / _gcd) for key, val in el_amt.items()}
factor: float
formula, factor = reduce_formula(dct, iupac_ordering=iupac_ordering)
if formula in type(self).special_formulas:
formula = type(self).special_formulas[formula]
Expand All @@ -485,8 +490,8 @@ def get_integer_formula_and_factor(

@property
def reduced_formula(self) -> str:
"""A pretty normalized formula, i.e., LiFePO4 instead of
Li4Fe4P4O16.
"""A normalized formula, i.e., "LiFePO4" instead of
"Li4Fe4P4O16".
"""
return self.get_reduced_formula_and_factor()[0]

Expand Down Expand Up @@ -1055,7 +1060,7 @@ def _get_oxi_state_guesses(
raise ValueError(f"Composition {comp} cannot accommodate max_sites setting!")

# Load prior probabilities of oxidation states, used to rank solutions
if not type(self).oxi_prob:
if type(self).oxi_prob is None:
all_data = loadfn(f"{MODULE_DIR}/../analysis/icsd_bv.yaml")
type(self).oxi_prob = {Species.from_str(sp): data for sp, data in all_data["occurrence"].items()}
oxi_states_override = oxi_states_override or {}
Expand Down Expand Up @@ -1319,38 +1324,38 @@ def _parse_chomp_and_rank(match, formula: str, m_dict: dict[str, float], m_point


def reduce_formula(
sym_amt: dict[str, float] | dict[str, int],
sym_amt: Mapping[str, float],
iupac_ordering: bool = False,
) -> tuple[str, float]:
"""Helper function to reduce a sym_amt dict to a reduced formula and factor.
) -> tuple[str, int]:
"""Helper function to reduce a symbol-amount mapping.
Args:
sym_amt (dict): {symbol: amount}.
sym_amt (dict[str, float]): Symbol to amount mapping.
iupac_ordering (bool, optional): Whether to order the
formula by the iupac "electronegativity" series, defined in
formula by the IUPAC "electronegativity" series, defined in
Table VI of "Nomenclature of Inorganic Chemistry (IUPAC
Recommendations 2005)". This ordering effectively follows
the groups and rows of the periodic table, except the
the groups and rows of the periodic table, except for
Lanthanides, Actinides and hydrogen. Note that polyanions
will still be determined based on the true electronegativity of
the elements.
Returns:
tuple[str, float]: reduced formula and factor.
tuple[str, int]: reduced formula and factor.
"""
syms = sorted(sym_amt, key=lambda x: [get_el_sp(x).X, x])
syms: list[str] = sorted(sym_amt, key=lambda x: [get_el_sp(x).X, x])

syms = list(filter(lambda x: abs(sym_amt[x]) > Composition.amount_tolerance, syms))

factor = 1
# Enforce integers for doing gcd.
# Enforce integer for calculating greatest common divisor
factor: int = 1
if all(int(i) == i for i in sym_amt.values()):
factor = abs(gcd(*(int(i) for i in sym_amt.values())))

poly_anions = []
# if the composition contains a poly anion
# If the composition contains polyanion
poly_anions: list[str] = []
if len(syms) >= 3 and get_el_sp(syms[-1]).X - get_el_sp(syms[-2]).X < 1.65:
poly_sym_amt = {syms[i]: sym_amt[syms[i]] / factor for i in [-2, -1]}
poly_sym_amt: dict[str, float] = {syms[i]: sym_amt[syms[i]] / factor for i in (-2, -1)}
poly_form, poly_factor = reduce_formula(poly_sym_amt, iupac_ordering=iupac_ordering)

if poly_factor != 1:
Expand All @@ -1369,9 +1374,17 @@ def reduce_formula(
return "".join([*reduced_form, *poly_anions]), factor


class CompositionError(Exception):
"""Composition exceptions."""


class ChemicalPotential(dict, MSONable):
"""Represent set of chemical potentials. Can be: multiplied/divided by a Number
multiplied by a Composition (returns an energy) added/subtracted with other ChemicalPotentials.
"""Represent set of chemical potentials.
Can be:
- multiplied/divided by a Number
- multiplied by a Composition (returns an energy)
- added/subtracted with other ChemicalPotentials
"""

def __init__(self, *args, **kwargs) -> None:
Expand Down Expand Up @@ -1424,7 +1437,3 @@ def get_energy(self, composition: Composition, strict: bool = True) -> float:
if strict and (missing := set(composition) - set(self)):
raise ValueError(f"Potentials not specified for {missing}")
return sum(self.get(key, 0) * val for key, val in composition.items())


class CompositionError(Exception):
"""Exception class for composition errors."""
2 changes: 1 addition & 1 deletion src/pymatgen/core/ion.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def get_reduced_formula_and_factor(
Args:
iupac_ordering (bool, optional): Whether to order the
formula by the iupac "electronegativity" series, defined in
formula by the IUPAC "electronegativity" series, defined in
Table VI of "Nomenclature of Inorganic Chemistry (IUPAC
Recommendations 2005)". This ordering effectively follows
the groups and rows of the periodic table, except the
Expand Down
Loading

0 comments on commit dcd92e5

Please sign in to comment.