#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Real pytest suite for mass_relations.py — the parameter-free RELATION engine. These tests actually RUN the engine against the real PDG-2024 ./data CSVs and assert on real output values / bytes. They are NOT trivially-true stubs: * The clean-data run must exit 0, the quantum-number gate must PASS, and each RELATION must produce the expected numeric residual within tolerance. * A DELIBERATELY-BROKEN row (charge != sum of constituents) MUST trip the quantum-number gate and fail closed (exit 1) — exercised here directly. * A residual pushed beyond its declared tolerance (corrupted Omega- mass) MUST fail closed. * A missing 'pdg_source' citation on a numeric mass MUST fail closed. Run from the repository root: python -m pytest tests/test_mass_relations.py -q """ from __future__ import annotations import csv import json import shutil import sys from pathlib import Path import pytest # Make the engine importable regardless of CWD. REPO_ROOT = Path(__file__).resolve().parents[1] if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) import mass_relations as mr # noqa: E402 DATA_DIR = REPO_ROOT / "data" CONFIG = REPO_ROOT / "relations_config.json" # --------------------------------------------------------------------------- # # Fixtures: a clean copy of data + config in a temp dir we can corrupt. # # --------------------------------------------------------------------------- # @pytest.fixture() def sandbox(tmp_path: Path) -> Path: """A writable copy of data/ + relations_config.json under tmp_path.""" dst_data = tmp_path / "data" shutil.copytree(DATA_DIR, dst_data) shutil.copy(CONFIG, tmp_path / "relations_config.json") return tmp_path def _run(box: Path) -> int: return mr.main([ "--data", str(box / "data"), "--out", str(box / "out"), "--config", str(box / "relations_config.json"), ]) def _read_report(box: Path) -> dict: return json.loads((box / "out" / "relations_report.json").read_text(encoding="utf-8")) def _rewrite_csv(path: Path, rows, fieldnames) -> None: with path.open("w", encoding="utf-8", newline="") as fh: w = csv.DictWriter(fh, fieldnames=fieldnames) w.writeheader() for r in rows: w.writerow(r) def _load_rows(path: Path): with path.open("r", encoding="utf-8-sig", newline="") as fh: reader = csv.DictReader(fh) return list(reader), reader.fieldnames # --------------------------------------------------------------------------- # # 1. Clean data: exit 0, gate PASS, every relation PASS. # # --------------------------------------------------------------------------- # def test_clean_data_exits_zero_and_all_pass(sandbox: Path) -> None: rc = _run(sandbox) assert rc == 0, "clean PDG-2024 data must pass the full RELATION suite" rep = _read_report(sandbox) assert rep["overall_pass"] is True assert rep["quantum_number_gate"]["passed"] is True assert rep["quantum_number_gate"]["n_failed"] == 0 assert rep["totals"]["relations_failed"] == 0 assert rep["totals"]["relations_evaluated"] >= 5 # R1, R2, R3, >=2 Regge # The safe wording is present in the report bytes. md = (sandbox / "out" / "relations_report.md").read_text(encoding="utf-8") assert "does NOT compute or claim absolute hadron masses from geometry" in md assert rep["grade"] == "RELATION" # --------------------------------------------------------------------------- # # 2. GMO numeric result (parameter-free). # # --------------------------------------------------------------------------- # def test_gmo_octet_numeric(sandbox: Path) -> None: cfg = mr.load_config(sandbox / "relations_config.json") octet = mr.load_csv(sandbox / "data" / "baryon_octet.csv") r = mr.relation_gmo_octet(octet, cfg) # LHS = 2(m_N + m_Xi); RHS = 3 m_Lambda + m_Sigma. PDG-2024 isospin averages. assert r.observed == pytest.approx(4514.4075, abs=0.01) # LHS assert r.predicted == pytest.approx(4540.2027, abs=0.01) # RHS # Relative residual ~0.57%, well within the 1% tolerance. assert r.residual == pytest.approx(0.005698, abs=1e-4) assert r.passed is True assert r.grade == "RELATION" # --------------------------------------------------------------------------- # # 3. Decuplet equal-spacing numeric result (parameter-free). # # --------------------------------------------------------------------------- # def test_decuplet_equal_spacing_numeric(sandbox: Path) -> None: cfg = mr.load_config(sandbox / "relations_config.json") dec = mr.load_csv(sandbox / "data" / "baryon_decuplet.csv") r = mr.relation_decuplet_equal_spacing(dec, cfg) steps = r.observed # [Sigma*-Delta, Xi*-Sigma*, Omega-Xi*] assert steps[0] == pytest.approx(151.93, abs=0.05) assert steps[1] == pytest.approx(148.97, abs=0.05) assert steps[2] == pytest.approx(140.65, abs=0.05) assert r.predicted == pytest.approx(147.18, abs=0.05) # mean step # Max deviation from mean ~4.4%, within the 10% tolerance. assert r.residual == pytest.approx(0.0444, abs=2e-3) assert r.passed is True # --------------------------------------------------------------------------- # # 4. Isospin signs (parameter-free): n>p, K0>K+, D0 None: cfg = mr.load_config(sandbox / "relations_config.json") mult = mr.load_csv(sandbox / "data" / "isospin_multiplets.csv") r = mr.relation_isospin_signs(mult, cfg) assert r.passed is True rows = {m["multiplet"]: m for m in r.inputs["multiplets"]} assert rows["nucleon"]["observed_sign"] == "positive" # m_n > m_p assert rows["kaon"]["observed_sign"] == "positive" # m_K0 > m_K+ assert rows["D_meson"]["observed_sign"] == "negative" # m_D0 < m_D+ assert rows["nucleon"]["observed_diff_MeV"] == pytest.approx(1.2933, abs=1e-3) # --------------------------------------------------------------------------- # # 5. Regge linearity: high R^2, positive slope. # # --------------------------------------------------------------------------- # def test_regge_linearity_high_r2(sandbox: Path) -> None: cfg = mr.load_config(sandbox / "relations_config.json") regge = mr.load_csv(sandbox / "data" / "regge_trajectories.csv") results = mr.relation_regge_linearity(regge, cfg) assert len(results) >= 2 for r in results: assert r.observed["R_squared"] >= 0.97, f"{r.relation_id} R^2 too low" assert r.predicted["slope_MeV2_per_unit"] > 0, f"{r.relation_id} slope not positive" assert r.passed is True def test_least_squares_known_line() -> None: # y = 3x + 5 exactly => slope 3, intercept 5, R^2 = 1. slope, intercept, r2 = mr._least_squares([0, 1, 2, 3], [5, 8, 11, 14]) assert slope == pytest.approx(3.0) assert intercept == pytest.approx(5.0) assert r2 == pytest.approx(1.0) # --------------------------------------------------------------------------- # # 6. FAIL-CLOSED: deliberately-broken row (charge != sum of constituents). # # --------------------------------------------------------------------------- # def test_broken_charge_row_fails_closed(sandbox: Path) -> None: """A proton row with quark content 'uud' (charge +1) but listed Q=0 MUST trip the quantum-number gate and fail closed (exit 1).""" octet_path = sandbox / "data" / "baryon_octet.csv" rows, fields = _load_rows(octet_path) for row in rows: if row["particle"] == "proton": row["Q"] = "0" # WRONG: uud sums to +1, not 0. _rewrite_csv(octet_path, rows, fields) rc = _run(sandbox) assert rc == 1, "a charge != sum-of-constituents row MUST fail closed" rep = _read_report(sandbox) assert rep["overall_pass"] is False assert rep["quantum_number_gate"]["passed"] is False assert rep["quantum_number_gate"]["n_failed"] >= 1 # The specific failing finding is the proton charge check. bad = [f for f in rep["quantum_number_gate"]["findings"] if f["particle"] == "proton" and f["field"] == "charge" and not f["ok"]] assert bad, "proton charge mismatch must be reported as a failed finding" assert bad[0]["derived"] == pytest.approx(1.0) assert bad[0]["listed"] == pytest.approx(0.0) # When the gate fails, relations are NOT evaluated (untrustworthy constituents). assert rep["totals"]["relations_evaluated"] == 0 def test_broken_strangeness_row_fails_closed(sandbox: Path) -> None: """A Lambda(uds) row with listed strangeness 0 (should be -1) MUST fail closed.""" octet_path = sandbox / "data" / "baryon_octet.csv" rows, fields = _load_rows(octet_path) for row in rows: if row["particle"] == "Lambda": row["strangeness"] = "0" # WRONG: uds has S=-1. _rewrite_csv(octet_path, rows, fields) rc = _run(sandbox) assert rc == 1 rep = _read_report(sandbox) bad = [f for f in rep["quantum_number_gate"]["findings"] if f["particle"] == "Lambda" and f["field"] == "strangeness" and not f["ok"]] assert bad and bad[0]["derived"] == -1 and bad[0]["listed"] == 0 # --------------------------------------------------------------------------- # # 7. FAIL-CLOSED: a relation residual pushed beyond tolerance. # # --------------------------------------------------------------------------- # def test_residual_beyond_tolerance_fails_closed(sandbox: Path) -> None: """Corrupt the Omega- mass so the equal-spacing step blows past 10%. Keep Q/strangeness consistent so the quantum-number gate still PASSES; only the RELATION residual should trip. This proves the residual gate is real and independent of the quantum-number gate.""" dec_path = sandbox / "data" / "baryon_decuplet.csv" rows, fields = _load_rows(dec_path) for row in rows: if row["particle"] == "Omega-": row["mass_MeV"] = "2200.0" # absurd: Omega-Xi* step ~668 MeV vs ~150. _rewrite_csv(dec_path, rows, fields) rc = _run(sandbox) assert rc == 1, "an equal-spacing residual beyond tolerance MUST fail closed" rep = _read_report(sandbox) assert rep["quantum_number_gate"]["passed"] is True # QN gate still OK r2 = [r for r in rep["relations"] if r["relation_id"] == "R2"][0] assert r2["passed"] is False assert r2["residual"] > 0.10 # beyond the declared 10% tolerance assert rep["overall_pass"] is False def test_gmo_residual_beyond_tolerance_fails_closed(sandbox: Path) -> None: """Corrupt the Lambda mass to break GMO past 1% (QN gate unaffected).""" octet_path = sandbox / "data" / "baryon_octet.csv" rows, fields = _load_rows(octet_path) for row in rows: if row["particle"] == "Lambda": row["mass_MeV"] = "1400.0" # far off; breaks 2(N+Xi)=3*Lambda+Sigma. _rewrite_csv(octet_path, rows, fields) rc = _run(sandbox) assert rc == 1 rep = _read_report(sandbox) assert rep["quantum_number_gate"]["passed"] is True r1 = [r for r in rep["relations"] if r["relation_id"] == "R1"][0] assert r1["passed"] is False assert r1["residual"] > 0.01 # --------------------------------------------------------------------------- # # 8. FAIL-CLOSED: missing pdg_source citation on a numeric mass. # # --------------------------------------------------------------------------- # def test_missing_pdg_source_fails_closed(sandbox: Path) -> None: """CITE EVERY CONSTANT: a numeric mass without a pdg_source MUST fail closed.""" octet_path = sandbox / "data" / "baryon_octet.csv" rows, fields = _load_rows(octet_path) for row in rows: if row["particle"] == "proton": row["pdg_source"] = "" # strip the citation. _rewrite_csv(octet_path, rows, fields) rc = _run(sandbox) assert rc == 1, "a numeric mass without a pdg_source citation MUST fail closed" # --------------------------------------------------------------------------- # # 9. FAIL-CLOSED: missing required input file. # # --------------------------------------------------------------------------- # def test_missing_input_file_fails_closed(sandbox: Path) -> None: (sandbox / "data" / "regge_trajectories.csv").unlink() rc = _run(sandbox) assert rc == 1, "a missing required input file MUST fail closed" # --------------------------------------------------------------------------- # # 10. FAIL-CLOSED: config missing a required tolerance. # # --------------------------------------------------------------------------- # def test_config_missing_tolerance_fails_closed(sandbox: Path) -> None: cfg_path = sandbox / "relations_config.json" cfg = json.loads(cfg_path.read_text(encoding="utf-8")) del cfg["tolerances"]["gmo_octet_rel_tol"] # drop a required tolerance. cfg_path.write_text(json.dumps(cfg), encoding="utf-8") rc = _run(sandbox) assert rc == 1, "a config missing a declared tolerance MUST fail closed" if __name__ == "__main__": sys.exit(pytest.main([__file__, "-q"]))