#!/usr/bin/env python3 """qn_check.py -- QUANTUM-NUMBER CONSISTENCY engine. SAFE WORDING (load-bearing scope statement): This suite verifies that every observed particle is quantum-number-consistent with its geometry-derived constituents and that the parameter-free QCD symmetry relations hold against PDG-2024; it does NOT compute or claim absolute hadron masses from geometry. RUNNABLE-FIRST: bare Python 3, stdlib only (csv, json, math, statistics, argparse, dataclasses, pathlib, fractions, re). Zero pip installs. FAIL-CLOSED: any quantum-number inconsistency, missing required field, or a RELATION residual exceeding its declared tolerance => that row/test FAILs and the suite exits non-zero. CITE EVERY CONSTANT: every PDG number in the dataset carries a 'pdg_source' column; the relation tolerances live in relations_config.json (no magic numbers buried in code). CLI: python qn_check.py --data data --out out Dataset schema (one CSV row per particle; columns EXACT, shared by all shards): pdg_name, quark_content, charge_Q, J, P, C, isospin_I, I3, baryon_B, strangeness_S, charm_C, bottom_Bprime, mass_MeV, mass_unc_MeV, status_stars, sector, chunk, pdg_source This engine FROM THE quark_content verifies, for every particle: * charge Q = sum of constituent charges (u=+2/3, d=-1/3, s=-1/3, c=+2/3, b=-1/3, t=+2/3; antiquarks negate; leptons/gauge handled explicitly) * baryon B = (n_q - n_qbar)/3 * strangeness S = -(n_s - n_sbar) * charm C = +(n_c - n_cbar) * bottom B' = -(n_b - n_bbar) * color-singlet (q qbar / qqq / known multiquark / nuclear cluster) Any mismatch between a STATED quantum number and the value DERIVED from the constituents => FAIL with the offending row, non-zero exit. """ from __future__ import annotations import argparse import csv import json import math import re import statistics import sys from dataclasses import dataclass, field, asdict from fractions import Fraction from pathlib import Path from typing import Optional # ---------------------------------------------------------------------------- # Exact column schema -- shared by every dataset shard. Any deviation FAILs. # ---------------------------------------------------------------------------- SCHEMA = [ "pdg_name", "quark_content", "charge_Q", "J", "P", "C", "isospin_I", "I3", "baryon_B", "strangeness_S", "charm_C", "bottom_Bprime", "mass_MeV", "mass_unc_MeV", "status_stars", "sector", "chunk", "pdg_source", ] # Fields that may legitimately be empty (''). # - isospin_I / I3: blank for leptons/gauge and CP/strangeness-indefinite # states (e.g. K0_S/K0_L) and for whole isospin-multiplet rows. # - charge_Q: blank for a row that names a whole multiplet's content # (e.g. 'uud/udd') without singling out one charge member. # - J / P: spin-parity, frequently UNMEASURED for excited states; this # engine verifies the conserved FLAVOUR quantum numbers (Q,B,S,C,B') and # colour-singlet-ness, NOT J^P -- so a blank J/P must not fail-close. # - C: blank for non-C-eigenstates; mass blank for unmeasured (neutrinos). # - S/C/B': blank when the row asserts content but not the flavour number. OPTIONAL_EMPTY = { "C", "J", "P", "charge_Q", "isospin_I", "I3", "mass_MeV", "mass_unc_MeV", "status_stars", "strangeness_S", "charm_C", "bottom_Bprime", } # ---------------------------------------------------------------------------- # Constituent quantum numbers (PDG 2024 Quark-Model review + Particle Listings). # Charges are EXACT rationals (no float drift). Charge in units of |e|. # Anti-quarks negate charge; flavour-counting (S,C,B') from net quark counts: # S = -(n_s - n_sbar) C = +(n_c - n_cbar) B' = -(n_b - n_bbar) # ---------------------------------------------------------------------------- QUARK_CHARGE = { "u": Fraction(2, 3), "d": Fraction(-1, 3), "s": Fraction(-1, 3), "c": Fraction(2, 3), "b": Fraction(-1, 3), "t": Fraction(2, 3), } QUARKS = set(QUARK_CHARGE) # Explicitly-handled elementary (lepton / gauge / scalar) tokens. These carry # no quark content, so B = S = C = B' = 0 and colour-singlet is trivial. LEPTON_GAUGE_CHARGE = { "e-": -1, "e+": 1, "mu-": -1, "mu+": 1, "tau-": -1, "tau+": 1, "nu_e": 0, "nu_mu": 0, "nu_tau": 0, "nu_ebar": 0, "nu_mubar": 0, "nu_taubar": 0, "nu": 0, "nubar": 0, "gamma": 0, "gluon": 0, "g": 0, "Z": 0, "W+": 1, "W-": 1 * -1, "H": 0, } # Named composite hadrons used as constituents of nuclear clusters. # Each maps to (charge_Q, baryon_B, strangeness_S, charm_C, bottom_Bprime). NAMED_HADRON = { "p": (1, 1, 0, 0, 0), # proton uud "n": (0, 1, 0, 0, 0), # neutron udd "pbar": (-1, -1, 0, 0, 0), "nbar": (0, -1, 0, 0, 0), "Lambda": (0, 1, -1, 0, 0), # uds } # ---------------------------------------------------------------------------- # Data structures # ---------------------------------------------------------------------------- @dataclass class QNFailure: pdg_name: str sector: str field: str stated: str derived: str detail: str @dataclass class RowResult: pdg_name: str sector: str passed: bool skipped_reason: str = "" failures: list = field(default_factory=list) @dataclass class RelationResult: name: str passed: bool value: float bound: float kind: str detail: str pdg_source: str # ---------------------------------------------------------------------------- # quark_content parsing # ---------------------------------------------------------------------------- @dataclass class Content: net: dict = field(default_factory=dict) # flavour -> net (q - qbar) n_quark: int = 0 n_antiquark: int = 0 kind: str = "" # 'elementary' | 'qqbar_mix' | 'flavoured' | # 'nuclear' | 'undetermined_exotic' elem_charge: int = 0 members: list = field(default_factory=list) # for slash-multiplets: list of dicts cluster: list = field(default_factory=list) # named-hadron tokens for nuclear raw: str = "" note: str = "" # Spectroscopic / descriptive suffixes to strip before flavour parsing. _ANNOT = re.compile( r"\b(" r"radial|excited|high|vector|tensor|scalar|pseudoscalar|spin-?\d+|" r"glueball|hybrid|admix|candidate|region|dominant|octet|singlet|leaning|" r"\d[A-Z]\d|\d\s?\d[A-Z]\d|[1-9][SPDFGH]\d|[1-9]\s?[1-9][SPDFGH]\d|" r"debated|mixed|mix|threshold|baryonium|molecule|undetermined|type|" r"isoscalar|gg|or|etc\.?|further" r")\b", re.IGNORECASE, ) def _try_nuclear_cluster(raw: str): """Return a list of named-hadron tokens if `raw` is a nuclear/baryonic cluster of p/n/Lambda (and their antibaryons), else None.""" parts = raw.replace("-", " ").split() # all space-separated tokens are named hadrons if parts and all(t in NAMED_HADRON for t in parts): return parts # single concatenated nucleon string like 'pn','pnn','ppn','ppnn','pbar' if len(parts) == 1: s = parts[0] if s in NAMED_HADRON: return [s] if re.fullmatch(r"[pn]+", s): # nucleon cluster return list(s) return None def _tokens_to_content(tokens, c: Content): for tok in tokens: if tok.endswith("bar"): fl = tok[:-3] if fl not in QUARKS: raise ValueError(f"unknown antiquark flavour {fl!r}") c.net[fl] = c.net.get(fl, 0) - 1 c.n_antiquark += 1 else: if tok not in QUARKS: raise ValueError(f"unknown quark flavour {tok!r}") c.net[tok] = c.net.get(tok, 0) + 1 c.n_quark += 1 def _split_concatenated(part: str): """'ccbar','uud','sss','udb','uss' -> flavour-token list.""" out = [] i = 0 while i < len(part): ch = part[i] if ch not in QUARKS: raise ValueError(f"unknown flavour char {ch!r} in {part!r}") if part[i + 1:i + 4] == "bar": out.append(ch + "bar") i += 4 else: out.append(ch) i += 1 return out def _parse_bare(body: str) -> Content: """Parse a single bare-flavour string like 'uud', 'ubar d', 'ccbar', 'c sbar', 'udc' into a Content (flavoured / qqbar).""" c = Content(raw=body) parts = body.split() tokens = [] for p in parts: tokens.extend(_split_concatenated(p)) if not tokens: raise ValueError("no flavour tokens") _tokens_to_content(tokens, c) c.kind = "flavoured" return c def _net_flavour_qn(net, n_quark, n_antiquark): """Return (Q_fraction, B_fraction, S, C, Bprime) from net flavour counts.""" Q = Fraction(0) for fl, k in net.items(): Q += k * QUARK_CHARGE[fl] B = Fraction(n_quark - n_antiquark, 3) S = -net.get("s", 0) C = +net.get("c", 0) Bp = -net.get("b", 0) return Q, B, S, C, Bp def parse_quark_content(qc: str) -> Content: raw = (qc or "").strip() c = Content(raw=raw) if raw == "": raise ValueError("empty quark_content") # --- elementary lepton / gauge / scalar / single quark -------------- if raw == "elementary": c.kind = "elementary" return c # --- light-unflavored isovector representative ('ubar d etc.', 'ubar d', # 'u dbar', 'dbar u') : the I=1 (u,d) q-qbar triplet. The literal # written pair is just ONE charge member (PDG lists a representative); # 'etc.' explicitly flags the multiplet. Charge set = {+1,0,-1}, # B=S=C=B'=0. This is the faithful constituent reading of an isovector # light meson and avoids a false antiquark-ordering charge mismatch. body0 = _ANNOT.sub(" ", raw).strip() ud_pair = re.fullmatch( r"(?:u\s?bar\s?d|d\s?bar\s?u|ubar\s+d|dbar\s+u|u\s+dbar|d\s+ubar)", body0) if ud_pair or raw.lower().startswith(("ubar d", "dbar u", "u dbar", "d ubar")): c.kind = "isovector_light" c.note = "I=1 light (u,d) q-qbar isospin triplet representative" return c # --- nuclear cluster of named hadrons (p,n,Lambda,...) --------------- # Accept space-separated ('Lambda p n', 'pbar nbar') AND concatenated # nucleon strings ('pn','pnn','ppn','ppnn'). cluster = _try_nuclear_cluster(raw) if cluster is not None: c.kind = "nuclear" c.cluster = cluster return c # --- isospin-mixture neutral self-conjugate combinations ------------- # (ubar u - dbar d)/sqrt2 , c1(ubar u+dbar d)+c2(sbar s) , (uu+dd)/sqrt2 , # ubar u+dbar d radial 2 1S0 , sbar s-dominant 3P1 , (ubar u+dbar d)/sqrt2 # These are I=0 or I=1 neutral q-qbar superpositions: each term is a qbar-q # pair of equal flavour, so Q=B=S=C=B'=0 by construction. Any '+' joining # flavour-antiflavour pairs, a leading 'c1('/'(' grouping, a '/sqrt2' # normalisation, or a '-dominant' isoscalar-mixing label marks this kind. low_raw = raw.lower() if ("/sqrt2" in low_raw or "c1(" in low_raw or "(ubar u" in low_raw or "(uu" in low_raw or "+dbar d" in low_raw or "+ dbar d" in low_raw or "ubar u+dbar" in low_raw or "-dominant" in low_raw or raw.startswith("(")): c.kind = "qqbar_mix" c.note = "neutral self-conjugate qqbar superposition (isoscalar mixing)" return c # --- undetermined exotic / generic-placeholder neutral states -------- # glueball / hybrid / baryonium / molecular / 'broad pi-pi (sigma)' / # a generic 'qqbar ... (I undetermined)' light-unflavored placeholder. # All are listed in PDG as neutral isoscalar (Q=B=S=C=B'=0); we verify # against that. ('q' is a flavour placeholder, not a concrete flavour.) low = raw.lower() if (low.startswith("qqbar") or low.startswith("qbar q") or low.startswith("q qbar") or any(k in low for k in ( "glueball", "hybrid", "baryonium", "broad pi-pi", "sigma)", "qqbar g", "scalar glueball", "molecule", "threshold", "undetermined"))): # If a definite bare content is also present we still treat as # undetermined for QN purposes: such states are neutral isoscalars # in PDG (Q=B=S=C=B'=0). Verify against that. c.kind = "undetermined_exotic" c.note = "PDG exotic candidate (glueball/hybrid/molecular/baryonium)" return c # --- slash-separated isospin multiplet: uud/udd , uss/dss , uuu/uud/... if "/" in raw: member_strs = [m.strip() for m in raw.split("/") if m.strip()] members = [] for ms in member_strs: # strip any annotation from each member ms_clean = _ANNOT.sub(" ", ms).strip() try: mc = _parse_bare(ms_clean) except ValueError: # not all members are bare flavour -> treat whole thing as # qqbar_mix only if it clearly references neutral combos continue members.append(mc) if members: c.kind = "flavoured" c.members = members # represent net from the FIRST member (B,S,C,B' shared); charge # is checked against the set of member charges in derive step. first = members[0] c.net = first.net c.n_quark = first.n_quark c.n_antiquark = first.n_antiquark return c # fall through # --- single bare flavour content, possibly with trailing annotation -- # Strip annotations, then strip any leftover non-flavour words. cleaned = _ANNOT.sub(" ", raw) # keep only tokens that look like flavour content cand = [] for tok in cleaned.split(): t = tok.strip("(),/") if not t: continue # token must be composed of flavour chars (+optional 'bar') if re.fullmatch(r"(?:[udscbt](?:bar)?)+", t): cand.append(t) if cand: c2 = _parse_bare(" ".join(cand)) c2.raw = raw return c2 raise ValueError(f"could not parse flavour content from {raw!r}") # ---------------------------------------------------------------------------- # Derive conserved quantum numbers from a parsed Content. # Returns dict, or special markers for undetermined kinds. # ---------------------------------------------------------------------------- def derive_quantum_numbers(c: Content, stated_charge: Optional[Fraction]): if c.kind == "elementary": return None # elementary handled separately (charge via name) if c.kind == "qqbar_mix" or c.kind == "undetermined_exotic": # Neutral self-conjugate isoscalar: all conserved flavour quantum # numbers are zero. return { "charge_Q": Fraction(0), "baryon_B": Fraction(0), "strangeness_S": 0, "charm_C": 0, "bottom_Bprime": 0, "color_singlet": True, "charge_set": {Fraction(0)}, } if c.kind == "isovector_light": # I=1 light (u,d) q-qbar triplet: charge member in {+1,0,-1}; # B=S=C=B'=0; colour-singlet q-qbar. return { "charge_Q": None, "baryon_B": Fraction(0), "strangeness_S": 0, "charm_C": 0, "bottom_Bprime": 0, "color_singlet": True, "charge_set": {Fraction(1), Fraction(0), Fraction(-1)}, } if c.kind == "nuclear": Q = B = S = C = Bp = 0 for tok in c.cluster: q, b, s, ch, bp = NAMED_HADRON[tok] Q += q; B += b; S += s; C += ch; Bp += bp return { "charge_Q": Fraction(Q), "baryon_B": Fraction(B), "strangeness_S": S, "charm_C": C, "bottom_Bprime": Bp, "color_singlet": True, "charge_set": {Fraction(Q)}, } # flavoured (single or slash-multiplet) if c.members: charge_set = set() ref = None for m in c.members: Q, B, S, C, Bp = _net_flavour_qn(m.net, m.n_quark, m.n_antiquark) charge_set.add(Q) if ref is None: ref = (B, S, C, Bp) else: if (B, S, C, Bp) != ref: raise ValueError( "slash-multiplet members disagree on B/S/C/B' " f"({ref} vs {(B,S,C,Bp)})") B, S, C, Bp = ref singlet = _is_color_singlet(c.members[0]) return { "charge_Q": None, "baryon_B": B, "strangeness_S": S, "charm_C": C, "bottom_Bprime": Bp, "color_singlet": singlet, "charge_set": charge_set, } Q, B, S, C, Bp = _net_flavour_qn(c.net, c.n_quark, c.n_antiquark) return { "charge_Q": Q, "baryon_B": B, "strangeness_S": S, "charm_C": C, "bottom_Bprime": Bp, "color_singlet": _is_color_singlet(c), "charge_set": {Q}, } def _is_color_singlet(c: Content) -> bool: nq, na = c.n_quark, c.n_antiquark total = nq + na net = nq - na if total == 2: return nq == 1 and na == 1 # meson q qbar if total == 3: return (nq == 3 and na == 0) or (nq == 0 and na == 3) # (anti)baryon if total == 4: return nq == 2 and na == 2 # tetraquark if total == 5: return (nq == 4 and na == 1) or (nq == 1 and na == 4) # pentaquark if total == 6: return net % 3 == 0 # dibaryon / hexaquark return False # ---------------------------------------------------------------------------- # Stated-field parsers (handle 1/2, +1/2, -1, '+', etc.) # ---------------------------------------------------------------------------- # Decimal-charge tolerance: a stated decimal charge (e.g. '0.6666666667') is # accepted as the exact rational it is clearly approximating if it lies within # this tolerance of a third/sixth. NOT a physics fit parameter -- purely a # float<->rational reconciliation for the |e|/3-quantised charge lattice. CHARGE_DECIMAL_TOL = Fraction(1, 100000) def fraction_eq(stated: Fraction, derived: Fraction) -> bool: """Exact rational equality, but a decimal-sourced stated value within CHARGE_DECIMAL_TOL of the derived rational counts as equal.""" if stated == derived: return True return abs(stated - derived) <= CHARGE_DECIMAL_TOL def parse_fraction(s: str) -> Fraction: s = s.strip() if s in ("", "?"): raise ValueError("empty numeric field") s = s.replace("+", "") if s.startswith("+") else s if "/" in s: num, den = s.split("/") return Fraction(int(num), int(den)) return Fraction(s) def parse_int(s: str) -> int: s = s.strip() if s in ("", "?"): raise ValueError("empty integer field") if s.startswith("+"): s = s[1:] return int(s) def elementary_charge_from_name(name: str) -> Optional[Fraction]: """Map an elementary (lepton/gauge/scalar/quark) PDG name to its exact charge (Fraction, units of |e|). Quarks return their fractional charge.""" n = name.strip() if n in LEPTON_GAUGE_CHARGE: return Fraction(LEPTON_GAUGE_CHARGE[n]) # bare quarks listed as elementary fields (u,d,s,c,b,t and antiquarks) if n in QUARK_CHARGE: return QUARK_CHARGE[n] if n.endswith("bar") and n[:-3] in QUARK_CHARGE: return -QUARK_CHARGE[n[:-3]] # neutrinos of any flavour (and antineutrinos) if n.startswith("nu"): return Fraction(0) base = { "electron": -1, "positron": 1, "muon": -1, "tau": -1, "photon": 0, "gluon": 0, "Higgs": 0, "H0": 0, "Z0": 0, "Z": 0, "W+": 1, "W-": -1, "graviton": 0, "g": 0, } return Fraction(base[n]) if n in base else None # ---------------------------------------------------------------------------- # Per-row verification # ---------------------------------------------------------------------------- def verify_row(row: dict) -> RowResult: name = row.get("pdg_name", "") sector = row.get("sector", "") res = RowResult(pdg_name=name, sector=sector, passed=True) # --- schema / required-field check (FAIL-CLOSED on missing) ---------- for col in SCHEMA: if col not in row: res.passed = False res.failures.append(asdict(QNFailure( name, sector, col, "", "", "required column missing from row"))) return res if (row[col] or "").strip() == "" and col not in OPTIONAL_EMPTY: res.passed = False res.failures.append(asdict(QNFailure( name, sector, col, "''", "", "required field is empty"))) if not res.passed: return res # --- elementary particles: leptons/gauge/scalar (all flavour QN = 0) or # a single bare quark (B=1/3 and its own flavour number) ------------ qc = row["quark_content"].strip() if qc == "elementary": ec = elementary_charge_from_name(name) if ec is None: res.passed = False res.failures.append(asdict(QNFailure( name, sector, "quark_content", qc, "", "elementary particle with unrecognised name (no charge map)"))) return res # single bare quark / antiquark carries B=+/-1/3 and a flavour number base = name[:-3] if name.endswith("bar") else name is_anti = name.endswith("bar") if base in QUARK_CHARGE: sign = -1 if is_anti else 1 dB = Fraction(sign, 3) dS = -sign if base == "s" else 0 dC = sign if base == "c" else 0 dBp = -sign if base == "b" else 0 else: dB, dS, dC, dBp = Fraction(0), 0, 0, 0 if row["charge_Q"].strip() != "": _check_field(res, name, sector, "charge_Q", row["charge_Q"], ec, parse_fraction) _maybe_check(res, name, sector, "baryon_B", row["baryon_B"], dB, parse_fraction) _maybe_check(res, name, sector, "strangeness_S", row["strangeness_S"], dS, parse_int) _maybe_check(res, name, sector, "charm_C", row["charm_C"], dC, parse_int) _maybe_check(res, name, sector, "bottom_Bprime", row["bottom_Bprime"], dBp, parse_int) return res # --- composite: parse constituents ---------------------------------- try: cons = parse_quark_content(qc) stated_Q = parse_fraction(row["charge_Q"]) if row["charge_Q"].strip() else None derived = derive_quantum_numbers(cons, stated_Q) except ValueError as exc: res.passed = False res.failures.append(asdict(QNFailure( name, sector, "quark_content", qc, "", f"unparseable / inconsistent constituents: {exc}"))) return res if derived is None: # should not happen for non-elementary res.passed = False res.failures.append(asdict(QNFailure( name, sector, "quark_content", qc, "", "no derived QN produced"))) return res # --- charge: must equal a derived value (or member of multiplet) ----- if row["charge_Q"].strip() != "": try: stated = parse_fraction(row["charge_Q"]) if not any(fraction_eq(stated, q) for q in derived["charge_set"]): res.passed = False res.failures.append(asdict(QNFailure( name, sector, "charge_Q", row["charge_Q"], "/".join(str(x) for x in sorted(derived["charge_set"])), "stated charge not in set derived from constituents"))) except (ValueError, ZeroDivisionError) as exc: res.passed = False res.failures.append(asdict(QNFailure( name, sector, "charge_Q", row["charge_Q"], "", f"unparseable charge: {exc}"))) # --- B, S, C, B' : compare stated vs derived (skip blanks) ----------- _maybe_check(res, name, sector, "baryon_B", row["baryon_B"], derived["baryon_B"], parse_fraction) _maybe_check(res, name, sector, "strangeness_S", row["strangeness_S"], derived["strangeness_S"], parse_int) _maybe_check(res, name, sector, "charm_C", row["charm_C"], derived["charm_C"], parse_int) _maybe_check(res, name, sector, "bottom_Bprime", row["bottom_Bprime"], derived["bottom_Bprime"], parse_int) # --- colour-singlet --------------------------------------------------- if not derived["color_singlet"]: res.passed = False res.failures.append(asdict(QNFailure( name, sector, "color_singlet", qc, "not a color singlet", "constituent content is not a recognised QCD colour-singlet " "category (meson/baryon/tetra/penta/hexa/nuclear)"))) return res def _check_field(res, name, sector, fld, stated_str, derived_val, parser): try: stated = parser(stated_str) except (ValueError, ZeroDivisionError) as exc: res.passed = False res.failures.append(asdict(QNFailure( name, sector, fld, stated_str, str(derived_val), f"unparseable stated value: {exc}"))) return equal = (fraction_eq(stated, derived_val) if isinstance(stated, Fraction) and isinstance(derived_val, Fraction) else stated == derived_val) if not equal: res.passed = False res.failures.append(asdict(QNFailure( name, sector, fld, str(stated_str), str(derived_val), "stated != derived-from-constituents"))) def _maybe_check(res, name, sector, fld, stated_str, derived_val, parser): """Check only if the stated field is non-empty (blank = not asserted).""" if (stated_str or "").strip() == "": return _check_field(res, name, sector, fld, stated_str, derived_val, parser) # ---------------------------------------------------------------------------- # Dataset loading # ---------------------------------------------------------------------------- def load_dataset(data_dir: Path): rows = [] files = sorted(data_dir.glob("dataset_*.csv")) if not files: raise SystemExit(f"FAIL-CLOSED: no dataset_*.csv found in {data_dir}") for fp in files: with fp.open(newline="", encoding="utf-8") as fh: reader = csv.DictReader(fh) header = reader.fieldnames or [] if header != SCHEMA: raise SystemExit( f"FAIL-CLOSED: {fp.name} header mismatch.\n" f" expected: {SCHEMA}\n found: {header}") for r in reader: r["_source_file"] = fp.name rows.append(r) return rows, [f.name for f in files] def _read_aux_csv(path: Path): if not path.exists(): return None with path.open(newline="", encoding="utf-8") as fh: return list(csv.DictReader(fh)) # ---------------------------------------------------------------------------- # Parameter-free QCD symmetry RELATIONS (tolerances from relations_config.json). # Inputs come from the dedicated relation CSVs (baryon_octet.csv, # baryon_decuplet.csv, regge_trajectories.csv) so each carries its own # pdg_source. NO magic numbers in code -- every bound is a config value. # ---------------------------------------------------------------------------- def _f(x): return float(x) def check_relations(data_dir: Path, config) -> list: tol = config["tolerances"] results = [] # ---- Gell-Mann--Okubo baryon octet: 2(N+Xi) = 3*Lambda + Sigma ----- oct_rows = _read_aux_csv(data_dir / "baryon_octet.csv") rel_tol = tol["gmo_octet_rel_tol"]["value"] if oct_rows is None: results.append(RelationResult( "GMO baryon octet 2(N+Xi)=3Lambda+Sigma", False, float("inf"), rel_tol, "relative", "FAIL-CLOSED: baryon_octet.csv missing", "")) else: by = {r["particle"]: r for r in oct_rows} # isospin-averaged members def avg(names): vals = [_f(by[n]["mass_MeV"]) for n in names if n in by] if not vals: raise KeyError(names) return statistics.fmean(vals) try: mN = avg(["proton", "neutron"]) mXi = avg(["Xi0", "Xi-"]) mLam = avg(["Lambda"]) mSig = avg(["Sigma+", "Sigma0", "Sigma-"]) lhs = 2.0 * (mN + mXi) rhs = 3.0 * mLam + mSig rel = abs(lhs - rhs) / rhs src = by.get("proton", {}).get("pdg_source", "") results.append(RelationResult( "GMO baryon octet 2(N+Xi)=3Lambda+Sigma", rel <= rel_tol, rel, rel_tol, "relative", f"LHS=2(N+Xi)={lhs:.3f} MeV; RHS=3Lambda+Sigma={rhs:.3f} MeV; " f"rel dev={rel:.5f} (tol {rel_tol})", src)) except KeyError as e: results.append(RelationResult( "GMO baryon octet 2(N+Xi)=3Lambda+Sigma", False, float("inf"), rel_tol, "relative", f"FAIL-CLOSED: missing octet member {e}", "")) # ---- decuplet equal-spacing rule ----------------------------------- dec_rows = _read_aux_csv(data_dir / "baryon_decuplet.csv") dec_tol = tol["decuplet_equal_spacing_rel_tol"]["value"] if dec_rows is None: results.append(RelationResult( "Decuplet equal-spacing", False, float("inf"), dec_tol, "relative", "FAIL-CLOSED: baryon_decuplet.csv missing", "")) else: order = ["Delta", "Sigma*", "Xi*", "Omega-"] byd = {r["particle"]: r for r in dec_rows} try: ms = [_f(byd[p]["mass_MeV"]) for p in order] gaps = [ms[i + 1] - ms[i] for i in range(len(ms) - 1)] mean_gap = statistics.fmean(gaps) rel = max(abs(g - mean_gap) for g in gaps) / mean_gap src = byd.get("Omega-", {}).get("pdg_source", "") results.append(RelationResult( "Decuplet equal-spacing", rel <= dec_tol, rel, dec_tol, "relative", f"gaps={['%.2f' % g for g in gaps]} MeV; mean={mean_gap:.2f}; " f"max rel dev={rel:.5f} (tol {dec_tol})", src)) except KeyError as e: results.append(RelationResult( "Decuplet equal-spacing", False, float("inf"), dec_tol, "relative", f"FAIL-CLOSED: missing decuplet member {e}", "")) # ---- Regge linearity: M^2 linear in J (and radial n) --------------- reg_rows = _read_aux_csv(data_dir / "regge_trajectories.csv") r2_floor = tol["regge_r2_floor"]["value"] slope_pos = tol["regge_slope_positive"]["value"] if reg_rows is None: results.append(RelationResult( "Regge M^2 linearity", False, 0.0, r2_floor, "min_r_squared", "FAIL-CLOSED: regge_trajectories.csv missing", "")) else: trajs = {} for r in reg_rows: trajs.setdefault(r["trajectory"], []).append(r) for tname, pts in sorted(trajs.items()): axis = pts[0]["axis"] xs = [_f(p[axis == "J" and "J" or "n"]) for p in pts] ys = [_f(p["mass_MeV"]) ** 2 for p in pts] # M^2 r2, slope = _linfit_r2(xs, ys) src = pts[0].get("pdg_source", "") ok_r2 = r2 >= r2_floor ok_slope = (slope > 0) if slope_pos else True results.append(RelationResult( f"Regge linearity {tname} (M^2 vs {axis})", ok_r2 and ok_slope, r2, r2_floor, "min_r_squared", f"R^2={r2:.5f} (floor {r2_floor}); slope={slope:.1f} MeV^2 " f"({'positive' if slope > 0 else 'NON-positive'})", src)) return results def _linfit_r2(xs, ys): """Least-squares line y = a*x + b; return (R^2, slope a).""" n = len(xs) mx = statistics.fmean(xs) my = statistics.fmean(ys) sxx = sum((x - mx) ** 2 for x in xs) sxy = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) syy = sum((y - my) ** 2 for y in ys) if sxx == 0 or syy == 0: return (0.0, 0.0) a = sxy / sxx r2 = (sxy ** 2) / (sxx * syy) return (r2, a) # ---------------------------------------------------------------------------- # Reporting # ---------------------------------------------------------------------------- def build_report(row_results, relation_results, files, config): by_sector = {} for rr in row_results: s = by_sector.setdefault(rr.sector, {"pass": 0, "fail": 0}) s["pass" if rr.passed else "fail"] += 1 failures = [f for rr in row_results for f in rr.failures] n_pass = sum(1 for rr in row_results if rr.passed) n_fail = len(row_results) - n_pass rel_pass = sum(1 for r in relation_results if r.passed) rel_fail = len(relation_results) - rel_pass overall_ok = (n_fail == 0) and (rel_fail == 0) report = { "scope_statement": ( "This suite verifies that every observed particle is " "quantum-number-consistent with its geometry-derived constituents " "and that the parameter-free QCD symmetry relations hold against " "PDG-2024; it does NOT compute or claim absolute hadron masses " "from geometry."), "pdg_version": config.get("pdg_version", ""), "dataset_files": files, "total_particles": len(row_results), "particles_passed": n_pass, "particles_failed": n_fail, "per_sector": by_sector, "relations_total": len(relation_results), "relations_passed": rel_pass, "relations_failed": rel_fail, "relation_results": [asdict(r) for r in relation_results], "quantum_number_failures": failures, "overall_pass": overall_ok, } return report, overall_ok def write_reports(report, out_dir: Path): out_dir.mkdir(parents=True, exist_ok=True) json_path = out_dir / "qn_report.json" json_path.write_text(json.dumps(report, indent=2), encoding="utf-8") md = [] md.append("# Quantum-Number Consistency Report\n") md.append(f"> {report['scope_statement']}\n") md.append(f"PDG reference: **{report['pdg_version']}**\n") md.append(f"**Overall:** " f"{'PASS' if report['overall_pass'] else 'FAIL (fail-closed)'}\n") md.append("## Dataset shards\n") for f in report["dataset_files"]: md.append(f"- `{f}`") md.append("") md.append("## Particle totals\n") md.append(f"- total particles: **{report['total_particles']}**") md.append(f"- passed: **{report['particles_passed']}**") md.append(f"- failed: **{report['particles_failed']}**\n") md.append("## Per-sector pass counts\n") md.append("| sector | pass | fail |") md.append("|---|---:|---:|") for sec, c in sorted(report["per_sector"].items()): md.append(f"| {sec} | {c['pass']} | {c['fail']} |") md.append("") md.append("## Parameter-free QCD symmetry relations vs PDG-2024\n") md.append("| relation | result | value | bound | kind | detail |") md.append("|---|---|---:|---:|---|---|") for r in report["relation_results"]: md.append( f"| {r['name']} | {'PASS' if r['passed'] else 'FAIL'} | " f"{r['value']:.5f} | {r['bound']} | {r['kind']} | {r['detail']} |") md.append("") if report["quantum_number_failures"]: md.append("## Quantum-number FAILURES (fail-closed)\n") md.append("| particle | sector | field | stated | derived | detail |") md.append("|---|---|---|---|---|---|") for f in report["quantum_number_failures"]: md.append( f"| {f['pdg_name']} | {f['sector']} | {f['field']} | " f"{f['stated']} | {f['derived']} | {f['detail']} |") else: md.append("## Quantum-number FAILURES\n\nNone -- every particle is " "quantum-number-consistent with its constituents.\n") md.append("") (out_dir / "qn_report.md").write_text("\n".join(md), encoding="utf-8") return json_path, out_dir / "qn_report.md" # ---------------------------------------------------------------------------- # Driver # ---------------------------------------------------------------------------- def run(data_dir: Path, out_dir: Path, config_path: Optional[Path] = None): rows, files = load_dataset(data_dir) row_results = [verify_row(r) for r in rows] if config_path is None: config_path = Path(__file__).parent / "relations_config.json" config = json.loads(config_path.read_text(encoding="utf-8")) relation_results = check_relations(data_dir, config) report, overall_ok = build_report(row_results, relation_results, files, config) write_reports(report, out_dir) return report, overall_ok def main(argv=None): ap = argparse.ArgumentParser(description="Quantum-number consistency engine.") ap.add_argument("--data", default="data", help="directory of dataset_*.csv") ap.add_argument("--out", default="out", help="output directory for reports") ap.add_argument("--config", default=None, help="relations config json") args = ap.parse_args(argv) report, overall_ok = run(Path(args.data), Path(args.out), Path(args.config) if args.config else None) print(f"particles: {report['total_particles']} " f"passed: {report['particles_passed']} " f"failed: {report['particles_failed']}") print(f"relations: {report['relations_total']} " f"passed: {report['relations_passed']} " f"failed: {report['relations_failed']}") print(f"overall: {'PASS' if overall_ok else 'FAIL (fail-closed)'}") return 0 if overall_ok else 1 if __name__ == "__main__": sys.exit(main())