Files
beta_shape_pnpi/beta_spectrum/components/exchange.py
T
unzhakov 330ebddaef refactor: refine CLI options and enhance debug logging
- Remove --mode, --transition-type, and --decay-index CLI flags
  (parameters now come from ENSDF data or custom JSON input)
- Clarify energy units in all CLI help text (MeV throughout, keV for detector params)
- Enhance logging verbosity:
  - -v (INFO): shows source, component initialization, workflow steps
  - -vv (DEBUG): shows detailed internals of all spectrum components
    (parameters, evaluation counts, input/output ranges)
- Improve CSV headers with element notation (e.g. Tc99 -> Ru99)
- Add CONVENTIONS.md documenting energy unit and logging conventions
- Update TODO.md with completion status
2026-05-05 09:08:08 +03:00

108 lines
2.9 KiB
Python

from pathlib import Path
from typing import Dict, Optional
import logging
import numpy as np
from beta_spectrum.base import SpectrumComponent
class ExchangeCorrection(SpectrumComponent):
"""
Atomic xchange correction using Hayen 2018 Table X coefficients (App. G)
"""
def __init__(
self,
Z: int,
filename: Optional[str] = None,
eps: float = 1e-6,
logger: Optional[logging.Logger] = None,
):
super().__init__(logger=logger)
self.Z = Z
self.eps = eps
if filename is None:
filename = str(self._default_coeff_path())
self.coeffs = self._load_coeffs(filename)
if self._logger:
self._logger.debug(
"ExchangeCorrection: Z=%d, coefficients loaded from %s",
Z,
filename,
)
def _load_coeffs(self, filename: str) -> Dict[str, float]:
data = np.genfromtxt(filename, delimiter=",", names=True)
for row in data:
if int(row["Z"]) == self.Z:
return {
"a": float(row["a"]),
"b": float(row["b"]),
"c": float(row["c"]),
"d": float(row["d"]),
"e": float(row["e"]),
"f": float(row["f"]),
"g": float(row["g"]),
"h": float(row["h"]),
"i": float(row["i"]),
}
raise ValueError(f"No exchange coefficients available for Z={self.Z}")
def _default_coeff_path(self) -> Path:
return Path(__file__).resolve().parent.parent / "data" / "exchange_coeff.csv"
def __call__(self, W: np.ndarray) -> np.ndarray:
if self._logger:
self._logger.debug(
"ExchangeCorrection: evaluating at %d points, W range=[%.4f, %.4f]",
len(W),
W.min(),
W.max(),
)
W = np.asarray(W)
# Define physical cutoff
W_cut = 1.003
# Compute full expression ONLY above cutoff
W_safe = np.maximum(W, W_cut)
Wp = W_safe - 1.0
a = self.coeffs["a"]
b = self.coeffs["b"]
c = self.coeffs["c"]
d = self.coeffs["d"]
e = self.coeffs["e"]
f_coef = self.coeffs["f"]
g = self.coeffs["g"]
h = self.coeffs["h"]
i = self.coeffs["i"]
term1 = a / Wp
term2 = b / (Wp**2)
term3 = c * np.exp(-d * Wp)
base = np.maximum(W_safe - f_coef, self.eps)
term4 = e * np.sin((base**g + h)) / (W_safe**i)
X_safe = 1.0 + term1 + term2 + term3 + term4
result = np.asarray(X_safe, dtype=np.float64)
if self._logger:
self._logger.debug(
"ExchangeCorrection: output range=[%.6e, %.6e]",
result.min(),
result.max(),
)
return result