fix(nuclear_data): use measured transition endpoints per branch

paceENSDF stores the measured beta endpoint energy of each transition
(ENSDF column 7). The parser instead derived branch endpoints as
base-endpoint minus level energy with the base taken from an arbitrary
first-listed branch, so Co60 produced negative branch endpoints and a
hard ValueError (bs_pnpi Co60 exited 1 regardless of cutoff).

- BranchInfo carries the measured endpoint_MeV
- overall endpoint = max measured endpoint over all branches
- decay_info_to_config prefers measured endpoints; the subtraction is
  retained only as a legacy fallback for hand-built DecayInfo objects
- Co60/Tc99/Cs137 endpoint tests + CLI dry-run/export tests (D3)
This commit is contained in:
2026-10-02 08:42:51 +03:00
parent 9c3dd80b5c
commit 3590ca91a1
3 changed files with 90 additions and 18 deletions
+38
View File
@@ -294,6 +294,44 @@ class TestDecayInfoToConfig:
# ---------------------------------------------------------------------------
@pytest.mark.slow
@pytest.mark.requires_ensdf
@pytest.mark.skipif(not _has_paceENSDF(), reason="paceENSDF not installed")
class TestPaceENSDFBranchEndpoints:
"""Per-branch endpoints are the measured transition endpoint energies
(ENSDF column 7), not base-endpoint minus level energy subtractions
with a base taken from an arbitrary first branch (D3)."""
def _branch_endpoints(self, nuclide: str) -> list[float]:
info = get_decay_info_from_paceENSDF(nuclide, "beta_minus")
cfg = decay_info_to_config(info)
assert cfg.branches is not None
return sorted(
(b.endpoint_MeV for b in cfg.branches),
reverse=True,
)
def test_co60_branch_endpoints_positive(self) -> None:
# Measured endpoints: 1492.0, 670.0, 317.88 keV (levels above the
# first branch's endpoint previously produced negative values and
# a hard ValueError from BranchConfig validation).
eps = self._branch_endpoints("Co60")
assert eps == pytest.approx([1.492, 0.670, 0.31788], abs=1e-6)
assert all(e > 0 for e in eps)
def test_tc99_branch_endpoints(self) -> None:
eps = self._branch_endpoints("Tc99")
assert eps == pytest.approx([0.2975, 0.2080], abs=1e-6)
def test_cs137_branch_endpoints(self) -> None:
eps = self._branch_endpoints("Cs137")
assert eps == pytest.approx([1.176, 0.89213, 0.51403], abs=1e-6)
def test_overall_endpoint_is_max_branch_endpoint(self) -> None:
info = get_decay_info_from_paceENSDF("Co60", "beta_minus")
assert info.endpoint_MeV == pytest.approx(1.492, abs=1e-6)
@pytest.mark.requires_ensdf
@pytest.mark.skipif(not _has_paceENSDF(), reason="paceENSDF not installed")
class TestPaceENSDFIntegration: