Source code for atom_hifi.training.force_truth
"""Force-resolution helpers for ε_F microstate references."""
from __future__ import annotations
import json
from functools import lru_cache
from importlib import resources
import numpy as np
from ase.data import atomic_numbers
[docs]
@lru_cache(maxsize=1)
def load_kappa_table() -> dict:
"""Load packaged QHO force-stiffness table."""
path = resources.files("atom_hifi").joinpath(
"data/qho_freq_table_v1_kappa_msq.json")
with path.open(encoding="utf-8") as fh:
return json.load(fh)
[docs]
def kappa_table_version() -> str:
return str(load_kappa_table().get("_meta", {}).get("version", "unknown"))
[docs]
def qho_force_kappa(element: str) -> float:
"""Return QHO κ = m<ω²> for force-resolution scaling (eV/Ų)."""
if element not in atomic_numbers:
raise ValueError(f"Unknown element symbol: {element!r}")
table = load_kappa_table()
entry = table.get("elements", {}).get(element)
if entry is None:
raise ValueError(f"No QHO force κ table entry for {element!r}.")
kappa = float(entry["kappa_stiff_eV_A2"])
if kappa <= 0:
raise ValueError(f"QHO force κ for {element!r} must be positive.")
return kappa
[docs]
def force_eps_from_curve(
element: str,
*,
f_tol: float,
sigma_rattle,
mean_eps,
) -> tuple[float, float]:
"""Interpolate ε_F from cached ε(σ) calibration curve.
Returns ``(eps_F, u_star)`` where ``u_star = F_tol / κ``.
"""
f_tol = float(f_tol)
if f_tol <= 0:
raise ValueError(f"f_tol must be positive, got {f_tol!r}.")
sigma = np.asarray(sigma_rattle, dtype=float)
eps = np.asarray(mean_eps, dtype=float)
if sigma.ndim != 1 or eps.ndim != 1 or len(sigma) != len(eps):
raise ValueError("sigma_rattle and mean_eps must be 1-D arrays of equal length.")
if len(sigma) < 2:
raise ValueError("ε_F interpolation requires at least two ε(σ) points.")
order = np.argsort(sigma)
sigma = sigma[order]
eps = eps[order]
if np.any(np.diff(sigma) <= 0):
raise ValueError("sigma_rattle values must be unique.")
u_star = f_tol / qho_force_kappa(element)
if u_star < sigma[0] or u_star > sigma[-1]:
raise ValueError(
f"u*={u_star:.6g} Å for {element} lies outside cached ε(σ) grid "
f"[{sigma[0]:.6g}, {sigma[-1]:.6g}] Å; rerun thermal ε calibration "
"with a wider sigma_rattle grid.")
return float(np.interp(u_star, sigma, eps)), float(u_star)