Source code for smact.metallicity
"""Utility functions for handling intermetallic compounds in SMACT."""
from __future__ import annotations
from typing import TYPE_CHECKING, cast
import numpy as np
if TYPE_CHECKING:
from collections.abc import Collection
__all__ = [
"get_d_block_element_fraction",
"get_distinct_metal_count",
"get_element_fraction",
"get_metal_fraction",
"get_pauling_test_mismatch",
"metallicity_score",
]
from pymatgen.core import Composition
import smact
from smact import Element
from smact.properties import valence_electron_count
def _ensure_composition(composition: str | Composition) -> Composition:
"""Convert input to a pymatgen Composition if it isn't already.
Args:
composition: Chemical formula as string or pymatgen Composition
Returns:
Composition: A pymatgen Composition object
Raises:
ValueError: If the composition string is empty
ValueError: If the formula is invalid and can't be parsed.
"""
if isinstance(composition, str):
if not composition.strip():
msg = "Empty composition"
raise ValueError(msg)
# Try to parse with pymatgen
try:
return Composition(composition)
except ValueError as exc:
# If pymatgen can't parse, re-raise with a message the test expects
msg = "Invalid formula"
raise ValueError(msg) from exc
return composition
[docs]
def get_element_fraction(composition: str | Composition, element_set: Collection[str]) -> float:
"""Calculate the fraction of elements from a given set in a composition.
This helper function is used to avoid code duplication in functions that
calculate fractions of specific element types (e.g., metals, d-block elements).
Args:
composition: Chemical formula as string or pymatgen Composition
element_set: Set of element symbols to check for
Returns:
float: Fraction of the composition that consists of elements from the set (0-1)
"""
comp = _ensure_composition(composition)
total_amt = sum(comp.values())
if total_amt == 0:
return 0.0
target_amt = sum(amt for el, amt in comp.items() if el.symbol in element_set)
return target_amt / total_amt
[docs]
def get_d_block_element_fraction(composition: str | Composition) -> float:
"""Calculate the fraction of d-block elements in a composition.
Implemented using get_element_fraction helper with smact.d_block set.
"""
return get_element_fraction(composition, smact.d_block)
[docs]
def get_pauling_test_mismatch(composition: str | Composition) -> float:
"""Calculate a score for how much the composition deviates from ideal Pauling electronegativity ordering.
Higher mismatch => more difference (ionic, e.g. NaCl).
Lower mismatch => metal-metal bonds (e.g. Fe-Al).
Returns:
float: Mismatch score (0=perfect match, higher=more deviation, NaN=missing data)
"""
comp = _ensure_composition(composition)
elements = [Element(el.symbol) for el in comp.elements]
electronegativities = [el.pauling_eneg for el in elements]
# If any element lacks a known electronegativity, return NaN
if any(e is None or e <= 0 for e in electronegativities):
return float("nan")
# At this point all values are known to be positive floats
eneg_values = cast("list[float]", electronegativities)
mismatches: list[float] = [
abs(eneg1 - eneg2) for i, eneg1 in enumerate(eneg_values) for eneg2 in eneg_values[i + 1 :]
]
# Return average mismatch across all unique pairs
return float(np.mean(mismatches)) if mismatches else 0.0