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:
@@ -163,6 +163,23 @@ class TestCLIPaceENSDF:
|
||||
assert exc.value.code == 1
|
||||
assert not (tmp_path / "tc99deg.csv").exists()
|
||||
|
||||
def test_paceensdf_co60_dry_run_multi_branch(self, capsys: Any) -> None:
|
||||
"""Co60 parses into a healthy multi-branch config (D3): no crash,
|
||||
all branch endpoints positive."""
|
||||
main(["Co60", "--dry-run"])
|
||||
out = capsys.readouterr().out
|
||||
assert "Multi-branch mode: 3 branches" in out
|
||||
|
||||
def test_paceensdf_co60_export(self, tmp_path: Any) -> None:
|
||||
out = str(tmp_path / "co60.csv")
|
||||
main(["Co60", "--output", out])
|
||||
df = read_export(out)
|
||||
area = np.trapezoid(df["total_spectrum"], df["energy_MeV"])
|
||||
assert np.isclose(area, 1.0, rtol=1e-3)
|
||||
eps = header_lines_of(out)
|
||||
assert any("E₀=1492.0" in h for h in eps)
|
||||
assert not any("excluded" in h for h in eps)
|
||||
|
||||
def test_cli_builds_paceensdf_config_via_library(
|
||||
self, monkeypatch: Any, capsys: Any
|
||||
) -> None:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user