matter-os / core /elements.py
tmepass000's picture
MATTER OS v0.1.0
87b48de verified
Raw History Blame Contribute Delete
3.52 kB
"""Periodic-table / isotope registry with charge, spin, electron-count metadata (decision 42).
Data source: ase.data (atomic numbers, standard atomic masses, covalent radii; ASE is LGPL-2.1+).
Common oxidation states are a small curated table for screening only. Isotopes beyond the
standard-mass entry are not tabulated here: requests for specific isotopes are recorded as
metadata and flagged (`isotope_data: not_tabulated`) rather than given invented masses.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from ase.data import atomic_masses, atomic_numbers, chemical_symbols, covalent_radii
# Valence electrons (main-group counting; transition metals: group number up to 12)
_MAIN_VALENCE = {
"H": 1, "He": 2, "Li": 1, "Be": 2, "B": 3, "C": 4, "N": 5, "O": 6, "F": 7, "Ne": 8,
"Na": 1, "Mg": 2, "Al": 3, "Si": 4, "P": 5, "S": 6, "Cl": 7, "Ar": 8,
"K": 1, "Ca": 2, "Ga": 3, "Ge": 4, "As": 5, "Se": 6, "Br": 7, "Kr": 8,
"Sc": 3, "Ti": 4, "V": 5, "Cr": 6, "Mn": 7, "Fe": 8, "Co": 9, "Ni": 10, "Cu": 11, "Zn": 12,
"Ag": 11, "Au": 11, "Pd": 10, "Pt": 10, "Rb": 1, "Sr": 2, "In": 3, "Sn": 4, "Sb": 5, "Te": 6, "I": 7, "Xe": 8,
}
COMMON_OXIDATION_STATES = {
"H": [-1, 1], "Li": [1], "Be": [2], "B": [3], "C": [-4, 2, 4], "N": [-3, 3, 5], "O": [-2], "F": [-1],
"Na": [1], "Mg": [2], "Al": [3], "Si": [-4, 4], "P": [-3, 3, 5], "S": [-2, 2, 4, 6], "Cl": [-1, 1, 5, 7],
"K": [1], "Ca": [2], "Ti": [2, 3, 4], "V": [2, 3, 4, 5], "Cr": [2, 3, 6], "Mn": [2, 3, 4, 7],
"Fe": [2, 3], "Co": [2, 3], "Ni": [2], "Cu": [1, 2], "Zn": [2], "Ag": [1], "Au": [1, 3], "Pd": [2, 4], "Pt": [2, 4],
}
NOBLE_GASES = {"He", "Ne", "Ar", "Kr", "Xe", "Rn"}
@dataclass(frozen=True)
class ElementInfo:
symbol: str
Z: int
mass_amu: float
covalent_radius_ang: float
valence_electrons: int | None
common_oxidation_states: list[int] = field(default_factory=list)
flags: list[str] = field(default_factory=list)
def element(symbol: str) -> ElementInfo:
if symbol not in atomic_numbers or symbol == "X":
raise ValueError(f"unknown element symbol: {symbol}")
z = atomic_numbers[symbol]
flags = []
if z > 86:
flags.append("heavy_element_relativistic_effects_not_handled_by_default_solvers")
if z > 103:
flags.append("superheavy_regime_unsupported")
val = _MAIN_VALENCE.get(symbol)
if val is None:
flags.append("valence_count_not_tabulated")
return ElementInfo(
symbol=symbol,
Z=z,
mass_amu=float(atomic_masses[z]),
covalent_radius_ang=float(covalent_radii[z]),
valence_electrons=val,
common_oxidation_states=list(COMMON_OXIDATION_STATES.get(symbol, [])),
flags=flags,
)
def all_symbols() -> list[str]:
return [s for s in chemical_symbols[1:]]
def electron_count(symbols: list[str], charge: int = 0) -> int:
return sum(atomic_numbers[s] for s in symbols) - int(charge)
def spin_parity_ok(n_electrons: int, multiplicity: int) -> bool:
"""2S+1 must have parity consistent with electron count: even N -> odd multiplicity."""
return (n_electrons % 2) == ((multiplicity - 1) % 2)
def isotope_metadata(symbol: str, mass_number: int | None) -> dict:
info = element(symbol)
if mass_number is None:
return {"symbol": symbol, "mass_amu": info.mass_amu, "isotope_data": "standard_atomic_weight"}
return {"symbol": symbol, "mass_number": mass_number, "mass_amu": None, "isotope_data": "not_tabulated"}