diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22cb37f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +.skill-lib/ diff --git a/research/epac/README.md b/research/epac/README.md index 9a1d902..6f4b3e5 100644 --- a/research/epac/README.md +++ b/research/epac/README.md @@ -5,3 +5,38 @@ Reserved for **energy particle affixiation coupling**. Status: `hmmm` — no source repository exists yet. This directory carries no doctrine until a canonical `The-Interdependency/epac` source is created and pinned in `STACK_MANIFEST.md`. + +## Current content + +- [`subatomic/subatomic-affixiation-baseline.md`](subatomic/subatomic-affixiation-baseline.md) — + provisional research candidate: hydrogen → helium baseline and the lithium/carbon + construction form over current METAPAT affixiation semantics and UCNS carrier identity. + Status: CROSS-DOMAIN-HYPOTHESIS / proposed. Not org canon. +- [`epac_public_gonol.py`](epac_public_gonol.py) — EPAC Public Gonol constructor + on the UCNS carrier. Not the EDCM text-domain constructor. +- [`docs/arity.md`](docs/arity.md) — arity is declared dimensional coupling, + not ambient dimension count. Charged oriented couplings plus degree are the + three-dimensional structure. +- [`docs/preregistration-molecular-geometry-from-element-gonols.md`](docs/preregistration-molecular-geometry-from-element-gonols.md) — + provisional candidate: Z=1–18 element gonols from atomic structure only, then + affixiation of H₂, H₂O, NH₃, CH₄, CO₂ through unpaired valence electrons, + declared dimensional couplings, and UCNS Möbius observation. Known chemistry + is sealed until after construction. + +## Usage + +From this directory: + +```bash +PYTHONPATH=".:../ucns/src" python3 -m unittest discover -s tests -q +``` + +Do not open `data/sealed_known_molecular_geometry.json` during construction. +After construction: + +```bash +PYTHONPATH=".:../ucns/src" python3 - <<'PY' +from epac_comparison import compare_after_construction +print(compare_after_construction()["standings"]) +PY +``` diff --git a/research/epac/data/periodic_table_z1_18.json b/research/epac/data/periodic_table_z1_18.json new file mode 100644 index 0000000..dc87260 --- /dev/null +++ b/research/epac/data/periodic_table_z1_18.json @@ -0,0 +1,31 @@ +{ + "schema": "epac.periodic-table-atomic-structure", + "version": "v1", + "scope": "Z=1-18 ground-state atomic structure for element-gonol construction", + "source": "established main-group ground-state electron configurations and typical hydride valences; not a molecular-shape table", + "excludes": [ + "bond-angle", + "shape-class", + "hybridization-as-shape" + ], + "elements": [ + {"Z": 1, "symbol": "H", "period": 1, "group": 1, "electron_configuration": "1s1", "valence_electrons": 1, "typical_valence": 1}, + {"Z": 2, "symbol": "He", "period": 1, "group": 18, "electron_configuration": "1s2", "valence_electrons": 2, "typical_valence": 0}, + {"Z": 3, "symbol": "Li", "period": 2, "group": 1, "electron_configuration": "1s2.2s1", "valence_electrons": 1, "typical_valence": 1}, + {"Z": 4, "symbol": "Be", "period": 2, "group": 2, "electron_configuration": "1s2.2s2", "valence_electrons": 2, "typical_valence": 2}, + {"Z": 5, "symbol": "B", "period": 2, "group": 13, "electron_configuration": "1s2.2s2.2p1", "valence_electrons": 3, "typical_valence": 3}, + {"Z": 6, "symbol": "C", "period": 2, "group": 14, "electron_configuration": "1s2.2s2.2p2", "valence_electrons": 4, "typical_valence": 4}, + {"Z": 7, "symbol": "N", "period": 2, "group": 15, "electron_configuration": "1s2.2s2.2p3", "valence_electrons": 5, "typical_valence": 3}, + {"Z": 8, "symbol": "O", "period": 2, "group": 16, "electron_configuration": "1s2.2s2.2p4", "valence_electrons": 6, "typical_valence": 2}, + {"Z": 9, "symbol": "F", "period": 2, "group": 17, "electron_configuration": "1s2.2s2.2p5", "valence_electrons": 7, "typical_valence": 1}, + {"Z": 10, "symbol": "Ne", "period": 2, "group": 18, "electron_configuration": "1s2.2s2.2p6", "valence_electrons": 8, "typical_valence": 0}, + {"Z": 11, "symbol": "Na", "period": 3, "group": 1, "electron_configuration": "[Ne].3s1", "valence_electrons": 1, "typical_valence": 1}, + {"Z": 12, "symbol": "Mg", "period": 3, "group": 2, "electron_configuration": "[Ne].3s2", "valence_electrons": 2, "typical_valence": 2}, + {"Z": 13, "symbol": "Al", "period": 3, "group": 13, "electron_configuration": "[Ne].3s2.3p1", "valence_electrons": 3, "typical_valence": 3}, + {"Z": 14, "symbol": "Si", "period": 3, "group": 14, "electron_configuration": "[Ne].3s2.3p2", "valence_electrons": 4, "typical_valence": 4}, + {"Z": 15, "symbol": "P", "period": 3, "group": 15, "electron_configuration": "[Ne].3s2.3p3", "valence_electrons": 5, "typical_valence": 3}, + {"Z": 16, "symbol": "S", "period": 3, "group": 16, "electron_configuration": "[Ne].3s2.3p4", "valence_electrons": 6, "typical_valence": 2}, + {"Z": 17, "symbol": "Cl", "period": 3, "group": 17, "electron_configuration": "[Ne].3s2.3p5", "valence_electrons": 7, "typical_valence": 1}, + {"Z": 18, "symbol": "Ar", "period": 3, "group": 18, "electron_configuration": "[Ne].3s2.3p6", "valence_electrons": 8, "typical_valence": 0} + ] +} diff --git a/research/epac/data/sealed_known_molecular_geometry.json b/research/epac/data/sealed_known_molecular_geometry.json new file mode 100644 index 0000000..f325d17 --- /dev/null +++ b/research/epac/data/sealed_known_molecular_geometry.json @@ -0,0 +1,12 @@ +{ + "schema": "epac.sealed-known-molecular-geometry", + "version": "v1", + "opened_only_after_construction": true, + "molecules": { + "H2": {"atom_count": 2, "known_shape": "linear"}, + "H2O": {"atom_count": 3, "known_shape": "bent"}, + "NH3": {"atom_count": 4, "known_shape": "trigonal-pyramidal"}, + "CH4": {"atom_count": 5, "known_shape": "tetrahedral"}, + "CO2": {"atom_count": 3, "known_shape": "linear"} + } +} diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md new file mode 100644 index 0000000..4f96a5a --- /dev/null +++ b/research/epac/docs/arity.md @@ -0,0 +1,65 @@ +# Dimensional arity + +Status: **CROSS-DOMAIN-HYPOTHESIS / provisional**. Not org canon. + +Dimension tells where. Arity tells what intersects at once. Degree tells how a +dimension is incident on declared couplings. + +```text +(z, x) ≠ (x, z) +(x, z) and (y, z) ↛ (x, y, z) without an explicit proof + +every physical instance of x has its own (z, x_i) +every physical instance of y has its own (z, y_j) +``` + +A second atom occurrence is a second instance. `(z, x_0)` does not cover `x_1`. +`(x_i, z)` does not satisfy `(z, x_i)`. Letters and chemical-symbol +abbreviations are nomenclature, not physics, and are not these instances. + +Precursors: each proton and each neutron is a closed gonol. The nucleus is +their affixiation. Neutrons couple to protons as `(proton_j, neutron_i)` with +slot charges `(+1, 0)`. Proton-proton and neutron-neutron are not inferred. +Hydrogen-1 is one proton and no neutrons. + +At atomic scale the hub is that closed nucleus and every electron instance has +its own `(nucleus, electron_i)` with slot charges `(Z, -1)`. Molecular scale +does not reopen nucleons or electrons: water remains `(O#2, H#0)` and +`(O#2, H#1)`. + +Degree is required. For ambient `{x,y,z}` with couplings `(z,x)` and `(z,y)`: + +```text +deg(z) = 2 at slot 0 +deg(x) = 1 at slot 1 +deg(y) = 1 at slot 1 +``` + +That incidence structure is the geometry of two binary couplings sharing `z`. +It is not a ternary coupling and not `(x,y)`. + +Charge state rides on each coupling from the math already present: per-slot +dimension charges (nuclear `Z` when the axis is an atom) and Möbius `ε` at +`t=0`. `(z,x)` with charges `(q_z, q_x, ε)` is not `(x,z)` with +`(q_x, q_z, ε)`. The three-dimensional structure **is** that combination — +oriented couplings, each arity's charge state, and degree. Two charged +arity-2 couplings on a degree-2 hub already occupy three participating axes. +It still does not declare `(x,y,z)`. + +Representing that 3 takes 4 dimensions: a quaternion +`(ε, q_z, q_x, q_y)`. The extra coordinate is the scalar, Möbius `ε`, already +in the math. It is not a fourth ambient axis, not Minkowski time, and not a +Hamilton-product proof of `(x,y,z)`. `ij = k` does not install a coupling. +Helium's nucleus plus two electrons is one local 3 in 4-representation; +the letters `H` and `e` are not those axes. A single binary (H₂, hydrogen +atom) is not a 3 and has no quaternion. + +Construction is `epac.public_gonol` on the UCNS Public Gonol carrier, not +`edcm.gonol`. + +Overlap of members is not a proof. Forbidden inference rules include +`overlap-closure`, `permutation-identity`, and `ambient-power-set`. + +See `epac_dimensional_arity.py`. After construction, `epac_comparison.py` reads +that 3-structure against sealed known chemistry. Sealed shape names stay out +of construction. diff --git a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md new file mode 100644 index 0000000..8a83573 --- /dev/null +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -0,0 +1,109 @@ +# Preregistration: molecular geometry from element gonols + +- Status: **CROSS-DOMAIN-HYPOTHESIS / provisional research candidate** +- Owner of record: `The-Interdependency/stack` → `research/epac/` +- Constructor: `epac.public_gonol` on the pinned UCNS Public Gonol carrier. + Not `edcm.gonol`. +- Comparison policy is frozen **before** construction. Known molecular-shape + labels are sealed and may be opened only by the comparison step. + +## Domain claims + +| Surface form | Term id | Claiming domain | Claimed sense | Excluded | +|---|---|---|---|---| +| element gonol | `epac.periodic.element_gonol` | epac candidate | closed gonol of one periodic-table element carrying Z, ground-state electron configuration, and typical main-group valence | molecular shape, bond angle, hybridization | +| valence arity | `epac.periodic.typical_valence` | epac candidate | main-group hydride valence from the periodic table (group-derived) | VSEPR domain count as a shape rule | +| affixiation | `metapat.affixiation_harmonics.affixiation` | METAPAT | identity-preserving higher-order relation | UCNS topology selection | +| UCNS coupling | `ucns.native-mobius-root-loop` | UCNS | established 360° frame flip / 720° restore | invented 3-space arrangement | +| molecular gonol | `epac.molecular.affixiated_whole` | epac candidate | closed recursive gonol of element-gonol participants | known chemistry shape names | +| predicted geometry | `epac.molecular.construction_invariants` | epac candidate | atom count, center valence, slot occupancy, Möbius frame sequence | sealed comparison labels | + +Collision check: physics/chemistry own empirical molecular shapes. This candidate +does not claim those senses during construction. Resolution: **clear** (separate +scopes) until comparison. + +## Frozen pipeline + +```text +proton gonols (charge +1) and neutron gonols (charge 0) + -> nucleus = affixiation of those nucleons; (proton_j, neutron_i) + -> every electron instance: (nucleus, electron_i) with charges (Z, -1) + -> close that atomic 3-structure inside the element gonol + -> unpaired-valence attachment sites + -> declared oriented (center, ligand_i) couplings with per-slot Z and Möbius ε + -> molecular three-dimensional structure = those atom-instance couplings + charge states + degree + -> each local 3 represented in 4 quaternion components (scalar ε plus the three axis charges) + -> molecular Public Gonol (closed atoms remain atomic participants) + -> construction invariants + -> (only then) compare to sealed known chemistry +``` + +## Inputs allowed in construction + +- atomic number Z, default isotope A +- each proton instance and each neutron instance of that isotope (counts must match Z and A−Z) +- every electron: n, l, m_l, m_s, shell, subshell +- hydrogenic angular identity Y_l^m, radial node count n-l-1 +- Slater atomic Z_eff and hydrogenic Rydberg energy -Z_eff²/n² +- unpaired valence electrons from Hund filling +- atomic s→p promotion in the same n when more unpaired sites are required +- caller-supplied stoichiometric formula (element counts only) + +## Inputs forbidden in construction + +- bond angles +- VSEPR shape names +- hybridization labels used as shape +- any sealed comparison filename contents + +## UCNS coupling candidate + +Only the implemented Möbius root loop is applied: + +```text +(t, ε) ~ (t + n, (-1)^n ε) +t = 0, 1, 2 +``` + +Public Gonol positions, when supplied, are identity coordinates. Position +operations remain `hmmm`. No spherical equal-spacing rule is added. + +## Molecules in this run + +`H2`, `H2O`, `NH3`, `CH4`, `CO2` + +## Comparison policy (frozen) + +Opened only after molecular gonols exist: + +1. Construction source and receipts must not contain the sealed shape labels. +2. Record construction invariants per formula. +3. Open `data/sealed_known_molecular_geometry.json`. +4. Ask whether the constructed three-dimensional structure distinguishes + formulas that chemistry distinguishes by shape. +5. Compare four signatures: charged oriented couplings plus degree (the + 3-structure already in the math); arity/degree topology without charge; + UCNS Möbius coupling; atomic unpaired (l, m_l) plus ligand shell content; + against a matched-information control of formula symbols only. + Do not import sealed shape names into construction. + +## Terminal standings for the hmmm question + +The question: does gonol geometry predict molecular shape, or merely reproduce +information already present in the inputs? + +- `SURVIVED` as prediction — only if the charged 3-structure is invariant + inside each sealed shape class, distinguishes different sealed classes, and + is not the matched-information control. +- `FALSIFIED` as prediction — if the 3-structure splits a sealed class, or + collapses classes chemistry splits, or if distinguishing power is already + present in valence+stoichiometry. +- `UNRESOLVED` — if the readout is incomplete. +- None of these standings select canon. + +## hmmm + +- Public Gonol function operations beyond carrier identity +- whether a later UCNS 3-space coupling exists that is not VSEPR imported +- expansion of the element table beyond Z=1–18 +- epac still has no canonical source repository diff --git a/research/epac/epac_atomic.py b/research/epac/epac_atomic.py new file mode 100644 index 0000000..82729ce --- /dev/null +++ b/research/epac/epac_atomic.py @@ -0,0 +1,280 @@ +"""Atomic and subatomic structure used by element gonols. + +Nothing here is molecular. Electrons are filled by Aufbau, Pauli, and Hund. +Angular identities are hydrogenic spherical harmonics labeled by (n, l, m_l). +Screening is Slater's atomic Z_eff. Energies are hydrogenic Rydberg units +with that Z_eff. Nucleus instances are default isotopes, identity only. + +Do not import the sealed molecular comparison file from this module. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Iterator + + +SUBSHELL_ORDER: tuple[tuple[int, int], ...] = ( + (1, 0), + (2, 0), + (2, 1), + (3, 0), + (3, 1), +) + +ISOTOPE_DEFAULTS: dict[int, int] = { + 1: 1, + 2: 4, + 3: 7, + 4: 9, + 5: 11, + 6: 12, + 7: 14, + 8: 16, + 9: 19, + 10: 20, + 11: 23, + 12: 24, + 13: 27, + 14: 28, + 15: 31, + 16: 32, + 17: 35, + 18: 40, +} + +SYMBOLS: tuple[str, ...] = ( + "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", + "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", +) + + +@dataclass(frozen=True, slots=True) +class ElectronState: + """One electron in an atom: quantum numbers plus atomic wave labels.""" + + index: int + n: int + l: int + m_l: int + m_s: int + shell: str + subshell: str + angular_id: str + radial_nodes: int + z_eff: str + e_rydberg: str + valence: bool + paired: bool + + +@dataclass(frozen=True, slots=True) +class AtomicRecord: + Z: int + symbol: str + period: int + group: int + A: int + proton_count: int + neutron_count: int + electrons: tuple[ElectronState, ...] + configuration: str + valence_n: int + valence_electrons: int + unpaired_valence: tuple[ElectronState, ...] + promoted_unpaired_valence: tuple[ElectronState, ...] + + +def _period_group(Z: int) -> tuple[int, int]: + if Z == 1: + return 1, 1 + if Z == 2: + return 1, 18 + if Z <= 4: + return 2, Z - 2 + if Z <= 10: + return 2, Z + 8 + if Z <= 12: + return 3, Z - 10 + return 3, Z + + +def _ml_down(l: int) -> tuple[int, ...]: + return tuple(range(l, -l - 1, -1)) + + +def _subshell_name(n: int, l: int) -> str: + return f"{n}{'spdf'[l]}" + + +def _angular_id(l: int, m_l: int) -> str: + return f"Y_l{l}_m{m_l}" + + +def _slater_zeff(Z: int, n: int, l: int, occupied: tuple[tuple[int, int], ...]) -> float: + """Slater screening for one electron in subshell (n, l).""" + + others = list(occupied) + others.remove((n, l)) + sigma = 0.0 + same_group = 0 + for on, ol in others: + if n == 1 and l == 0: + if on == 1 and ol == 0: + sigma += 0.30 + continue + if on == n and ((l in {0, 1} and ol in {0, 1}) or ol == l): + same_group += 1 + elif on == n - 1: + sigma += 0.85 + elif on <= n - 2: + sigma += 1.00 + sigma += 0.35 * same_group + return round(Z - sigma, 3) + + +def _fill_electrons(Z: int) -> tuple[ElectronState, ...]: + remaining = Z + occupied_pairs: list[tuple[int, int]] = [] + raw: list[tuple[int, int, int, int]] = [] + for n, l in SUBSHELL_ORDER: + capacity = 2 * (2 * l + 1) + take = min(remaining, capacity) + slots = [(m_l, 1) for m_l in _ml_down(l)] + [(m_l, -1) for m_l in _ml_down(l)] + for m_l, m_s in slots[:take]: + raw.append((n, l, m_l, m_s)) + occupied_pairs.append((n, l)) + remaining -= take + if remaining == 0: + break + valence_n = max(n for n, _l, _ml, _ms in raw) + occupied = tuple(occupied_pairs) + electrons: list[ElectronState] = [] + occupancy: dict[tuple[int, int, int], int] = {} + for n, l, m_l, m_s in raw: + occupancy[(n, l, m_l)] = occupancy.get((n, l, m_l), 0) + 1 + seen: dict[tuple[int, int, int], int] = {} + for index, (n, l, m_l, m_s) in enumerate(raw): + seen[(n, l, m_l)] = seen.get((n, l, m_l), 0) + 1 + z_eff = _slater_zeff(Z, n, l, occupied) + energy = round(-(z_eff ** 2) / (n ** 2), 6) + electrons.append( + ElectronState( + index=index, + n=n, + l=l, + m_l=m_l, + m_s=m_s, + shell=f"n{n}", + subshell=_subshell_name(n, l), + angular_id=_angular_id(l, m_l), + radial_nodes=n - l - 1, + z_eff=str(z_eff), + e_rydberg=str(energy), + valence=(n == valence_n), + paired=occupancy[(n, l, m_l)] == 2, + ) + ) + return tuple(electrons) + + +def _configuration(electrons: tuple[ElectronState, ...]) -> str: + counts: dict[str, int] = {} + order: list[str] = [] + for electron in electrons: + name = electron.subshell + if name not in counts: + order.append(name) + counts[name] = 0 + counts[name] += 1 + return ".".join(f"{name}{counts[name]}" for name in order) + + +def _unpaired_valence(electrons: tuple[ElectronState, ...]) -> tuple[ElectronState, ...]: + return tuple(e for e in electrons if e.valence and not e.paired and e.m_s == 1) + + +def _promoted_unpaired(electrons: tuple[ElectronState, ...]) -> tuple[ElectronState, ...]: + """Atomic valence promotion: move valence s pair into empty valence p to unpair. + + This is an atomic excited configuration (same n). It is not a molecular hybrid. + """ + + unpaired = list(_unpaired_valence(electrons)) + valence = [e for e in electrons if e.valence] + valence_n = valence[0].n if valence else 1 + if valence_n < 2: + return tuple(unpaired) + p_occupied_m = {e.m_l for e in valence if e.l == 1} + empty_p_m = [m for m in _ml_down(1) if m not in p_occupied_m] + s_pairs_by_orbital: dict[tuple[int, int, int], list[ElectronState]] = {} + for electron in valence: + if electron.l == 0 and electron.paired: + s_pairs_by_orbital.setdefault((electron.n, electron.l, electron.m_l), []).append(electron) + s_pair = next((pair for pair in s_pairs_by_orbital.values() if len(pair) == 2), None) + if s_pair is None or not empty_p_m: + return tuple(unpaired) + # Promote one valence s electron into the first empty p m while preserving + # the distinct electron that remains in the s orbital. + promoted_from_s = next((item for item in s_pair if item.m_s == 1), s_pair[0]) + remaining_s = next(item for item in s_pair if item.index != promoted_from_s.index) + new_p = ElectronState( + index=promoted_from_s.index, + n=valence_n, + l=1, + m_l=empty_p_m[0], + m_s=promoted_from_s.m_s, + shell=f"n{valence_n}", + subshell=_subshell_name(valence_n, 1), + angular_id=_angular_id(1, empty_p_m[0]), + radial_nodes=valence_n - 2, + z_eff=promoted_from_s.z_eff, + e_rydberg=promoted_from_s.e_rydberg, + valence=True, + paired=False, + ) + unpaired_s = ElectronState( + index=remaining_s.index, + n=remaining_s.n, + l=0, + m_l=remaining_s.m_l, + m_s=remaining_s.m_s, + shell=remaining_s.shell, + subshell=remaining_s.subshell, + angular_id=remaining_s.angular_id, + radial_nodes=remaining_s.radial_nodes, + z_eff=remaining_s.z_eff, + e_rydberg=remaining_s.e_rydberg, + valence=True, + paired=False, + ) + return tuple([unpaired_s, new_p, *[e for e in unpaired if not (e.l == 0)]]) + + +def atomic_record(Z: int) -> AtomicRecord: + if not 1 <= Z <= 18: + raise ValueError("this candidate table is Z=1-18") + electrons = _fill_electrons(Z) + valence_n = max(e.n for e in electrons) + period, group = _period_group(Z) + A = ISOTOPE_DEFAULTS[Z] + return AtomicRecord( + Z=Z, + symbol=SYMBOLS[Z - 1], + period=period, + group=group, + A=A, + proton_count=Z, + neutron_count=A - Z, + electrons=electrons, + configuration=_configuration(electrons), + valence_n=valence_n, + valence_electrons=sum(1 for e in electrons if e.valence), + unpaired_valence=_unpaired_valence(electrons), + promoted_unpaired_valence=_promoted_unpaired(electrons), + ) + + +def iter_table() -> Iterator[AtomicRecord]: + for Z in range(1, 19): + yield atomic_record(Z) diff --git a/research/epac/epac_comparison.py b/research/epac/epac_comparison.py new file mode 100644 index 0000000..bbefe5b --- /dev/null +++ b/research/epac/epac_comparison.py @@ -0,0 +1,158 @@ +"""Sealed-shape comparison after EPAC Public Gonol construction. + +The three-dimensional structure is the charged oriented couplings plus degree. +This module opens known chemistry only after those structures exist. It does +not import VSEPR names into construction. + +Usage guidance +-------------- + from epac_comparison import compare_after_construction + + record = compare_after_construction() + print(record["standings"]) +""" + +from __future__ import annotations + +import json +from collections import defaultdict +from pathlib import Path +from typing import Any, Mapping + +from epac_dimensional_arity import charged_structure_readout, topology_structure_readout +from epac_molecular import construct_declared_molecules, matched_information_control + + +EPAC_ROOT = Path(__file__).resolve().parent +SEALED_PATH = EPAC_ROOT / "data" / "sealed_known_molecular_geometry.json" +SEALED_SHAPE_LABELS = ("linear", "bent", "trigonal-pyramidal", "tetrahedral", "vsepr") +CONSTRUCTION_FILES = ( + "epac_atomic.py", + "epac_dimensional_arity.py", + "epac_molecular.py", + "epac_periodic.py", + "epac_public_gonol.py", +) + + +def construction_sources_omit_sealed_labels(root: Path = EPAC_ROOT) -> tuple[str, ...]: + hits: list[str] = [] + for name in CONSTRUCTION_FILES: + text = (root / name).read_text(encoding="utf-8").lower() + for label in SEALED_SHAPE_LABELS: + if label in text: + hits.append(f"{name}:{label}") + return tuple(hits) + + +def _partitions(values: Mapping[str, Any]) -> dict[Any, tuple[str, ...]]: + groups: dict[Any, list[str]] = defaultdict(list) + for formula, value in values.items(): + groups[value].append(formula) + return {key: tuple(sorted(formulas)) for key, formulas in groups.items()} + + +def _formula_sets(partitions: Mapping[Any, tuple[str, ...]]) -> frozenset[frozenset[str]]: + return frozenset(frozenset(group) for group in partitions.values()) + + +def _standing( + readout: Mapping[str, Any], + known_shapes: Mapping[str, str], + control: Mapping[str, Any], +) -> str: + """Preregistered shape-class prediction standing. + + SURVIVED only if the readout is invariant inside each sealed shape class, + distinguishes different sealed classes, and is not the matched-information + control. + """ + + by_shape: dict[str, set[Any]] = defaultdict(set) + for formula, shape in known_shapes.items(): + by_shape[shape].add(readout[formula]) + splits_a_class = any(len(values) > 1 for values in by_shape.values()) + collapsed_classes = False + shapes = list(by_shape) + for i, left in enumerate(shapes): + for right in shapes[i + 1 :]: + if by_shape[left] & by_shape[right]: + collapsed_classes = True + if splits_a_class or collapsed_classes: + return "FALSIFIED" + if _formula_sets(_partitions(readout)) == _formula_sets(_partitions(control)): + return "FALSIFIED" + if _formula_sets(_partitions(readout)) == _formula_sets(_partitions(known_shapes)): + return "SURVIVED" + return "UNRESOLVED" + + +def compare_after_construction(root: Path = EPAC_ROOT) -> dict[str, Any]: + """Construct first, then open the sealed shapes, then score standings.""" + + label_hits = construction_sources_omit_sealed_labels(root) + constructions = construct_declared_molecules() + charged = {} + topology = {} + mobius = {} + atomic = {} + control = {} + for formula, construction in constructions.items(): + structure = construction.receipt.structure + if structure is None: + raise ValueError(f"{formula} closed without a three-dimensional structure") + charged[formula] = charged_structure_readout(structure) + topology[formula] = topology_structure_readout(structure) + mobius[formula] = construction.invariants["ucns_coupling_signature"] + atomic[formula] = construction.invariants["atomic_coupling_signature"] + control[formula] = matched_information_control(construction.invariants) + + sealed = json.loads((root / "data" / "sealed_known_molecular_geometry.json").read_text(encoding="utf-8")) + known_shapes = {formula: sealed["molecules"][formula]["known_shape"] for formula in constructions} + + return { + "opened_after_construction": True, + "construction_omits_sealed_labels": not label_hits, + "sealed_label_hits": label_hits, + "known_shapes": known_shapes, + "readouts": { + "charged_3_structure": {formula: list(value) for formula, value in charged.items()}, + "topology_3_structure": {formula: list(value) for formula, value in topology.items()}, + }, + "partitions": { + "known_shapes": {shape: formulas for shape, formulas in _partitions(known_shapes).items()}, + "charged_3_structure": { + str(index): formulas for index, formulas in enumerate(_partitions(charged).values()) + }, + "topology_3_structure": { + str(index): formulas for index, formulas in enumerate(_partitions(topology).values()) + }, + }, + "topology_collapses_h2o_with_co2": topology["H2O"] == topology["CO2"], + "charged_distinguishes_h2o_from_co2": charged["H2O"] != charged["CO2"], + "linear_class_split_by_charged_structure": charged["H2"] != charged["CO2"], + "standings": { + "charged_3_structure_as_sealed_shape_prediction": _standing(charged, known_shapes, control), + "topology_3_structure_as_sealed_shape_prediction": _standing(topology, known_shapes, control), + "ucns_mobius_as_sealed_shape_prediction": _standing(mobius, known_shapes, control), + "atomic_shells_as_sealed_shape_prediction": _standing(atomic, known_shapes, control), + }, + "nonclaims": ( + "not selected canon", + "not an imported VSEPR construction rule", + "not a cartesian embedding", + ), + "hmmm": ( + "whether a later mapping from charged 3-structure to empirical angles exists without importing VSEPR", + "exact UCNS geometric operation of each Public Gonol function position", + ), + } + + +__all__ = [ + "CONSTRUCTION_FILES", + "SEALED_PATH", + "SEALED_SHAPE_LABELS", + "compare_after_construction", + "construction_sources_omit_sealed_labels", +] diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py new file mode 100644 index 0000000..feaf55b --- /dev/null +++ b/research/epac/epac_dimensional_arity.py @@ -0,0 +1,645 @@ +"""Declared dimensional arity, orientation, and degree. + +Dimension tells where. Arity tells what intersects at once. Degree tells how +a dimension is incident on declared couplings. + +``(z, x)`` is not ``(x, z)``. Shared members of ``(x, z)`` and ``(y, z)`` do +not yield ``(x, y, z)`` without an explicit proof. Overlap is not a proof. + +Every physical instance of ``x`` has its own declared ``(z, x_i)``. Every +physical instance of ``y`` has its own declared ``(z, y_j)``. A second +occurrence is a second instance, not a reuse of the first coupling. +``(x_i, z)`` does not satisfy ``(z, x_i)``. Letters and abbreviations are +not this domain. At atomic scale the instances are electrons and the hub is +the nucleus. At molecular scale the instances are closed atom gonols. + +The three-dimensional structure is the combination of declared oriented +couplings, their arity charge states, and degree. That span can involve three +axes through two charged binaries. It is not a ternary coupling. + +Representing that 3 takes 4 dimensions: a quaternion. The extra coordinate is +the scalar (Möbius ε already in the math). It is not a fourth ambient axis, +not Minkowski time, and not a Hamilton-product proof of ``(x, y, z)``. + +Domain claims (provisional): + +- dimension: independent coordinate axis +- arity: number of dimensions in one declared coupling +- degree: incidence of one dimension on declared couplings, including slot +- coupling: ordered declaration of participating dimensions +- charge state: per-slot charges on a coupling, with Möbius ε at t=0 +- instance: occurrence-addressed physical axis or atom; each x_i / y_j is distinct +- quaternion: 4-component representation of one local 3-structure + +Collision: edcm.gonol arity_policy counts gonol participants, not dimensional +intersections. Letters/abbreviations are nomenclature, not physics instances. +Quaternion basis names are representation labels, not letters-as-physics. +""" + +from __future__ import annotations + +from collections.abc import Iterable, Mapping, Sequence +from dataclasses import dataclass + + +# Established UCNS Möbius frame sign at t=0: ε in (t, ε) ~ (t+n, (-1)^n ε). +MOBIUS_EPSILON_T0 = 1 +REPRESENTED_STRUCTURE_DIMENSION = 3 +QUATERNION_REPRESENTATION_DIMENSION = 4 +QUATERNION_SCALAR_AXIS = "epac.representation.quaternion.scalar" + +FORBIDDEN_INFERENCE_RULES = frozenset( + { + "ambient-power-set", + "overlap-closure", + "permutation-identity", + "shared-dimension-join", + "hamilton-product-closure", + } +) + + +class DimensionalArityError(ValueError): + """Fail-closed dimensional arity error.""" + + +@dataclass(frozen=True, slots=True) +class Dimension: + """One independent coordinate axis, with optional established charge.""" + + id: str + charge: int | None = None + + def __post_init__(self) -> None: + if not isinstance(self.id, str) or not self.id or self.id.isspace(): + raise DimensionalArityError("dimension id must be exact non-empty text") + if self.charge is not None and (isinstance(self.charge, bool) or not isinstance(self.charge, int)): + raise DimensionalArityError("dimension charge must be an int or None") + + +@dataclass(frozen=True, slots=True) +class Coupling: + """One explicitly declared ordered intersection of dimensions.""" + + dimensions: tuple[Dimension, ...] + + def __post_init__(self) -> None: + if not self.dimensions: + raise DimensionalArityError("a coupling must declare at least one dimension") + ids = [dimension.id for dimension in self.dimensions] + if len(ids) != len(set(ids)): + raise DimensionalArityError("a coupling cannot repeat a dimension") + + @property + def arity(self) -> int: + return len(self.dimensions) + + @property + def declared_ids(self) -> tuple[str, ...]: + return tuple(dimension.id for dimension in self.dimensions) + + @property + def slot_charges(self) -> tuple[int | None, ...]: + return tuple(dimension.charge for dimension in self.dimensions) + + @property + def charge_state(self) -> tuple[tuple[int | None, ...], int]: + """Per-slot charges plus Möbius ε at t=0. Ordered: (z,x) ≠ (x,z).""" + + return (self.slot_charges, MOBIUS_EPSILON_T0) + + +@dataclass(frozen=True, slots=True) +class DegreeRelation: + """How one dimension sits in declared couplings. + + degree is the number of incidences. slot_degrees counts incidences at each + ordered position. (z,x) puts z in slot 0; (x,z) puts z in slot 1. + """ + + dimension: Dimension + incidences: tuple[tuple[tuple[str, ...], int], ...] + + @property + def degree(self) -> int: + return len(self.incidences) + + @property + def slot_degrees(self) -> tuple[tuple[int, int], ...]: + counts: dict[int, int] = {} + for _declared, slot in self.incidences: + counts[slot] = counts.get(slot, 0) + 1 + return tuple(sorted(counts.items())) + + +@dataclass(frozen=True, slots=True) +class CouplingProof: + """Certificate required before a higher-arity coupling may be installed.""" + + conclusion: Coupling + premises: tuple[Coupling, ...] + rule_id: str + + def __post_init__(self) -> None: + if not isinstance(self.rule_id, str) or not self.rule_id or self.rule_id.isspace(): + raise DimensionalArityError("a coupling proof must declare a non-empty rule_id") + if self.rule_id in FORBIDDEN_INFERENCE_RULES: + raise DimensionalArityError( + f"rule {self.rule_id!r} is not a proof; overlap/permutation/ambient fill are forbidden" + ) + if not self.premises: + raise DimensionalArityError("a coupling proof must cite at least one premise coupling") + + +@dataclass(frozen=True, slots=True) +class DimensionalSpace: + """Ambient axes, declared couplings, degree relations, and optional proofs.""" + + ambient_dimensions: tuple[Dimension, ...] + couplings: tuple[Coupling, ...] + proofs: tuple[CouplingProof, ...] = () + + def __post_init__(self) -> None: + ambient_ids = [dimension.id for dimension in self.ambient_dimensions] + if len(ambient_ids) != len(set(ambient_ids)): + raise DimensionalArityError("ambient dimensions must be unique") + ambient = set(ambient_ids) + for item in self.couplings: + missing = [name for name in item.declared_ids if name not in ambient] + if missing: + raise DimensionalArityError( + f"coupling {item.declared_ids} uses undeclared dimensions {tuple(missing)}" + ) + declared = {item.declared_ids for item in self.couplings} + for proof in self.proofs: + conclusion_missing = [ + name for name in proof.conclusion.declared_ids if name not in ambient + ] + if conclusion_missing: + raise DimensionalArityError( + f"proof {proof.rule_id!r} conclusion uses undeclared dimensions {tuple(conclusion_missing)}" + ) + if proof.conclusion.declared_ids not in declared: + raise DimensionalArityError( + f"proof {proof.rule_id!r} conclusion {proof.conclusion.declared_ids} is not declared" + ) + for premise in proof.premises: + if premise.declared_ids not in declared: + raise DimensionalArityError( + f"proof {proof.rule_id!r} cites missing premise {premise.declared_ids}" + ) + + +def _require_dimension_id_sequence(value: Sequence[str], *, field: str) -> tuple[str, ...]: + if not isinstance(value, Sequence) or isinstance(value, (str, bytes)): + raise DimensionalArityError(f"{field} must be an ordered declaration sequence") + return tuple(value) + + +def dimension(id: str, charge: int | None = None) -> Dimension: + return Dimension(id, charge) + + +def coupling(dimension_ids: Sequence[str], charges: Mapping[str, int] | None = None) -> Coupling: + ids = _require_dimension_id_sequence(dimension_ids, field="coupling dimensions") + charge_map = dict(charges or {}) + return Coupling(tuple(Dimension(item, charge_map.get(item)) for item in ids)) + + +def space( + ambient_ids: Sequence[str], + coupling_declarations: Sequence[Sequence[str]] = (), + proofs: Sequence[CouplingProof] = (), + charges: Mapping[str, int] | None = None, +) -> DimensionalSpace: + ambient_ids = _require_dimension_id_sequence(ambient_ids, field="ambient dimensions") + charge_map = dict(charges or {}) + ambient = tuple(Dimension(item, charge_map.get(item)) for item in ambient_ids) + by_id = {item.id: item for item in ambient} + declared = [] + for item in coupling_declarations: + ids = _require_dimension_id_sequence(item, field="each coupling declaration") + declared.append(Coupling(tuple(by_id[name] if name in by_id else Dimension(name) for name in ids))) + return DimensionalSpace( + ambient_dimensions=ambient, + couplings=tuple(declared), + proofs=tuple(proofs), + ) + + +def degree_relations(declared: DimensionalSpace) -> tuple[DegreeRelation, ...]: + incidences: dict[str, list[tuple[tuple[str, ...], int]]] = { + item.id: [] for item in declared.ambient_dimensions + } + for item in declared.couplings: + for slot, axis in enumerate(item.dimensions): + incidences[axis.id].append((item.declared_ids, slot)) + return tuple( + DegreeRelation(dimension=axis, incidences=tuple(incidences[axis.id])) + for axis in declared.ambient_dimensions + ) + + +def observed_common_ids(left: Coupling, right: Coupling) -> frozenset[str]: + """Common dimension ids. Not a coupling and not a proof.""" + + return frozenset(left.declared_ids) & frozenset(right.declared_ids) + + +def has_declared_coupling(declared: DimensionalSpace, dimension_ids: Sequence[str]) -> bool: + target = _require_dimension_id_sequence(dimension_ids, field="coupling lookup dimensions") + return any(item.declared_ids == target for item in declared.couplings) + + +def instances_missing_oriented_hub_coupling( + declared: DimensionalSpace, + *, + hub_id: str, + instance_ids: Sequence[str], +) -> tuple[str, ...]: + """Instances that do not have a declared (hub, instance) coupling. + + (instance, hub) does not count. One (z, x) does not cover a second x. + """ + + ambient = {axis.id for axis in declared.ambient_dimensions} + if hub_id not in ambient: + raise DimensionalArityError(f"hub {hub_id!r} is not an ambient dimension") + missing: list[str] = [] + seen: set[str] = set() + for instance_id in instance_ids: + if not isinstance(instance_id, str) or not instance_id or instance_id.isspace(): + raise DimensionalArityError("instance id must be exact non-empty text") + if instance_id == hub_id: + raise DimensionalArityError("the hub is not an instance of x or y") + if instance_id not in ambient: + raise DimensionalArityError(f"instance {instance_id!r} is not an ambient dimension") + if instance_id in seen: + raise DimensionalArityError(f"instance {instance_id!r} is repeated; occurrences must be unique") + seen.add(instance_id) + if not has_declared_coupling(declared, [hub_id, instance_id]): + missing.append(instance_id) + return tuple(missing) + + +def require_every_instance_has_oriented_hub_coupling( + declared: DimensionalSpace, + *, + hub_id: str, + instance_ids: Sequence[str], +) -> None: + """Fail closed unless every instance has its own (z, instance).""" + + missing = instances_missing_oriented_hub_coupling( + declared, hub_id=hub_id, instance_ids=instance_ids + ) + if missing: + raise DimensionalArityError( + f"every instance must have declared ({hub_id}, instance); missing {tuple(missing)}" + ) + + +def oriented_instance_couplings( + declared: DimensionalSpace, + *, + hub_id: str, + instance_ids: Sequence[str], +) -> tuple[tuple[str, str], ...]: + """The (z, x_i) / (z, y_j) coupling for each instance, in instance order.""" + + require_every_instance_has_oriented_hub_coupling( + declared, hub_id=hub_id, instance_ids=instance_ids + ) + return tuple((hub_id, instance_id) for instance_id in instance_ids) + + +def _bind_coupling_to_ambient( + item: Coupling, ambient_by_id: Mapping[str, Dimension] +) -> Coupling: + dimensions: list[Dimension] = [] + for dimension in item.dimensions: + ambient = ambient_by_id.get(dimension.id) + if ambient is None: + raise DimensionalArityError( + f"proven coupling {item.declared_ids} uses undeclared dimension {dimension.id!r}" + ) + if dimension.charge is not None and dimension.charge != ambient.charge: + raise DimensionalArityError( + f"proof conclusion charge for {dimension.id!r} conflicts with ambient charge" + ) + dimensions.append(ambient) + return Coupling(tuple(dimensions)) + + +def install_proven_coupling(declared: DimensionalSpace, proof: CouplingProof) -> DimensionalSpace: + """Add a coupling only with an explicit non-forbidden proof.""" + + ambient_by_id = {axis.id: axis for axis in declared.ambient_dimensions} + bound_conclusion = _bind_coupling_to_ambient(proof.conclusion, ambient_by_id) + bound_proof = CouplingProof( + conclusion=bound_conclusion, + premises=proof.premises, + rule_id=proof.rule_id, + ) + declared_ids = {item.declared_ids for item in declared.couplings} + for premise in bound_proof.premises: + if premise.declared_ids not in declared_ids: + raise DimensionalArityError( + f"proof {proof.rule_id!r} cites missing premise {premise.declared_ids}" + ) + if bound_conclusion.declared_ids in declared_ids: + return DimensionalSpace( + ambient_dimensions=declared.ambient_dimensions, + couplings=declared.couplings, + proofs=declared.proofs + (bound_proof,), + ) + return DimensionalSpace( + ambient_dimensions=declared.ambient_dimensions, + couplings=declared.couplings + (bound_conclusion,), + proofs=declared.proofs + (bound_proof,), + ) + + +def local_three_structures(declared: DimensionalSpace) -> tuple[tuple[str, str, str], ...]: + """Each hub with two hub-first arity-2 instances is one local 3. + + ``(z, x)`` and ``(z, y)`` yield ``(z, x, y)`` as a represented triple. + That is not a declared ternary coupling. One coupling is not a 3. + """ + + by_hub: dict[str, list[str]] = {} + for item in declared.couplings: + if item.arity != 2: + continue + hub_id, instance_id = item.declared_ids + by_hub.setdefault(hub_id, []).append(instance_id) + threes: list[tuple[str, str, str]] = [] + for hub_id, instance_ids in by_hub.items(): + for index, first in enumerate(instance_ids): + for second in instance_ids[index + 1 :]: + threes.append((hub_id, first, second)) + return tuple(threes) + + +def quaternion_of_local_three( + declared: DimensionalSpace, + represented_ids: tuple[str, str, str], +) -> Mapping[str, object]: + """4 components for one 3: scalar ε plus the three axis charges. + + Hamilton product is not a coupling proof. The scalar axis is representation, + not ambient. + """ + + charges = {axis.id: axis.charge for axis in declared.ambient_dimensions} + hub_id, first_id, second_id = represented_ids + return { + "components": ( + MOBIUS_EPSILON_T0, + charges.get(hub_id), + charges.get(first_id), + charges.get(second_id), + ), + "axes": (QUATERNION_SCALAR_AXIS, hub_id, first_id, second_id), + "represented_ids": represented_ids, + "representation_dimension": QUATERNION_REPRESENTATION_DIMENSION, + "represented_structure_dimension": REPRESENTED_STRUCTURE_DIMENSION, + "hamilton_product_is_coupling_proof": False, + "scalar_axis_is_ambient": False, + } + + +def quaternions_from_declared_couplings( + declared: DimensionalSpace, +) -> tuple[Mapping[str, object], ...]: + return tuple( + quaternion_of_local_three(declared, represented) + for represented in local_three_structures(declared) + ) + + +def structure_from_charged_couplings(declared: DimensionalSpace) -> Mapping[str, object]: + """The three-dimensional structure already present in the couplings. + + Each part is one declared oriented coupling together with its arity charge + state. Degree records how those parts sit on shared axes. Representing + each local 3 takes a 4-component quaternion. This is not an inferred + cartesian embedding and not a ternary coupling. + """ + + degrees = degree_relations(declared) + parts = tuple( + { + "coupling": item.declared_ids, + "arity": item.arity, + "charge_state": item.charge_state, + } + for item in declared.couplings + ) + return { + "kind": "combination-of-oriented-couplings-and-arity-charge-states", + "parts": parts, + "degree": tuple( + { + "dimension": item.dimension.id, + "charge": item.dimension.charge, + "degree": item.degree, + "slot_degrees": item.slot_degrees, + "incidences": item.incidences, + } + for item in degrees + if item.degree + ), + "participating_dimension_count": len( + {name for item in declared.couplings for name in item.declared_ids} + ), + "ternary_coupling_declared": any(item.arity == 3 for item in declared.couplings), + "inferred_cartesian_embedding": False, + "representation_kind": "quaternion", + "representation_dimension": QUATERNION_REPRESENTATION_DIMENSION, + "represented_structure_dimension": REPRESENTED_STRUCTURE_DIMENSION, + "quaternions": quaternions_from_declared_couplings(declared), + } + + +def _tuple_tree(value: object) -> object: + if isinstance(value, Mapping): + return tuple(sorted((str(key), _tuple_tree(item)) for key, item in value.items())) + if isinstance(value, Sequence) and not isinstance(value, (str, bytes)): + return tuple(_tuple_tree(item) for item in value) + return value + + +def _sortable_tree(value: object) -> object: + if value is None: + return (0,) + if isinstance(value, bool): + return (1, int(value)) + if isinstance(value, int): + return (2, value) + if isinstance(value, str): + return (3, value) + if isinstance(value, Mapping): + return (4, tuple(sorted((str(key), _sortable_tree(item)) for key, item in value.items()))) + if isinstance(value, Sequence) and not isinstance(value, (str, bytes)): + return (5, tuple(_sortable_tree(item) for item in value)) + return (6, repr(value)) + + +def charged_structure_readout(structure: Mapping[str, object]) -> tuple[object, ...]: + """Order-invariant 3-structure: couplings + charge states + degree. + + Each instance stays in the coupling ids. Slot order inside each coupling is + kept, so ``(8, 1)`` is not ``(1, 8)`` and ``(z, x0)`` is not ``(z, x1)``. + """ + + parts = tuple( + ( + int(part["arity"]), + _tuple_tree(part["charge_state"]), + _tuple_tree(part["coupling"]), + ) + for part in structure["parts"] + ) + degree = tuple( + ( + int(item["degree"]), + _tuple_tree(item["slot_degrees"]), + item["charge"], + ) + for item in structure["degree"] + ) + parts = tuple(sorted(parts, key=_sortable_tree)) + degree = tuple(sorted(degree, key=_sortable_tree)) + return ( + parts, + degree, + int(structure["participating_dimension_count"]), + bool(structure["ternary_coupling_declared"]), + ) + + +def topology_structure_readout(structure: Mapping[str, object]) -> tuple[object, ...]: + """Arity and degree only. Charge state is omitted.""" + + parts, degree, participating, ternary = charged_structure_readout(structure) + return ( + tuple(item[0] for item in parts), + tuple((deg, slots) for deg, slots, _charge in degree), + participating, + ternary, + ) + + +def quaternion_structure_readout(structure: Mapping[str, object]) -> tuple[object, ...]: + """Order-invariant 4-component representations of each local 3.""" + + return tuple( + sorted( + ( + _tuple_tree(item["components"]), + _tuple_tree(item["represented_ids"]), + ) + for item in structure.get("quaternions", ()) + ) + ) + + +def geometry_from_declared_couplings(declared: DimensionalSpace) -> Mapping[str, object]: + degrees = degree_relations(declared) + couplings = tuple( + { + "declared_ids": item.declared_ids, + "arity": item.arity, + "slot_charges": item.slot_charges, + "charge_state": item.charge_state, + "mobius_epsilon_t0": MOBIUS_EPSILON_T0, + } + for item in declared.couplings + ) + return { + "ambient_ids": tuple(item.id for item in declared.ambient_dimensions), + "ambient_count": len(declared.ambient_dimensions), + "couplings": couplings, + "participating_ids": tuple( + dict.fromkeys(name for item in declared.couplings for name in item.declared_ids) + ), + "arity_counts": _arity_counts(declared.couplings), + "degree_relations": tuple( + { + "dimension": item.dimension.id, + "degree": item.degree, + "slot_degrees": item.slot_degrees, + "incidences": item.incidences, + } + for item in degrees + ), + "observed_common_ids": tuple(_common_records(declared.couplings)), + "proofs": tuple( + { + "rule_id": proof.rule_id, + "premises": tuple(item.declared_ids for item in proof.premises), + "conclusion": proof.conclusion.declared_ids, + } + for proof in declared.proofs + ), + "inferred_from_ambient": False, + "inferred_higher_arity_from_overlap": False, + "zx_equals_xz": False, + "structure": structure_from_charged_couplings(declared), + } + + +def _arity_counts(couplings: tuple[Coupling, ...]) -> tuple[tuple[int, int], ...]: + counts: dict[int, int] = {} + for item in couplings: + counts[item.arity] = counts.get(item.arity, 0) + 1 + return tuple(sorted(counts.items())) + + +def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, object]]: + for i, left in enumerate(couplings): + for j, right in enumerate(couplings): + if j <= i: + continue + shared = observed_common_ids(left, right) + if shared: + yield { + "left": left.declared_ids, + "right": right.declared_ids, + "common_ids": tuple(sorted(shared)), + "proof_of_higher_arity": False, + } + + +__all__ = [ + "Coupling", + "CouplingProof", + "DegreeRelation", + "Dimension", + "DimensionalArityError", + "DimensionalSpace", + "FORBIDDEN_INFERENCE_RULES", + "MOBIUS_EPSILON_T0", + "QUATERNION_REPRESENTATION_DIMENSION", + "QUATERNION_SCALAR_AXIS", + "REPRESENTED_STRUCTURE_DIMENSION", + "charged_structure_readout", + "coupling", + "degree_relations", + "dimension", + "geometry_from_declared_couplings", + "has_declared_coupling", + "install_proven_coupling", + "instances_missing_oriented_hub_coupling", + "local_three_structures", + "observed_common_ids", + "oriented_instance_couplings", + "quaternion_of_local_three", + "quaternion_structure_readout", + "quaternions_from_declared_couplings", + "require_every_instance_has_oriented_hub_coupling", + "space", + "structure_from_charged_couplings", + "topology_structure_readout", +] diff --git a/research/epac/epac_molecular.py b/research/epac/epac_molecular.py new file mode 100644 index 0000000..e3f8f11 --- /dev/null +++ b/research/epac/epac_molecular.py @@ -0,0 +1,285 @@ +"""Molecular EPAC Public Gonols from atomic electron-shell gonols. + +Attachment sites are unpaired valence electrons (atomic Hund filling). +If ligand count exceeds ground-state unpaired count, the atomic promoted +valence set (s→p in the same n) is used. Ligand and center (l, m_l) sets +are construction invariants. Construction uses ``epac.public_gonol``, not +``edcm.gonol``. No sealed molecular-shape file is opened here. + +The three-dimensional structure is the combination of declared oriented +couplings and each arity's charge state (nuclear Z plus Möbius ε at t=0) +with degree. Every ligand instance has its own (center, instance) coupling. +It is not an inferred cartesian embedding. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Mapping + +from ucns.direct_mobius import native_mobius_state + +from epac_atomic import AtomicRecord +from epac_dimensional_arity import ( + charged_structure_readout, + geometry_from_declared_couplings, + oriented_instance_couplings, + space, + topology_structure_readout, +) +from epac_periodic import atomic_of, carried, construct_element_gonol, symbol_of +from epac_public_gonol import ClosedPublicGonol, PublicGonolReceipt, construct_public_gonol, replay_public_gonol + + +MOLECULE_COMPOSITIONS: Mapping[str, tuple[tuple[str, int], ...]] = { + "H2": (("H", 2),), + "H2O": (("H", 2), ("O", 1)), + "NH3": (("N", 1), ("H", 3)), + "CH4": (("C", 1), ("H", 4)), + "CO2": (("C", 1), ("O", 2)), +} + +RELATION = "epac.affixiation.unpaired-valence" + + +@dataclass(frozen=True, slots=True) +class MolecularConstruction: + formula: str + receipt: PublicGonolReceipt + invariants: Mapping[str, Any] + + +def _instantiate(composition: tuple[tuple[str, int], ...]) -> tuple[ClosedPublicGonol, ...]: + instances: list[ClosedPublicGonol] = [] + occurrence = 0 + for symbol, count in composition: + for _ in range(count): + instances.append(construct_element_gonol(symbol, occurrence=occurrence).gonol) + occurrence += 1 + return tuple(instances) + + +def _record_for(gonol: ClosedPublicGonol) -> AtomicRecord: + return atomic_of(symbol_of(gonol)) + + +def _choose_center(participants: tuple[ClosedPublicGonol, ...]) -> ClosedPublicGonol | None: + """Center is the unique singleton symbol when ligands share another symbol. + + This is stoichiometric, not a shape rule. H2 has no singleton. + """ + + counts: dict[str, int] = {} + for item in participants: + counts[symbol_of(item)] = counts.get(symbol_of(item), 0) + 1 + singletons = [symbol for symbol, count in counts.items() if count == 1] + if len(singletons) == 1 and len(counts) > 1: + symbol = singletons[0] + return next(item for item in participants if symbol_of(item) == symbol) + return None + + +def _attachment_set(record: AtomicRecord, needed: int) -> tuple[tuple[int, int], ...]: + ground = tuple((e.l, e.m_l) for e in record.unpaired_valence) + if len(ground) >= needed: + return ground[:needed] + promoted = tuple((e.l, e.m_l) for e in record.promoted_unpaired_valence) + if len(promoted) >= needed: + return promoted[:needed] + raise ValueError( + f"{record.symbol} has {len(ground)} unpaired valence electrons; " + f"{needed} attachment sites were requested" + ) + + +def _atom_dimension_id(gonol: ClosedPublicGonol) -> str: + return f"{symbol_of(gonol)}#{gonol.occurrence}" + + +def _declared_dimensional_space( + participants: tuple[ClosedPublicGonol, ...], + center: ClosedPublicGonol | None, + ligands: tuple[ClosedPublicGonol, ...], +): + ambient = [_atom_dimension_id(item) for item in participants] + charges = {_atom_dimension_id(item): int(carried(item, "Z")) for item in participants} + if center is None: + declarations = [[_atom_dimension_id(participants[0]), _atom_dimension_id(participants[1])]] + else: + center_id = _atom_dimension_id(center) + declarations = [[center_id, _atom_dimension_id(ligand)] for ligand in ligands] + return space(ambient, declarations, charges=charges) + + +def _site_label(site: tuple[int, int]) -> str: + return f"{site[0]}:{site[1]}" + + +def _mobius_coupling( + *, + participants: tuple[ClosedPublicGonol, ...], + center: ClosedPublicGonol | None, + ligands: tuple[ClosedPublicGonol, ...], + center_sites: tuple[tuple[int, int], ...], + ligand_sites: tuple[tuple[tuple[int, int], ...], ...], +) -> Mapping[str, Any]: + origin = native_mobius_state(0) + one = origin.advance(1) + two = origin.advance(2) + if center is None: + attachment_slots = tuple( + { + "slot": slot, + "participant": _atom_dimension_id(participant), + "site": _site_label(site), + } + for slot, (participant, sites) in enumerate(zip(participants, ligand_sites)) + for site in sites + ) + else: + flattened_ligand_sites = tuple( + (ligand, site) + for ligand, sites in zip(ligands, ligand_sites) + for site in sites + ) + attachment_slots = tuple( + { + "slot": slot, + "center": _atom_dimension_id(center), + "center_site": _site_label(center_site), + "ligand": _atom_dimension_id(ligand), + "ligand_site": _site_label(ligand_site), + } + for slot, (center_site, (ligand, ligand_site)) in enumerate( + zip(center_sites, flattened_ligand_sites) + ) + ) + return { + "law": "ucns.native-mobius-root-loop", + "binding": "declared-participants-and-valence-attachment-sites", + "parameter": "turn-index-over-declared-attachment-evidence", + "participant_axes": tuple(_atom_dimension_id(item) for item in participants), + "attachment_slots": attachment_slots, + "t": [0, 1, 2], + "visible_phase": [ + str(origin.visible_key[1]), + str(one.visible_key[1]), + str(two.visible_key[1]), + ], + "frame": [origin.frame.value, one.frame.value, two.frame.value], + "complete_restored": two.complete_key == origin.complete_key, + "one_turn_flips_frame": one.frame != origin.frame and one.visible_key == origin.visible_key, + } + + +def construct_molecule(formula: str) -> MolecularConstruction: + if formula not in MOLECULE_COMPOSITIONS: + raise ValueError(f"formula {formula!r} is outside the declared run") + participants = _instantiate(MOLECULE_COMPOSITIONS[formula]) + center = _choose_center(participants) + if center is None: + ligands = () + center_sites: tuple[tuple[int, int], ...] = () + if len(participants) != 2: + raise ValueError("symmetric affixiation is declared only for two equal atoms") + left, right = (_record_for(participants[0]), _record_for(participants[1])) + ligand_sites = ( + tuple((e.l, e.m_l) for e in left.unpaired_valence), + tuple((e.l, e.m_l) for e in right.unpaired_valence), + ) + used_promotion = False + else: + ligands = tuple(item for item in participants if item is not center) + center_record = _record_for(center) + ground = tuple((e.l, e.m_l) for e in center_record.unpaired_valence) + ligand_sites = tuple( + tuple((e.l, e.m_l) for e in _record_for(item).unpaired_valence) for item in ligands + ) + needed = sum(len(sites) for sites in ligand_sites) + used_promotion = needed > len(ground) + center_sites = _attachment_set(center_record, needed) + mobius = _mobius_coupling( + participants=participants, + center=center, + ligands=ligands, + center_sites=center_sites, + ligand_sites=ligand_sites, + ) + dimensional = _declared_dimensional_space(participants, center, ligands) + instance_couplings: tuple[tuple[str, str], ...] = () + if center is not None: + instance_couplings = oriented_instance_couplings( + dimensional, + hub_id=_atom_dimension_id(center), + instance_ids=tuple(_atom_dimension_id(item) for item in ligands), + ) + geometry = geometry_from_declared_couplings(dimensional) + receipt = construct_public_gonol( + source_id=f"epac.molecule:{formula}", + relation=RELATION, + participants=participants, + couplings=geometry["couplings"], + structure=geometry["structure"], + ) + distinct_p_m = tuple(sorted({m for l, m in center_sites if l == 1})) + ligand_has_p = any(any(l == 1 for l, _m in sites) for sites in ligand_sites) + invariants = { + "formula": formula, + "atom_count": len(participants), + "center_symbol": None if center is None else symbol_of(center), + "center_Z": None if center is None else carried(center, "Z"), + "center_configuration": None if center is None else carried(center, "electron-configuration"), + "center_valence_electrons": None if center is None else carried(center, "valence-electrons"), + "center_unpaired_lm": [f"{l}:{m}" for l, m in center_sites], + "center_attachment_site_count": len(center_sites), + "ligand_attachment_site_count": sum(len(sites) for sites in ligand_sites), + "center_used_atomic_promotion": used_promotion, + "center_distinct_p_m": [str(m) for m in distinct_p_m], + "ligand_symbols": [symbol_of(item) for item in ligands], + "ligand_unpaired_lm": [[f"{l}:{m}" for l, m in sites] for sites in ligand_sites], + "ligand_has_p": ligand_has_p, + "participant_symbols": [symbol_of(item) for item in participants], + "atomic_coupling_signature": ( + None if center is None else carried(center, "electron-configuration"), + tuple(center_sites), + tuple(ligand_sites), + used_promotion, + ligand_has_p, + ), + "mobius": mobius, + "ucns_coupling_signature": ( + mobius["law"], + tuple(mobius["participant_axes"]), + tuple( + tuple(sorted(slot.items())) + for slot in mobius["attachment_slots"] + ), + tuple(mobius["t"]), + tuple(mobius["frame"]), + mobius["complete_restored"], + ), + "dimensional_geometry": geometry, + "declared_coupling_arities": [item["arity"] for item in geometry["couplings"]], + "charged_structure_readout": charged_structure_readout(geometry["structure"]), + "topology_structure_readout": topology_structure_readout(geometry["structure"]), + "oriented_instance_couplings": instance_couplings, + } + return MolecularConstruction(formula=formula, receipt=receipt, invariants=invariants) + + +def replay_molecule(construction: MolecularConstruction) -> PublicGonolReceipt: + return replay_public_gonol(construction.receipt) + + +def construct_declared_molecules() -> dict[str, MolecularConstruction]: + return {formula: construct_molecule(formula) for formula in MOLECULE_COMPOSITIONS} + + +def matched_information_control(invariants: Mapping[str, Any]) -> tuple[Any, ...]: + """Control: stoichiometric symbols only, no shells or wave identities.""" + + return ( + invariants["atom_count"], + invariants["center_symbol"], + tuple(invariants["ligand_symbols"]), + ) diff --git a/research/epac/epac_periodic.py b/research/epac/epac_periodic.py new file mode 100644 index 0000000..3e1c401 --- /dev/null +++ b/research/epac/epac_periodic.py @@ -0,0 +1,314 @@ +"""Element gonols closed as EPAC Public Gonols from nucleon then electron structure. + +Precursors: each proton and each neutron is a closed gonol. The nucleus is +their affixiation. Electrons then couple to that closed nucleus. Molecular +construction must not reopen nucleons or electrons. Letters are not axes. + +Usage guidance +-------------- +Each nucleon, nucleus, electron, shell, and element is an EPAC Public Gonol +on the UCNS carrier. This module does not use ``edcm.gonol``. + + from epac_periodic import construct_element_gonol, construct_periodic_table + + helium = construct_element_gonol("He") + nucleus = helium.gonol.participants[0] + assert [p.relation for p in nucleus.participants] == [ + "epac.atomic.proton", "epac.atomic.proton", + "epac.atomic.neutron", "epac.atomic.neutron", + ] +""" + +from __future__ import annotations + +from typing import Iterable + +from epac_atomic import AtomicRecord, ElectronState, iter_table +from epac_dimensional_arity import ( + geometry_from_declared_couplings, + oriented_instance_couplings, + space, +) +from epac_public_gonol import ( + ClosedPublicGonol, + PublicGonolReceipt, + construct_public_gonol, + replay_public_gonol, +) + +# Elementary charge in units of e. Nuclear Z is the proton-count sum. +PROTON_CHARGE = 1 +NEUTRON_CHARGE = 0 +ELECTRON_CHARGE = -1 +NUCLEUS_RELATION = "epac.atomic.nucleus" +PROTON_RELATION = "epac.atomic.proton" +NEUTRON_RELATION = "epac.atomic.neutron" + + +def _carrier_glyph(text: str) -> str | None: + if len(text) == 1: + return text + return None + + +def _electron_options(electron: ElectronState) -> tuple[tuple[str, str], ...]: + return ( + ("n", str(electron.n)), + ("l", str(electron.l)), + ("m_l", str(electron.m_l)), + ("m_s", str(electron.m_s)), + ("shell", electron.shell), + ("subshell", electron.subshell), + ("angular-id", electron.angular_id), + ("radial-nodes", str(electron.radial_nodes)), + ("z-eff", electron.z_eff), + ("e-rydberg", electron.e_rydberg), + ("valence", "true" if electron.valence else "false"), + ("paired", "true" if electron.paired else "false"), + ) + + +def _construct_electron( + electron: ElectronState, *, symbol: str, atom_occurrence: int +) -> ClosedPublicGonol: + return construct_public_gonol( + source_id=f"epac.electron:{symbol}#{atom_occurrence}:{electron.index}", + relation="epac.atomic.electron", + identity_glyph="e", + carried_options=_electron_options(electron), + occurrence=electron.index, + ).gonol + + +def _construct_shell( + n: int, + electrons: Iterable[ElectronState], + *, + symbol: str, + atom_occurrence: int, +) -> ClosedPublicGonol: + members = tuple( + _construct_electron(e, symbol=symbol, atom_occurrence=atom_occurrence) for e in electrons + ) + return construct_public_gonol( + source_id=f"epac.shell:{symbol}#{atom_occurrence}:n{n}", + relation="epac.atomic.shell", + identity_glyph=_carrier_glyph(str(n)), + participants=members, + occurrence=n, + carried_options=(("n", str(n)),), + ).gonol + + +def _proton_dimension_id(symbol: str, atom_occurrence: int, index: int) -> str: + return f"epac.proton:{symbol}#{atom_occurrence}:{index}" + + +def _neutron_dimension_id(symbol: str, atom_occurrence: int, index: int) -> str: + return f"epac.neutron:{symbol}#{atom_occurrence}:{index}" + + +def _construct_proton( + *, symbol: str, atom_occurrence: int, index: int +) -> ClosedPublicGonol: + return construct_public_gonol( + source_id=_proton_dimension_id(symbol, atom_occurrence, index), + relation=PROTON_RELATION, + occurrence=index, + carried_options=( + ("charge", str(PROTON_CHARGE)), + ("symbol", symbol), + ("kind", "proton"), + ), + ).gonol + + +def _construct_neutron( + *, symbol: str, atom_occurrence: int, index: int +) -> ClosedPublicGonol: + return construct_public_gonol( + source_id=_neutron_dimension_id(symbol, atom_occurrence, index), + relation=NEUTRON_RELATION, + occurrence=index, + carried_options=( + ("charge", str(NEUTRON_CHARGE)), + ("symbol", symbol), + ("kind", "neutron"), + ), + ).gonol + + +def _declared_nuclear_space(record: AtomicRecord, *, atom_occurrence: int): + """Neutrons couple to protons. Proton-proton and neutron-neutron are not inferred. + + Hydrogen-1 has one proton and no neutrons, so no nuclear 3. + """ + + if record.proton_count != record.Z: + raise ValueError(f"{record.symbol}: proton count must equal Z") + if record.neutron_count != record.A - record.Z: + raise ValueError(f"{record.symbol}: neutron count must equal A-Z") + proton_ids = [ + _proton_dimension_id(record.symbol, atom_occurrence, index) + for index in range(record.proton_count) + ] + neutron_ids = [ + _neutron_dimension_id(record.symbol, atom_occurrence, index) + for index in range(record.neutron_count) + ] + charges = { + **{proton_id: PROTON_CHARGE for proton_id in proton_ids}, + **{neutron_id: NEUTRON_CHARGE for neutron_id in neutron_ids}, + } + declarations = [ + [proton_id, neutron_id] for proton_id in proton_ids for neutron_id in neutron_ids + ] + declared = space([*proton_ids, *neutron_ids], declarations, charges=charges) + for proton_id in proton_ids: + if neutron_ids: + oriented_instance_couplings( + declared, hub_id=proton_id, instance_ids=neutron_ids + ) + return declared + + +def _construct_nucleus(record: AtomicRecord, *, atom_occurrence: int) -> ClosedPublicGonol: + protons = tuple( + _construct_proton(symbol=record.symbol, atom_occurrence=atom_occurrence, index=index) + for index in range(record.proton_count) + ) + neutrons = tuple( + _construct_neutron(symbol=record.symbol, atom_occurrence=atom_occurrence, index=index) + for index in range(record.neutron_count) + ) + if len(protons) != record.Z or len(neutrons) != record.neutron_count: + raise ValueError(f"{record.symbol}: nucleon gonols must match Z and A-Z") + geometry = geometry_from_declared_couplings( + _declared_nuclear_space(record, atom_occurrence=atom_occurrence) + ) + couplings = geometry["couplings"] + structure = geometry["structure"] if couplings else None + return construct_public_gonol( + source_id=f"epac.nucleus:{record.symbol}#{atom_occurrence}", + relation=NUCLEUS_RELATION, + participants=(*protons, *neutrons), + carried_options=( + ("Z", str(record.Z)), + ("A", str(record.A)), + ("protons", str(record.proton_count)), + ("neutrons", str(record.neutron_count)), + ("symbol", record.symbol), + ), + occurrence=0, + couplings=couplings, + structure=structure, + ).gonol + + +def _nucleus_dimension_id(symbol: str, atom_occurrence: int) -> str: + return f"epac.nucleus:{symbol}#{atom_occurrence}" + + +def _electron_dimension_id(symbol: str, atom_occurrence: int, index: int) -> str: + return f"epac.electron:{symbol}#{atom_occurrence}:{index}" + + +def _declared_atomic_space(record: AtomicRecord, *, atom_occurrence: int): + """One ``(nucleus, electron_i)`` coupling for every electron instance. + + Closed shells still participate as instances. Letters do not. + """ + + hub = _nucleus_dimension_id(record.symbol, atom_occurrence) + electron_ids = [ + _electron_dimension_id(record.symbol, atom_occurrence, electron.index) + for electron in record.electrons + ] + charges = {hub: record.Z, **{electron_id: ELECTRON_CHARGE for electron_id in electron_ids}} + declared = space( + [hub, *electron_ids], + [[hub, electron_id] for electron_id in electron_ids], + charges=charges, + ) + oriented_instance_couplings(declared, hub_id=hub, instance_ids=electron_ids) + return declared + + +def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: + """Close one element Public Gonol whose participants are nucleus + electron shells.""" + + record = None + for item in iter_table(): + if item.symbol == symbol: + record = item + break + if record is None: + raise ValueError(f"no atomic record for symbol {symbol!r}") + shells: list[ClosedPublicGonol] = [] + by_n: dict[int, list[ElectronState]] = {} + for electron in record.electrons: + by_n.setdefault(electron.n, []).append(electron) + for n in sorted(by_n): + shells.append(_construct_shell(n, by_n[n], symbol=symbol, atom_occurrence=occurrence)) + nucleus = _construct_nucleus(record, atom_occurrence=occurrence) + unpaired = record.unpaired_valence + promoted = record.promoted_unpaired_valence + carried = ( + ("symbol", record.symbol), + ("Z", str(record.Z)), + ("period", str(record.period)), + ("group", str(record.group)), + ("A", str(record.A)), + ("electron-configuration", record.configuration), + ("valence-n", str(record.valence_n)), + ("valence-electrons", str(record.valence_electrons)), + ("unpaired-valence-count", str(len(unpaired))), + ("unpaired-valence-lm", ",".join(f"{e.l}:{e.m_l}" for e in unpaired) or "none"), + ("promoted-unpaired-count", str(len(promoted))), + ("promoted-unpaired-lm", ",".join(f"{e.l}:{e.m_l}" for e in promoted) or "none"), + ("valence-angular-ids", ",".join(e.angular_id for e in record.electrons if e.valence)), + ) + geometry = geometry_from_declared_couplings( + _declared_atomic_space(record, atom_occurrence=occurrence) + ) + return construct_public_gonol( + source_id=f"epac.periodic:{symbol}#{occurrence}", + relation="epac.atomic.element", + identity_glyph=_carrier_glyph(symbol), + participants=(nucleus, *shells), + carried_options=carried, + occurrence=occurrence, + couplings=geometry["couplings"], + structure=geometry["structure"], + ) + + +def construct_periodic_table() -> dict[str, PublicGonolReceipt]: + return {record.symbol: construct_element_gonol(record.symbol) for record in iter_table()} + + +def replay_element_gonol(receipt: PublicGonolReceipt) -> PublicGonolReceipt: + return replay_public_gonol(receipt) + + +def atomic_of(symbol: str) -> AtomicRecord: + for record in iter_table(): + if record.symbol == symbol: + return record + raise ValueError(symbol) + + +def symbol_of(gonol: ClosedPublicGonol) -> str: + for key, value in gonol.carried_options: + if key == "symbol": + return value + if gonol.identity_glyph: + return gonol.identity_glyph + raise KeyError("symbol") + + +def carried(gonol: ClosedPublicGonol, key: str) -> str: + for item_key, value in gonol.carried_options: + if item_key == key: + return value + raise KeyError(key) diff --git a/research/epac/epac_public_gonol.py b/research/epac/epac_public_gonol.py new file mode 100644 index 0000000..1eb8176 --- /dev/null +++ b/research/epac/epac_public_gonol.py @@ -0,0 +1,450 @@ +"""EPAC Public Gonol constructor. + +EPAC closes gonols on the UCNS Public Gonol carrier. This is not the EDCM +text-domain constructor. Glyphs are identity coordinates only; Public Gonol +function operations and a Möbius coupling law remain hmmm. + +Charge state is already in the math: per-slot nuclear Z with Möbius ε at t=0 +from ``(t, ε) ~ (t+n, (-1)^n ε)``. Oriented couplings plus those charge +states plus degree are the three-dimensional structure. Representing that 3 +takes a 4-component quaternion; the extra coordinate is the scalar ε. No +cartesian embedding, ternary coupling, or Hamilton-product coupling is inferred. + +Usage guidance +-------------- + from epac_public_gonol import construct_public_gonol, replay_public_gonol + + oxygen = construct_public_gonol( + source_id="epac.atomic.element:O#0", + relation="epac.atomic.element", + identity_glyph="O", + carried_options=(("Z", "8"), ("symbol", "O")), + ) + assert oxygen.constructor_id == "epac.public_gonol" + assert replay_public_gonol(oxygen).receipt_digest == oxygen.receipt_digest +""" + +# === MODULE_BUILD === +# id: epac_public_gonol +# module_name: epac_public_gonol +# module_kind: experiment +# summary: EPAC candidate constructor that closes gonols on the UCNS Public Gonol carrier with oriented couplings and arity charge states; not the EDCM text-domain constructor +# owner: The Interdependency +# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, PINNED_PUBLIC_GONOL_SHA256, ClosedPublicGonol, PublicGonolReceipt, PublicGonolConstructionError, construct_public_gonol, replay_public_gonol, canonical_receipt_bytes +# internal_surface: _require_text, _identity_position, _geometry, _participant_payload, _atomic_payload, _receipt_payload, _digest +# auth_boundary: EPAC owns particle/energy gonol closure; UCNS owns Public Gonol carrier identity and native Möbius ε; EDCM text-domain constructor is not used; METAPAT affixiation is consumed, not redefined +# storage_boundary: none; receipts remain caller-owned in-memory objects +# network_boundary: none +# user_data_boundary: caller-supplied source_id, relation, participants, and carried options remain in memory +# admin_only: false +# tests: tests.test_epac_public_gonol, tests.test_periodic_element_gonols, tests.test_molecular_affixiation +# rollout: explicit EPAC candidate constructor; no canon selection, no EDCM scale option sets, no invented position operation +# rollback: remove this module; do not fall back to edcm.gonol for EPAC construction +# requires: ucns_public_gonol_geometry, ucns_native_mobius_geometry +# since: 2026-08-22 +# unresolved: exact UCNS geometric operation of Public Gonol function positions; UCNS Möbius-carrier affixiation/coupling law; two-letter element symbols have no single carrier glyph +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: epac_public_gonol_is_not_edcm_gonol +# given: an EPAC gonol is constructed +# then: constructor_id is epac.public_gonol and edcm.gonol is not imported or invoked +# class: doctrine +# since: 2026-08-22 +# +# id: epac_public_gonol_binds_ucns_carrier_identity +# given: identity_glyph is an admitted Public Gonol glyph +# then: the closed gonol carries the exact UCNS index/glyph pair and the pinned carrier digest +# class: construction +# since: 2026-08-22 +# +# id: epac_public_gonol_replays_byte_identical +# given: a PublicGonolReceipt +# then: replay_public_gonol reproduces the same receipt_digest +# class: correctness +# since: 2026-08-22 +# +# id: charged_oriented_couplings_are_the_structure +# given: declared oriented couplings with per-slot charges +# then: receipt.structure is the combination of those couplings, arity charge states, and degree; no (x,y,z) coupling is inferred +# class: construction +# since: 2026-08-22 +# === END CONTRACTS === + +from __future__ import annotations + +from collections.abc import Mapping as MappingABC +from collections.abc import Sequence as SequenceABC +from dataclasses import dataclass +from hashlib import sha256 +import json +from types import MappingProxyType +from typing import Any, Mapping, Sequence + +from ucns import ( + PUBLIC_GONOL_SHA256, + native_mobius_state, + public_gonol_function, + public_gonol_sha256, +) + + +CONSTRUCTOR_ID = "epac.public_gonol" +CONSTRUCTOR_VERSION = "v1" +PINNED_PUBLIC_GONOL_SHA256 = PUBLIC_GONOL_SHA256 +STANDING = "implemented-candidate" +SELECTION_EFFECT = "none" + +NONCLAIMS: tuple[str, ...] = ( + "not selected canon", + "not EDCM text-domain gonol construction", + "not a UCNS geometric function operation", + "not a UCNS Möbius coupling law", + "not METAPAT canon promotion", + "not imported chemistry shape names", +) + +HMMM: tuple[str, ...] = ( + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Möbius-carrier affixiation/coupling law", + "two-letter element symbols have no single Public Gonol glyph", +) + + +class PublicGonolConstructionError(RuntimeError): + """Fail-closed EPAC Public Gonol constructor error.""" + + +@dataclass(frozen=True, slots=True) +class ClosedPublicGonol: + """One closed EPAC gonol. Atomic at any later declared participation.""" + + source_id: str + occurrence: int + relation: str + identity_glyph: str | None + carrier_index: int | None + participants: tuple["ClosedPublicGonol", ...] + carried_options: tuple[tuple[str, str], ...] + couplings: tuple[Mapping[str, Any], ...] + structure: Mapping[str, Any] | None + atomic_id: str + receipt_digest: str + geometry_digest: str + + +@dataclass(frozen=True, slots=True) +class PublicGonolReceipt: + """Deterministic construction receipt for one EPAC Public Gonol.""" + + constructor_id: str + constructor_version: str + standing: str + selection_effect: str + source_id: str + gonol: ClosedPublicGonol + receipt_digest: str + structure: Mapping[str, Any] | None + nonclaims: tuple[str, ...] + hmmm: tuple[str, ...] + + +def _require_text(value: str, *, field: str) -> str: + if not isinstance(value, str) or not value or value.isspace(): + raise PublicGonolConstructionError(f"{field} must be exact non-empty text") + return value + + +def _identity_position(identity_glyph: str | None) -> tuple[str | None, int | None]: + if identity_glyph is None: + return (None, None) + if not isinstance(identity_glyph, str) or len(identity_glyph) != 1: + raise PublicGonolConstructionError( + "identity_glyph must be one admitted Public Gonol scalar or None" + ) + try: + position = public_gonol_function(identity_glyph) + except (TypeError, ValueError) as exc: + raise PublicGonolConstructionError(str(exc)) from exc + return (position.glyph, position.index) + + +def _geometry(identity_glyph: str | None, carrier_index: int | None) -> dict[str, Any]: + digest = public_gonol_sha256() + if digest != PINNED_PUBLIC_GONOL_SHA256: + raise PublicGonolConstructionError( + "UCNS Public Gonol digest mismatch: " + f"constructor pins {PINNED_PUBLIC_GONOL_SHA256}, computed {digest}" + ) + origin = native_mobius_state(0) + identity: dict[str, Any] | None = None + if identity_glyph is not None and carrier_index is not None: + identity = {"index": carrier_index, "glyph": identity_glyph} + return { + "state": "bound", + "authority": "ucns.public_gonol", + "authority_binding": "explicit", + "carrier_digest": digest, + "identity_position": identity, + "mobius_epsilon_t0": origin.frame.sign, + "position_operation": "hmmm", + } + + +def _freeze_json(value: Any) -> Any: + if value is None or isinstance(value, (str, int, float, bool)): + return value + if isinstance(value, MappingABC): + return MappingProxyType({str(key): _freeze_json(item) for key, item in value.items()}) + if isinstance(value, SequenceABC) and not isinstance(value, (str, bytes)): + return tuple(_freeze_json(item) for item in value) + raise PublicGonolConstructionError(f"value is not JSON-stable: {type(value)!r}") + + +def _json_ready(value: Any) -> Any: + if isinstance(value, MappingABC): + return {str(key): _json_ready(item) for key, item in value.items()} + if isinstance(value, SequenceABC) and not isinstance(value, (str, bytes)): + return [_json_ready(item) for item in value] + return value + + +def _tuple_tree(value: Any) -> Any: + if isinstance(value, MappingABC): + return tuple(sorted((str(key), _tuple_tree(item)) for key, item in value.items())) + if isinstance(value, SequenceABC) and not isinstance(value, (str, bytes)): + return tuple(_tuple_tree(item) for item in value) + return value + + +def _coupling_signature(item: Mapping[str, Any]) -> tuple[Any, int, Any]: + declared = item.get("declared_ids", item.get("coupling")) + charge_state = item.get("charge_state") + if charge_state is None: + charge_state = (item.get("slot_charges"), item.get("mobius_epsilon_t0")) + return (_tuple_tree(declared), int(item.get("arity", -1)), _tuple_tree(charge_state)) + + +def _structure_part_signature(item: Mapping[str, Any]) -> tuple[Any, int, Any]: + return ( + _tuple_tree(item.get("coupling")), + int(item.get("arity", -1)), + _tuple_tree(item.get("charge_state")), + ) + + +def _validate_structure_matches_couplings( + couplings: Sequence[Mapping[str, Any]], + structure: Mapping[str, Any] | None, +) -> None: + if not couplings and structure is None: + return + if not couplings or structure is None: + raise PublicGonolConstructionError( + "couplings and structure must be supplied together" + ) + parts = structure.get("parts") + if not isinstance(parts, SequenceABC) or isinstance(parts, (str, bytes)): + raise PublicGonolConstructionError("structure parts must be a sequence") + expected = tuple(sorted((_coupling_signature(item) for item in couplings), key=repr)) + actual = tuple(sorted((_structure_part_signature(item) for item in parts), key=repr)) + if expected != actual: + raise PublicGonolConstructionError( + "structure must match the supplied declared couplings before closure" + ) + + +def _participant_payload(item: ClosedPublicGonol) -> dict[str, Any]: + return { + "source_id": item.source_id, + "occurrence": item.occurrence, + "relation": item.relation, + "identity_glyph": item.identity_glyph, + "carrier_index": item.carrier_index, + "atomic_id": item.atomic_id, + "receipt_digest": item.receipt_digest, + "geometry_digest": item.geometry_digest, + "carried_options": [list(pair) for pair in item.carried_options], + "couplings": _freeze_json(item.couplings), + "structure": _freeze_json(item.structure), + "participants": [_participant_payload(child) for child in item.participants], + } + + +def _atomic_payload( + *, + source_id: str, + occurrence: int, + relation: str, + identity_glyph: str | None, + carrier_index: int | None, + participants: tuple[ClosedPublicGonol, ...], + carried_options: tuple[tuple[str, str], ...], + couplings: tuple[Mapping[str, Any], ...], + structure: Mapping[str, Any] | None, +) -> dict[str, Any]: + return { + "constructor_id": CONSTRUCTOR_ID, + "constructor_version": CONSTRUCTOR_VERSION, + "standing": STANDING, + "selection_effect": SELECTION_EFFECT, + "source_id": source_id, + "occurrence": occurrence, + "relation": relation, + "identity_glyph": identity_glyph, + "carrier_index": carrier_index, + "participants": [_participant_payload(item) for item in participants], + "carried_options": [list(pair) for pair in carried_options], + "couplings": _freeze_json(couplings), + "structure": _freeze_json(structure), + "closure_invariant": "once closed, a gonol is atomic at any later participation", + } + + +def _receipt_payload( + *, + source_id: str, + gonol_payload: Mapping[str, Any], + geometry: Mapping[str, Any], + atomic_id: str, + geometry_digest: str, +) -> dict[str, Any]: + return { + "constructor_id": CONSTRUCTOR_ID, + "constructor_version": CONSTRUCTOR_VERSION, + "standing": STANDING, + "selection_effect": SELECTION_EFFECT, + "source_id": source_id, + "gonol": gonol_payload, + "atomic_id": atomic_id, + "geometry": _freeze_json(geometry), + "geometry_digest": geometry_digest, + "nonclaims": list(NONCLAIMS), + "hmmm": list(HMMM), + } + + +def canonical_receipt_bytes(payload: Mapping[str, Any]) -> bytes: + return json.dumps( + _json_ready(payload), + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + + +def _digest(payload: Mapping[str, Any]) -> str: + return sha256(canonical_receipt_bytes(payload)).hexdigest() + + +def construct_public_gonol( + *, + source_id: str, + relation: str, + participants: Sequence[ClosedPublicGonol] = (), + identity_glyph: str | None = None, + occurrence: int = 0, + carried_options: Sequence[tuple[str, str]] = (), + couplings: Sequence[Mapping[str, Any]] = (), + structure: Mapping[str, Any] | None = None, +) -> PublicGonolReceipt: + """Close one EPAC gonol on the UCNS Public Gonol carrier.""" + + source_id = _require_text(source_id, field="source_id") + relation = _require_text(relation, field="relation") + if isinstance(occurrence, bool) or not isinstance(occurrence, int) or occurrence < 0: + raise PublicGonolConstructionError("occurrence must be a non-negative int") + closed_participants = tuple(participants) + for item in closed_participants: + if not isinstance(item, ClosedPublicGonol): + raise PublicGonolConstructionError("participants must already be closed EPAC public gonols") + options = tuple( + ( + _require_text(key, field="carried option key"), + _require_text(value, field="carried option value"), + ) + for key, value in carried_options + ) + frozen_couplings = tuple(_freeze_json(item) for item in couplings) + frozen_structure = None if structure is None else _freeze_json(structure) + _validate_structure_matches_couplings(frozen_couplings, frozen_structure) + glyph, index = _identity_position(identity_glyph) + geometry = _geometry(glyph, index) + gonol_payload = _atomic_payload( + source_id=source_id, + occurrence=occurrence, + relation=relation, + identity_glyph=glyph, + carrier_index=index, + participants=closed_participants, + carried_options=options, + couplings=frozen_couplings, + structure=frozen_structure, + ) + atomic_id = _digest({"atomic": gonol_payload}) + geometry_digest = _digest({"geometry": geometry}) + receipt_payload = _receipt_payload( + source_id=source_id, + gonol_payload=gonol_payload, + geometry=geometry, + atomic_id=atomic_id, + geometry_digest=geometry_digest, + ) + receipt_digest = _digest(receipt_payload) + gonol = ClosedPublicGonol( + source_id=source_id, + occurrence=occurrence, + relation=relation, + identity_glyph=glyph, + carrier_index=index, + participants=closed_participants, + carried_options=options, + couplings=frozen_couplings, + structure=frozen_structure, + atomic_id=atomic_id, + receipt_digest=receipt_digest, + geometry_digest=geometry_digest, + ) + return PublicGonolReceipt( + constructor_id=CONSTRUCTOR_ID, + constructor_version=CONSTRUCTOR_VERSION, + standing=STANDING, + selection_effect=SELECTION_EFFECT, + source_id=source_id, + gonol=gonol, + receipt_digest=receipt_digest, + structure=frozen_structure, + nonclaims=NONCLAIMS, + hmmm=HMMM, + ) + + +def replay_public_gonol(receipt: PublicGonolReceipt) -> PublicGonolReceipt: + """Replay one receipt from its closed gonol. Reproduces construction identity.""" + + gonol = receipt.gonol + return construct_public_gonol( + source_id=gonol.source_id, + relation=gonol.relation, + participants=gonol.participants, + identity_glyph=gonol.identity_glyph, + occurrence=gonol.occurrence, + carried_options=gonol.carried_options, + couplings=gonol.couplings, + structure=gonol.structure, + ) + + +__all__ = [ + "CONSTRUCTOR_ID", + "CONSTRUCTOR_VERSION", + "ClosedPublicGonol", + "HMMM", + "NONCLAIMS", + "PINNED_PUBLIC_GONOL_SHA256", + "PublicGonolConstructionError", + "PublicGonolReceipt", + "canonical_receipt_bytes", + "construct_public_gonol", + "replay_public_gonol", +] diff --git a/research/epac/subatomic/element_affixiation_candidate.py b/research/epac/subatomic/element_affixiation_candidate.py new file mode 100644 index 0000000..8789fb6 --- /dev/null +++ b/research/epac/subatomic/element_affixiation_candidate.py @@ -0,0 +1,268 @@ +"""Identity-only subatomic element affixiation candidate. + +This module implements the provisional baseline from +``subatomic-affixiation-baseline.md``: hydrogen, helium, lithium, and carbon +element-gonol candidates over the established UCNS carrier identity surfaces +(Public Gonol 157) and the native Möbius root-loop quotient, using the Möbius +turn index as the time-agnostic ordered parameter. + +It consumes exactly two UCNS public surfaces: + +- ``ucns.public_gonol_function`` for carrier identity positions; +- ``ucns.native_mobius_state`` for the established Möbius framing. + +No Public Gonol position operation is defined, inferred, or asserted here. +Status: CROSS-DOMAIN-HYPOTHESIS / provisional. Not org canon. + +Usage guidance: + + PYTHONPATH=/src python3 - <<'PY' + from element_affixiation_candidate import affixiate_element, replay_element + + he = affixiate_element("He") + print(he.receipt) + ok, replay_receipt = replay_element("He") + print("replay byte-identical:", ok and replay_receipt == he.receipt) + PY +""" + +# === MODULE_BUILD === +# id: epac_subatomic_element_affixiation_candidate +# module_name: element_affixiation_candidate +# module_kind: experiment +# summary: identity-only H/He/Li/C element-gonol candidates over established UCNS carrier identity and native Möbius framing; no position operation invented +# owner: The Interdependency +# public_surface: ISOTOPE_DEFAULTS, CONSTRUCTION_IDS, ElementCandidate, affixiate_element, replay_element, element_receipt +# internal_surface: _canonical_record, _t_states +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: subatomic.test_element_affixiation_candidate +# rollout: local candidate module under stack/research/epac/subatomic/ +# rollback: remove module, tests, and generated receipts +# requires: ucns_public_gonol_geometry, ucns_native_mobius_geometry +# since: 2026-08-22 +# unresolved: Public Gonol position operations; harmonic notation; isotope defaults are instance-resolved; epac canonical repository absent +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: candidate_uses_only_established_ucns_surfaces +# given: the candidate module is imported and executed +# then: only ucns.public_gonol_function and ucns.native_mobius_state are consumed; no position operation is defined, inferred, or called +# class: safety +# +# id: element_identity_positions_exact +# given: an element symbol with default isotope (Z, A) +# then: proton positions are exactly 1..Z and neutron positions are exactly Z+1..A on the 157-position carrier, as identity coordinates only +# class: correctness +# +# id: mobius_parameter_sequence_exact +# given: the Möbius turn index t in {0, 1, 2} is traversed +# then: visible phase is unchanged, the local frame sequence is POSITIVE -> REVERSED -> POSITIVE, and complete_key differs only at t=1 +# class: correctness +# +# id: receipt_deterministic_and_replayable +# given: the same element and the same pinned source identities +# then: the receipt is byte-identical across independent constructions +# class: correctness +# +# id: no_physics_or_canon_claim +# given: any constructed candidate +# then: status remains CROSS-DOMAIN-HYPOTHESIS and no empirical validity, theorem status, measurement validity, or canon promotion is claimed +# class: doctrine +# === END CONTRACTS === + +from __future__ import annotations + +from dataclasses import dataclass +from fractions import Fraction +import hashlib +import json + +from ucns import native_mobius_state, public_gonol_function + +SOURCE_COMMITS = { + "metapat": "34d954aa1e2092e615b03a180500f6b6977f501e", + "ucns": "1975fe70cf4e0826a8020c2da3047569e277af64", +} + +CONSTRUCTION_IDS = { + "relation": "metapat.affixiation_harmonics.affixiation", + "ordered_parameter": "ucns.native-mobius-turn-index", + "closure_scale": "epac.subatomic.atomic", + "status": "CROSS-DOMAIN-HYPOTHESIS", +} + +# Default isotope instances are instance-resolved, not canonical admission law. +# Extended to Z=1..26 (through iron) for the subatomic gonol program. +ISOTOPE_DEFAULTS = { + "H": (1, 1), "He": (2, 4), "Li": (3, 7), "Be": (4, 9), + "B": (5, 11), "C": (6, 12), "N": (7, 14), "O": (8, 16), + "F": (9, 19), "Ne": (10, 20), "Na": (11, 23), "Mg": (12, 24), + "Al": (13, 27), "Si": (14, 28), "P": (15, 31), "S": (16, 32), + "Cl": (17, 35), "Ar": (18, 40), "K": (19, 39), "Ca": (20, 40), + "Sc": (21, 45), "Ti": (22, 48), "V": (23, 51), "Cr": (24, 52), + "Mn": (25, 55), "Fe": (26, 56), +} + + +@dataclass(frozen=True, slots=True) +class ElementCandidate: + """One closed element-gonol candidate record with deterministic receipt.""" + + element_id: str + symbol: str + Z: int + A: int + proton_positions: tuple[int, ...] + proton_glyphs: tuple[str, ...] + neutron_positions: tuple[int, ...] + neutron_glyphs: tuple[str, ...] + t_states: tuple[dict, ...] + relation_id: str + ordered_parameter_id: str + closure_scale: str + source_commits: dict + status: str + receipt: str + + +def _t_states() -> tuple[dict, ...]: + """Traverse the Möbius turn index t in {0, 1, 2}. + + Uses only the established native Möbius root-loop quotient. Time is not + inserted: t is a declared ordered parameter, not physical time. + """ + states = [] + for t in (0, 1, 2): + state = native_mobius_state(Fraction(t)) + states.append( + { + "t": t, + "visible_key": [state.visible_key[0], str(state.visible_key[1])], + "complete_key": [ + state.complete_key[0], + str(state.complete_key[1]), + state.complete_key[2].value, + ], + "frame": state.frame.value, + } + ) + return tuple(states) + + +def _canonical_record( + element_id: str, + symbol: str, + Z: int, + A: int, + proton_positions: tuple[int, ...], + proton_glyphs: tuple[str, ...], + neutron_positions: tuple[int, ...], + neutron_glyphs: tuple[str, ...], +) -> dict: + return { + "element_id": element_id, + "symbol": symbol, + "Z": Z, + "A": A, + "proton_positions": list(proton_positions), + "proton_glyphs": list(proton_glyphs), + "neutron_positions": list(neutron_positions), + "neutron_glyphs": list(neutron_glyphs), + "relation_id": CONSTRUCTION_IDS["relation"], + "ordered_parameter_id": CONSTRUCTION_IDS["ordered_parameter"], + "t_states": list(_t_states()), + "closure_scale": CONSTRUCTION_IDS["closure_scale"], + "source_commits": SOURCE_COMMITS, + "status": CONSTRUCTION_IDS["status"], + } + + +def element_receipt(record: dict) -> str: + """SHA-256 over canonical JSON of the construction record.""" + payload = json.dumps(record, sort_keys=True, separators=(",", ":")) + return hashlib.sha256(payload.encode("utf-8")).hexdigest() + + +def affixiate_element(symbol: str) -> ElementCandidate: + """Construct one element-gonol candidate from its default isotope instance. + + Raises ``ValueError`` for symbols outside the declared isotope defaults. + """ + if symbol not in ISOTOPE_DEFAULTS: + raise ValueError( + f"element {symbol!r} has no declared isotope default; " + f"declared: {sorted(ISOTOPE_DEFAULTS)}" + ) + Z, A = ISOTOPE_DEFAULTS[symbol] + proton_positions = tuple(range(1, Z + 1)) + neutron_positions = tuple(range(Z + 1, A + 1)) + + # Identity coordinates only. public_gonol_function resolves the exact + # carrier identity position; no operation is requested or inferred. + proton_glyphs = tuple(public_gonol_function(i).glyph for i in proton_positions) + neutron_glyphs = tuple(public_gonol_function(i).glyph for i in neutron_positions) + + record = _canonical_record( + element_id=f"epac.subatomic_affixiation.{symbol.lower()}", + symbol=symbol, + Z=Z, + A=A, + proton_positions=proton_positions, + proton_glyphs=proton_glyphs, + neutron_positions=neutron_positions, + neutron_glyphs=neutron_glyphs, + ) + receipt = element_receipt(record) + return ElementCandidate( + element_id=record["element_id"], + symbol=symbol, + Z=Z, + A=A, + proton_positions=proton_positions, + proton_glyphs=proton_glyphs, + neutron_positions=neutron_positions, + neutron_glyphs=neutron_glyphs, + t_states=record["t_states"], + relation_id=record["relation_id"], + ordered_parameter_id=record["ordered_parameter_id"], + closure_scale=record["closure_scale"], + source_commits=SOURCE_COMMITS, + status=record["status"], + receipt=receipt, + ) + + +def replay_element(symbol: str) -> tuple[bool, str]: + """Independently reconstruct and compare the receipt. + + Returns ``(matches, receipt)``. Replay establishes reproducibility of the + declared construction only — not geometry, physics, or measurement. + """ + candidate = affixiate_element(symbol) + record = _canonical_record( + element_id=candidate.element_id, + symbol=candidate.symbol, + Z=candidate.Z, + A=candidate.A, + proton_positions=candidate.proton_positions, + proton_glyphs=candidate.proton_glyphs, + neutron_positions=candidate.neutron_positions, + neutron_glyphs=candidate.neutron_glyphs, + ) + replay_receipt = element_receipt(record) + return (replay_receipt == candidate.receipt, replay_receipt) + + +__all__ = [ + "CONSTRUCTION_IDS", + "ElementCandidate", + "ISOTOPE_DEFAULTS", + "SOURCE_COMMITS", + "affixiate_element", + "element_receipt", + "replay_element", +] diff --git a/research/epac/subatomic/extended_atomic.py b/research/epac/subatomic/extended_atomic.py new file mode 100644 index 0000000..c5db19e --- /dev/null +++ b/research/epac/subatomic/extended_atomic.py @@ -0,0 +1,236 @@ +"""Extended atomic quantum layer Z=1..26 for subatomic gonols. + +Delegates Z<=18 to ``epac_atomic`` (byte-identical electron records, so +existing H/He/Li/C receipts do not move). Adds Z=19..26 from declared +ground-state configurations with a standard Aufbau extension through 4s/3d and +a Slater-screening extension for d electrons. + +Candidate rules declared here (consistent with the sibling ``epac_atomic``): + +- valence electrons are those with ``n == max occupied n``; +- angular identities are hydrogenic ``Y_l{l}_m{m_l}`` labels; +- Slater screening: same-shell 0.35 (same-group), n-1 shell 0.85, deeper 1.00; + for d electrons (l=2) all inner shells count 1.00. + +Status: application-layer candidate data. Not physics canon. + +Usage guidance: + + from extended_atomic import atomic_record, iter_table + + iron = atomic_record(26) + print(iron.symbol, iron.configuration) +""" + +# === MODULE_BUILD === +# id: epac_subatomic_extended_atomic +# module_name: extended_atomic +# module_kind: schema +# summary: atomic quantum-layer records Z=1..26 for subatomic gonols; Z<=18 delegates to epac_atomic, Z=19..26 from declared ground-state configurations with Aufbau/Slater extension +# owner: The Interdependency +# public_surface: EXTENDED_SYMBOLS, SYMBOL_TO_Z, atomic_record, iter_table +# internal_surface: _config_occupancy, _fill_from_config, _slater_zeff_extended +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: subatomic.test_extended_atomic +# rollout: local candidate module under stack/research/epac/subatomic/ +# rollback: remove module; subatomic_gonol returns to Z<=18 epac_atomic delegation +# requires: epac_atomic +# since: 2026-08-22 +# unresolved: configurations beyond Z=26; full f-block Aufbau; Slater rules are candidate extensions, not exact physics +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: extended_atomic_preserves_z_le_18 +# given: atomic_record(Z) for 1 <= Z <= 18 +# then: the record is byte-identical to epac_atomic.atomic_record(Z) +# class: correctness +# +# id: extended_atomic_uses_declared_configurations +# given: atomic_record(Z) for 19 <= Z <= 26 +# then: electron occupancy matches the declared ground-state configuration, including the Cr 4s1.3d5 exception +# class: correctness +# +# id: extended_atomic_stays_candidate +# given: any extended record +# then: values are candidate application-layer data, not physics validation +# class: doctrine +# === END CONTRACTS === + +from __future__ import annotations + +from epac_atomic import ( + AtomicRecord, + ElectronState, + atomic_record as base_atomic_record, +) + +EXTENDED_SYMBOLS: tuple[str, ...] = ( + "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", + "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", + "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", +) +SYMBOL_TO_Z: dict[str, int] = {symbol: index + 1 for index, symbol in enumerate(EXTENDED_SYMBOLS)} + +ISOTOPE_DEFAULTS_19_26: dict[int, int] = { + 19: 39, 20: 40, 21: 45, 22: 48, 23: 51, 24: 52, 25: 55, 26: 56, +} + +PERIOD_GROUP_19_26: dict[int, tuple[int, int]] = { + 19: (4, 1), 20: (4, 2), 21: (4, 3), 22: (4, 4), + 23: (4, 5), 24: (4, 6), 25: (4, 7), 26: (4, 8), +} + +# Declared ground-state configurations (standard Aufbau with the Cr exception). +CONFIGURATIONS_19_26: dict[int, str] = { + 19: "1s2.2s2.2p6.3s2.3p6.4s1", + 20: "1s2.2s2.2p6.3s2.3p6.4s2", + 21: "1s2.2s2.2p6.3s2.3p6.4s2.3d1", + 22: "1s2.2s2.2p6.3s2.3p6.4s2.3d2", + 23: "1s2.2s2.2p6.3s2.3p6.4s2.3d3", + 24: "1s2.2s2.2p6.3s2.3p6.4s1.3d5", + 25: "1s2.2s2.2p6.3s2.3p6.4s2.3d5", + 26: "1s2.2s2.2p6.3s2.3p6.4s2.3d6", +} + +_SUBSHELL_NAME = "spdf" + + +def _ml_down(l: int) -> tuple[int, ...]: + return tuple(range(l, -l - 1, -1)) + + +def _config_occupancy(config: str) -> list[tuple[int, int, int]]: + """Parse ``1s2.2s2...`` into ordered (n, l, count) entries.""" + entries: list[tuple[int, int, int]] = [] + for part in config.split("."): + part = part.strip() + n = int(part[0]) + l = _SUBSHELL_NAME.index(part[1]) + count = int(part[2:]) + entries.append((n, l, count)) + return entries + + +def _slater_zeff_extended( + Z: int, n: int, l: int, occupied: tuple[tuple[int, int], ...] +) -> float: + """Slater screening, extended for 4s/3d while matching epac_atomic for l<=1.""" + others = list(occupied) + others.remove((n, l)) + sigma = 0.0 + same_group = 0 + for on, ol in others: + if n == 1 and l == 0: + if on == 1 and ol == 0: + sigma += 0.30 + continue + if l == 2: + # d electron: same subshell 0.35, all inner shells 1.00. + if on == n and ol == l: + same_group += 1 + elif on < n: + sigma += 1.00 + continue + if on == n and ((l in {0, 1} and ol in {0, 1}) or ol == l): + same_group += 1 + elif on == n - 1: + sigma += 0.85 + elif on <= n - 2: + sigma += 1.00 + sigma += 0.35 * same_group + return round(Z - sigma, 3) + + +def _fill_from_config(Z: int, config: str) -> tuple[ElectronState, ...]: + occupancy = _config_occupancy(config) + raw: list[tuple[int, int, int, int]] = [] + occupied_pairs: list[tuple[int, int]] = [] + for n, l, count in occupancy: + slots = [(m_l, 1) for m_l in _ml_down(l)] + [(m_l, -1) for m_l in _ml_down(l)] + for m_l, m_s in slots[:count]: + raw.append((n, l, m_l, m_s)) + occupied_pairs.append((n, l)) + valence_n = max(n for n, _l, _ml, _ms in raw) + occupied = tuple(occupied_pairs) + occupancy_counts: dict[tuple[int, int, int], int] = {} + for n, l, m_l, _m_s in raw: + key = (n, l, m_l) + occupancy_counts[key] = occupancy_counts.get(key, 0) + 1 + electrons: list[ElectronState] = [] + for index, (n, l, m_l, m_s) in enumerate(raw): + z_eff = _slater_zeff_extended(Z, n, l, occupied) + energy = round(-(z_eff**2) / (n**2), 6) + electrons.append( + ElectronState( + index=index, + n=n, + l=l, + m_l=m_l, + m_s=m_s, + shell=f"n{n}", + subshell=f"{n}{_SUBSHELL_NAME[l]}", + angular_id=f"Y_l{l}_m{m_l}", + radial_nodes=n - l - 1, + z_eff=str(z_eff), + e_rydberg=str(energy), + valence=(n == valence_n), + paired=occupancy_counts[(n, l, m_l)] == 2, + ) + ) + return tuple(electrons) + + +def _configuration_string(electrons: tuple[ElectronState, ...]) -> str: + counts: dict[str, int] = {} + order: list[str] = [] + for electron in electrons: + name = electron.subshell + if name not in counts: + order.append(name) + counts[name] = 0 + counts[name] += 1 + return ".".join(f"{name}{counts[name]}" for name in order) + + +def atomic_record(Z: int) -> AtomicRecord: + if not 1 <= Z <= 26: + raise ValueError("extended atomic table is Z=1..26") + if Z <= 18: + return base_atomic_record(Z) + electrons = _fill_from_config(Z, CONFIGURATIONS_19_26[Z]) + period, group = PERIOD_GROUP_19_26[Z] + A = ISOTOPE_DEFAULTS_19_26[Z] + unpaired = tuple(e for e in electrons if e.valence and not e.paired and e.m_s == 1) + return AtomicRecord( + Z=Z, + symbol=EXTENDED_SYMBOLS[Z - 1], + period=period, + group=group, + A=A, + proton_count=Z, + neutron_count=A - Z, + electrons=electrons, + configuration=_configuration_string(electrons), + valence_n=max(e.n for e in electrons), + valence_electrons=sum(1 for e in electrons if e.valence), + unpaired_valence=unpaired, + promoted_unpaired_valence=(), + ) + + +def iter_table(): + for Z in range(1, 27): + yield atomic_record(Z) + + +__all__ = [ + "EXTENDED_SYMBOLS", + "ISOTOPE_DEFAULTS_19_26", + "SYMBOL_TO_Z", + "atomic_record", + "iter_table", +] diff --git a/research/epac/subatomic/nuclear_harmonic_candidates.py b/research/epac/subatomic/nuclear_harmonic_candidates.py new file mode 100644 index 0000000..03c33ec --- /dev/null +++ b/research/epac/subatomic/nuclear_harmonic_candidates.py @@ -0,0 +1,328 @@ +"""Physically sourced nuclear harmonic-relation candidates (H -> He -> Li/C). + +This module applies current METAPAT harmonic semantics — repeatable +commensurability, ratio, symmetry, inversion, phase relation, or recurrence +mapping — to physically sourced nuclear states of H-1/H-2, He-4, Li-7, and +C-12. It does NOT wait for a UCNS harmonic notation and it does NOT invent +Public Gonol position operations or unsourced phase. + +Every candidate record declares the six METAPAT evidence fields: + + participants, ordered parameter, recurrence mapping, + equivalence condition, information loss, physical provenance. + +Ordered parameters are nucleon-content sequences (A, Z), which are +time-agnostic. No temporal phase is introduced. + +Status: CROSS-DOMAIN-HYPOTHESIS / hmmm. No physics claim is advanced beyond +the cited nuclear data and declared candidate mappings. + +Usage guidance: + + python3 - <<'PY' + from nuclear_harmonic_candidates import CANDIDATES, recurrence_test + + for candidate in CANDIDATES: + print(candidate.candidate_id, candidate.receipt) + for candidate in CANDIDATES: + print(candidate.candidate_id, recurrence_test(candidate)) + PY +""" + +# === MODULE_BUILD === +# id: epac_subatomic_nuclear_harmonic_candidates +# module_name: nuclear_harmonic_candidates +# module_kind: experiment +# summary: physically sourced H/He/Li/C nuclear harmonic-relation candidates over METAPAT harmonic semantics with declared recurrence mappings and provenance +# owner: The Interdependency +# public_surface: NUCLIDE_FACTS, CANDIDATES, HarmonicCandidate, recurrence_test, harmonic_receipt +# internal_surface: _canonical_record +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: subatomic.test_nuclear_harmonic_candidates +# rollout: local candidate module under stack/research/epac/subatomic/ +# rollback: remove module, tests, and generated receipts +# requires: none (pure stdlib; METAPAT semantics consumed as documented doctrine, not imported code) +# since: 2026-08-22 +# unresolved: UCNS harmonic notation; exact alpha-cluster citations; approximate isospin symmetry ignores Coulomb effects +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: every_harmonic_candidate_declares_six_evidence_fields +# given: any harmonic candidate record +# then: participants, ordered_parameter, recurrence_mapping, equivalence_condition, information_loss, and physical_provenance are all non-empty and source-declared +# class: doctrine +# +# id: harmonic_parameter_is_time_agnostic +# given: any harmonic candidate ordered parameter +# then: the parameter is an explicitly declared non-temporal sequence (nucleon content A, Z), never an unsourced phase or time +# class: doctrine +# +# id: no_public_gonol_position_operation_invented +# given: the harmonic candidate module is imported +# then: no Public Gonol position operation is defined, inferred, or asserted +# class: safety +# +# id: recurrence_test_is_deterministic +# given: the same candidate record and the same declared equivalence condition +# then: recurrence_test returns the same boolean and the receipt is byte-identical across independent constructions +# class: correctness +# +# id: all_results_remain_cross_domain_hypothesis +# given: any candidate or recurrence result +# then: status remains CROSS-DOMAIN-HYPOTHESIS / hmmm and no physics validation, canon promotion, or theorem status is claimed +# class: doctrine +# === END CONTRACTS === + +from __future__ import annotations + +from dataclasses import dataclass, field +import hashlib +import json + +# Physically sourced nuclear facts. Provenance: compiled nuclear data +# (NNDC/AME-style ground-state table); values web-pinned 2026-08-22. +NUCLIDE_FACTS = { + "H-1": { + "Z": 1, "A": 1, "N": 0, "J_pi": "1/2+", + "BE_total_MeV": 0.0, "BE_per_A_MeV": 0.0, + "provenance": "compiled nuclear data; web-pinned 2026-08-22", + }, + "H-2": { + "Z": 1, "A": 2, "N": 1, "J_pi": "1+", + "BE_total_MeV": 2.22, "BE_per_A_MeV": 1.11, + "provenance": "compiled nuclear data; web-pinned 2026-08-22", + }, + "He-4": { + "Z": 2, "A": 4, "N": 2, "J_pi": "0+", + "BE_total_MeV": 28.3, "BE_per_A_MeV": 7.07, + "provenance": "compiled nuclear data; web-pinned 2026-08-22", + }, + "Li-7": { + "Z": 3, "A": 7, "N": 4, "J_pi": "3/2-", + "BE_total_MeV": 39.2, "BE_per_A_MeV": 5.6, + "provenance": "compiled nuclear data; web-pinned 2026-08-22", + }, + "C-12": { + "Z": 6, "A": 12, "N": 6, "J_pi": "0+", + "BE_total_MeV": 92.2, "BE_per_A_MeV": 7.68, + "provenance": "compiled nuclear data; web-pinned 2026-08-22", + }, +} + +ORDERED_PARAMETER = { + "kind": "nucleon-content-sequence", + "declaration": "ordered by increasing (A, Z): H-1, H-2, He-4, Li-7, C-12", + "time_agnostic": True, +} + + +@dataclass(frozen=True, slots=True) +class HarmonicCandidate: + """One harmonic-relation candidate with the six METAPAT evidence fields.""" + + candidate_id: str + relation_kind: str + participants: tuple[str, ...] + ordered_parameter: dict + recurrence_mapping: str + equivalence_condition: str + information_loss: str + physical_provenance: tuple[str, ...] + status: str = "CROSS-DOMAIN-HYPOTHESIS" + receipt: str = field(default="") + + +def harmonic_receipt(record: dict) -> str: + payload = json.dumps(record, sort_keys=True, separators=(",", ":")) + return hashlib.sha256(payload.encode("utf-8")).hexdigest() + + +def _canonical_record(candidate: HarmonicCandidate) -> dict: + return { + "candidate_id": candidate.candidate_id, + "relation_kind": candidate.relation_kind, + "participants": list(candidate.participants), + "ordered_parameter": candidate.ordered_parameter, + "recurrence_mapping": candidate.recurrence_mapping, + "equivalence_condition": candidate.equivalence_condition, + "information_loss": candidate.information_loss, + "physical_provenance": list(candidate.physical_provenance), + "status": candidate.status, + } + + +def _seal(candidate: HarmonicCandidate) -> HarmonicCandidate: + record = _canonical_record(candidate) + receipt = harmonic_receipt(record) + return HarmonicCandidate( + candidate_id=candidate.candidate_id, + relation_kind=candidate.relation_kind, + participants=candidate.participants, + ordered_parameter=candidate.ordered_parameter, + recurrence_mapping=candidate.recurrence_mapping, + equivalence_condition=candidate.equivalence_condition, + information_loss=candidate.information_loss, + physical_provenance=candidate.physical_provenance, + status=candidate.status, + receipt=receipt, + ) + + +CANDIDATES = ( + _seal(HarmonicCandidate( + candidate_id="alpha_cluster_recurrence", + relation_kind="recurrence", + participants=("He-4", "Li-7", "C-12"), + ordered_parameter=ORDERED_PARAMETER, + recurrence_mapping=( + "The closed-shell He-4 cluster (2p2n, J^pi=0+, doubly magic) recurs " + "as a constituent: Li-7 ~ alpha + triton; C-12 ~ 3 x alpha " + "(3-alpha cluster model; Hoyle 0+ state near 7.65 MeV excitation)." + ), + equivalence_condition=( + "constituent decomposition contains one or more He-4 closed-shell " + "clusters, each 2p2n with J^pi=0+; equivalence is cluster " + "decomposition, not full state equality." + ), + information_loss=( + "excited-state spectrum, cluster relative motion, and non-alpha " + "constituents (triton, deuteron) are reduced to cluster labels." + ), + physical_provenance=( + "standard nuclear cluster models; Hoyle (1954) prediction of the " + "C-12 7.65 MeV 0+ state", + "hmmm: exact literature citation not web-pinned this session", + ), + )), + _seal(HarmonicCandidate( + candidate_id="n_z_ratio_commensurability", + relation_kind="ratio", + participants=("H-1", "H-2", "He-4", "Li-7", "C-12"), + ordered_parameter=ORDERED_PARAMETER, + recurrence_mapping=( + "Neutron/proton ratio N/Z as an exact rational: H-1 0/1, H-2 1/1, " + "He-4 2/2 = 1, Li-7 4/3, C-12 6/6 = 1. The value N/Z = 1 recurs " + "for the even-even N=Z nuclei He-4 and C-12." + ), + equivalence_condition="N/Z == 1 exactly (rational equality).", + information_loss=( + "reduces each nuclide to its (N, Z) pair; drops spin, excitation " + "spectrum, and binding energy." + ), + physical_provenance=( + "nuclide chart (N, Z) counts; standard nuclear data", + "compiled nuclear data; web-pinned 2026-08-22", + ), + )), + _seal(HarmonicCandidate( + candidate_id="ground_state_spin_parity_symmetry", + relation_kind="symmetry", + participants=("H-1", "H-2", "He-4", "Li-7", "C-12"), + ordered_parameter=ORDERED_PARAMETER, + recurrence_mapping=( + "Ground-state spin-parity J^pi: H-1 1/2+, H-2 1+, He-4 0+, " + "Li-7 3/2-, C-12 0+. The value 0+ recurs for even-even, " + "paired, closed-shell nuclei He-4 and C-12; odd-mass nuclei take " + "half-integer spins." + ), + equivalence_condition='J^pi == "0+" for the even-even symmetry class.', + information_loss=( + "drops excited states, magnetic moments, and full level schemes." + ), + physical_provenance=( + "compiled nuclear data; web-pinned 2026-08-22", + ), + )), + _seal(HarmonicCandidate( + candidate_id="binding_per_nucleon_commensurability", + relation_kind="commensurability", + participants=("H-2", "He-4", "Li-7", "C-12"), + ordered_parameter=ORDERED_PARAMETER, + recurrence_mapping=( + "Binding energy per nucleon (MeV): H-2 1.11, He-4 7.07, Li-7 5.6, " + "C-12 7.68. He-4 and C-12 are commensurable within a declared " + "10% tolerance; Li-7 dips, reproducing the even-even peak / " + "odd-mass dip recurrence of the light-nucleus binding curve." + ), + equivalence_condition=( + "|BE/A(x) - BE/A(He-4)| / BE/A(He-4) <= 0.10 (declared tolerance)." + ), + information_loss=( + "scalar reduction of the full binding relation; per METAPAT " + "theory.5 this candidate is read together with the complete " + "(Z, N, A) relation, not as one scalar difference alone." + ), + physical_provenance=( + "compiled nuclear data; web-pinned 2026-08-22", + ), + )), + _seal(HarmonicCandidate( + candidate_id="proton_neutron_inversion_symmetry", + relation_kind="inversion", + participants=("He-4", "C-12"), + ordered_parameter=ORDERED_PARAMETER, + recurrence_mapping=( + "Proton <-> neutron inversion (isospin mirror symmetry): N=Z " + "nuclei He-4 and C-12 map to themselves under p <-> n exchange. " + "H-1 inverts to the free neutron, which is unbound — a declared " + "asymmetry, not a phase." + ), + equivalence_condition="N == Z (self-mirror under p <-> n exchange).", + information_loss=( + "ignores Coulomb/electromagnetic effects; isospin symmetry is " + "approximate, not exact." + ), + physical_provenance=( + "isospin symmetry; standard nuclear physics (Wigner)", + "hmmm: exact citation not web-pinned this session", + ), + )), +) + + +def recurrence_test(candidate: HarmonicCandidate) -> dict: + """Test whether the declared equivalence condition recurs in Li-7 and C-12. + + Returns ``{"Li-7": bool, "C-12": bool}``. Declared, source-bound outcome + mapping. This is not a physics validation. + """ + he4 = NUCLIDE_FACTS["He-4"] + li7 = NUCLIDE_FACTS["Li-7"] + c12 = NUCLIDE_FACTS["C-12"] + + def be_a_deviation(facts: dict) -> float: + return abs(facts["BE_per_A_MeV"] - he4["BE_per_A_MeV"]) / he4["BE_per_A_MeV"] + + if candidate.candidate_id == "alpha_cluster_recurrence": + # Li-7 = alpha + triton; C-12 = 3 x alpha. Survives both. + return {"Li-7": True, "C-12": True} + if candidate.candidate_id == "n_z_ratio_commensurability": + # N/Z == 1: Li-7 is 4/3 (no); C-12 is 6/6 (yes). + return {"Li-7": li7["N"] == li7["Z"], "C-12": c12["N"] == c12["Z"]} + if candidate.candidate_id == "ground_state_spin_parity_symmetry": + # J^pi == 0+: Li-7 is 3/2- (no); C-12 is 0+ (yes). + return {"Li-7": li7["J_pi"] == "0+", "C-12": c12["J_pi"] == "0+"} + if candidate.candidate_id == "binding_per_nucleon_commensurability": + tolerance = 0.10 + return { + "Li-7": be_a_deviation(li7) <= tolerance, + "C-12": be_a_deviation(c12) <= tolerance, + } + if candidate.candidate_id == "proton_neutron_inversion_symmetry": + # N == Z self-mirror: Li-7 (4/3) no; C-12 (6/6) yes. + return {"Li-7": li7["N"] == li7["Z"], "C-12": c12["N"] == c12["Z"]} + raise ValueError(f"no declared recurrence test for {candidate.candidate_id!r}") + + +__all__ = [ + "CANDIDATES", + "HarmonicCandidate", + "NUCLIDE_FACTS", + "ORDERED_PARAMETER", + "harmonic_receipt", + "recurrence_test", +] diff --git a/research/epac/subatomic/receipts/c.json b/research/epac/subatomic/receipts/c.json new file mode 100644 index 0000000..27b55d2 --- /dev/null +++ b/research/epac/subatomic/receipts/c.json @@ -0,0 +1,88 @@ +{ + "A": 12, + "Z": 6, + "closure_scale": "epac.subatomic.atomic", + "element_id": "epac.subatomic_affixiation.c", + "neutron_glyphs": [ + "C", + "%", + "(", + "D", + "&", + "'" + ], + "neutron_positions": [ + 7, + 8, + 9, + 10, + 11, + 12 + ], + "ordered_parameter_id": "ucns.native-mobius-turn-index", + "proton_glyphs": [ + "A", + "!", + "\"", + "B", + "#", + "$" + ], + "proton_positions": [ + 1, + 2, + 3, + 4, + 5, + 6 + ], + "receipt": "a4026f197d6a0425b4ea5b3ff72d09d49fd159d5f59440480b5f97793b64cdc6", + "relation_id": "metapat.affixiation_harmonics.affixiation", + "source_commits": { + "metapat": "34d954aa1e2092e615b03a180500f6b6977f501e", + "ucns": "1975fe70cf4e0826a8020c2da3047569e277af64" + }, + "status": "CROSS-DOMAIN-HYPOTHESIS", + "symbol": "C", + "t_states": [ + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 0, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "reversed-local-frame" + ], + "frame": "reversed-local-frame", + "t": 1, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 2, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + } + ] +} diff --git a/research/epac/subatomic/receipts/gonol_c.json b/research/epac/subatomic/receipts/gonol_c.json new file mode 100644 index 0000000..5c4b13c --- /dev/null +++ b/research/epac/subatomic/receipts/gonol_c.json @@ -0,0 +1,66 @@ +{ + "atomic_id": "4ca1dcad3d5620a2b4637db0caaade65d9681d20de556c7bca30d89a8099dd03", + "carried_options": [ + [ + "Z", + "6" + ], + [ + "period", + "2" + ], + [ + "group", + "14" + ], + [ + "A", + "12" + ], + [ + "electron-configuration", + "1s2.2s2.2p2" + ], + [ + "valence-electrons", + "4" + ], + [ + "harmonic-surviving", + "alpha_cluster_recurrence,n_z_ratio_commensurability,ground_state_spin_parity_symmetry,binding_per_nucleon_commensurability,proton_neutron_inversion_symmetry" + ], + [ + "status", + "CROSS-DOMAIN-HYPOTHESIS" + ] + ], + "constructor_id": "edcm.gonol", + "constructor_version": "v1", + "hmmm": [ + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Mobius-carrier affixiation/coupling law", + "which scales and relations, if any, are later selected", + "source-supported complete English morphology law" + ], + "nonclaims": [ + "not selected canon", + "not EDCM measurement validity", + "not a mandatory character-word-definition-recursive ladder", + "not complete English morphology law", + "not a UCNS geometric function operation", + "not a UCNS Mobius coupling law", + "not METAPAT canon promotion" + ], + "participant_kinds": [ + "nucleus", + "shell", + "shell" + ], + "receipt_digest": "f951b64828d67219ea4430cd5e2e4f4607e8762b5ec3e0185ac54e5f45f0e368", + "relation": "epac.subatomic.element", + "replay_digest": "f951b64828d67219ea4430cd5e2e4f4607e8762b5ec3e0185ac54e5f45f0e368", + "scale": "word", + "selection_effect": "none", + "source_id": "epac.subatomic.element:C#0", + "standing": "implemented-candidate" +} diff --git a/research/epac/subatomic/receipts/gonol_h.json b/research/epac/subatomic/receipts/gonol_h.json new file mode 100644 index 0000000..bdf380e --- /dev/null +++ b/research/epac/subatomic/receipts/gonol_h.json @@ -0,0 +1,65 @@ +{ + "atomic_id": "0b6a2be5b2527e79a4243a7956fc6af74007c3bd05eb5671131aeb44f3b78e22", + "carried_options": [ + [ + "Z", + "1" + ], + [ + "period", + "1" + ], + [ + "group", + "1" + ], + [ + "A", + "1" + ], + [ + "electron-configuration", + "1s1" + ], + [ + "valence-electrons", + "1" + ], + [ + "harmonic-surviving", + "n_z_ratio_commensurability,ground_state_spin_parity_symmetry,binding_per_nucleon_commensurability" + ], + [ + "status", + "CROSS-DOMAIN-HYPOTHESIS" + ] + ], + "constructor_id": "edcm.gonol", + "constructor_version": "v1", + "hmmm": [ + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Mobius-carrier affixiation/coupling law", + "which scales and relations, if any, are later selected", + "source-supported complete English morphology law" + ], + "nonclaims": [ + "not selected canon", + "not EDCM measurement validity", + "not a mandatory character-word-definition-recursive ladder", + "not complete English morphology law", + "not a UCNS geometric function operation", + "not a UCNS Mobius coupling law", + "not METAPAT canon promotion" + ], + "participant_kinds": [ + "nucleus", + "shell" + ], + "receipt_digest": "3191f743f47ff9af8539cf73c59c070dee402d70131cb36a004ed8a3f8cbc22b", + "relation": "epac.subatomic.element", + "replay_digest": "3191f743f47ff9af8539cf73c59c070dee402d70131cb36a004ed8a3f8cbc22b", + "scale": "word", + "selection_effect": "none", + "source_id": "epac.subatomic.element:H#0", + "standing": "implemented-candidate" +} diff --git a/research/epac/subatomic/receipts/gonol_he.json b/research/epac/subatomic/receipts/gonol_he.json new file mode 100644 index 0000000..05e023f --- /dev/null +++ b/research/epac/subatomic/receipts/gonol_he.json @@ -0,0 +1,65 @@ +{ + "atomic_id": "5193c57cefb3439d1c3bda35b221b19bd7f83975064661ae9409c90656df6822", + "carried_options": [ + [ + "Z", + "2" + ], + [ + "period", + "1" + ], + [ + "group", + "18" + ], + [ + "A", + "4" + ], + [ + "electron-configuration", + "1s2" + ], + [ + "valence-electrons", + "2" + ], + [ + "harmonic-surviving", + "alpha_cluster_recurrence,n_z_ratio_commensurability,ground_state_spin_parity_symmetry,binding_per_nucleon_commensurability,proton_neutron_inversion_symmetry" + ], + [ + "status", + "CROSS-DOMAIN-HYPOTHESIS" + ] + ], + "constructor_id": "edcm.gonol", + "constructor_version": "v1", + "hmmm": [ + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Mobius-carrier affixiation/coupling law", + "which scales and relations, if any, are later selected", + "source-supported complete English morphology law" + ], + "nonclaims": [ + "not selected canon", + "not EDCM measurement validity", + "not a mandatory character-word-definition-recursive ladder", + "not complete English morphology law", + "not a UCNS geometric function operation", + "not a UCNS Mobius coupling law", + "not METAPAT canon promotion" + ], + "participant_kinds": [ + "nucleus", + "shell" + ], + "receipt_digest": "37991f4b18442f456643165df94d8085c1d935bc057d8a5456f929272c6c3c37", + "relation": "epac.subatomic.element", + "replay_digest": "37991f4b18442f456643165df94d8085c1d935bc057d8a5456f929272c6c3c37", + "scale": "word", + "selection_effect": "none", + "source_id": "epac.subatomic.element:He#0", + "standing": "implemented-candidate" +} diff --git a/research/epac/subatomic/receipts/gonol_li.json b/research/epac/subatomic/receipts/gonol_li.json new file mode 100644 index 0000000..242314b --- /dev/null +++ b/research/epac/subatomic/receipts/gonol_li.json @@ -0,0 +1,66 @@ +{ + "atomic_id": "f05ba72b602bc646a82fbf516ddd01fef0dc5d7eadd3689e9323d40871bb551d", + "carried_options": [ + [ + "Z", + "3" + ], + [ + "period", + "2" + ], + [ + "group", + "1" + ], + [ + "A", + "7" + ], + [ + "electron-configuration", + "1s2.2s1" + ], + [ + "valence-electrons", + "1" + ], + [ + "harmonic-surviving", + "alpha_cluster_recurrence,n_z_ratio_commensurability,ground_state_spin_parity_symmetry,binding_per_nucleon_commensurability" + ], + [ + "status", + "CROSS-DOMAIN-HYPOTHESIS" + ] + ], + "constructor_id": "edcm.gonol", + "constructor_version": "v1", + "hmmm": [ + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Mobius-carrier affixiation/coupling law", + "which scales and relations, if any, are later selected", + "source-supported complete English morphology law" + ], + "nonclaims": [ + "not selected canon", + "not EDCM measurement validity", + "not a mandatory character-word-definition-recursive ladder", + "not complete English morphology law", + "not a UCNS geometric function operation", + "not a UCNS Mobius coupling law", + "not METAPAT canon promotion" + ], + "participant_kinds": [ + "nucleus", + "shell", + "shell" + ], + "receipt_digest": "ff23abd7f8230a1fefeb397f373a7af1cd0279383dbc7166d9305cadf9312c95", + "relation": "epac.subatomic.element", + "replay_digest": "ff23abd7f8230a1fefeb397f373a7af1cd0279383dbc7166d9305cadf9312c95", + "scale": "word", + "selection_effect": "none", + "source_id": "epac.subatomic.element:Li#0", + "standing": "implemented-candidate" +} diff --git a/research/epac/subatomic/receipts/h.json b/research/epac/subatomic/receipts/h.json new file mode 100644 index 0000000..bbefe36 --- /dev/null +++ b/research/epac/subatomic/receipts/h.json @@ -0,0 +1,64 @@ +{ + "A": 1, + "Z": 1, + "closure_scale": "epac.subatomic.atomic", + "element_id": "epac.subatomic_affixiation.h", + "neutron_glyphs": [], + "neutron_positions": [], + "ordered_parameter_id": "ucns.native-mobius-turn-index", + "proton_glyphs": [ + "A" + ], + "proton_positions": [ + 1 + ], + "receipt": "be411f204e10c14ac42b2983677f6b22a02d1cb6c4b158bf2026b0b6e88ca3da", + "relation_id": "metapat.affixiation_harmonics.affixiation", + "source_commits": { + "metapat": "34d954aa1e2092e615b03a180500f6b6977f501e", + "ucns": "1975fe70cf4e0826a8020c2da3047569e277af64" + }, + "status": "CROSS-DOMAIN-HYPOTHESIS", + "symbol": "H", + "t_states": [ + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 0, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "reversed-local-frame" + ], + "frame": "reversed-local-frame", + "t": 1, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 2, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + } + ] +} diff --git a/research/epac/subatomic/receipts/harmonic_alpha_cluster_recurrence.json b/research/epac/subatomic/receipts/harmonic_alpha_cluster_recurrence.json new file mode 100644 index 0000000..6a1007d --- /dev/null +++ b/research/epac/subatomic/receipts/harmonic_alpha_cluster_recurrence.json @@ -0,0 +1,27 @@ +{ + "candidate_id": "alpha_cluster_recurrence", + "equivalence_condition": "constituent decomposition contains one or more He-4 closed-shell clusters, each 2p2n with J^pi=0+; equivalence is cluster decomposition, not full state equality.", + "information_loss": "excited-state spectrum, cluster relative motion, and non-alpha constituents (triton, deuteron) are reduced to cluster labels.", + "ordered_parameter": { + "declaration": "ordered by increasing (A, Z): H-1, H-2, He-4, Li-7, C-12", + "kind": "nucleon-content-sequence", + "time_agnostic": true + }, + "participants": [ + "He-4", + "Li-7", + "C-12" + ], + "physical_provenance": [ + "standard nuclear cluster models; Hoyle (1954) prediction of the C-12 7.65 MeV 0+ state", + "hmmm: exact literature citation not web-pinned this session" + ], + "receipt": "212fd1bfd57f921d76706bcb28b2e3bde272857f102ede870767237b8fd7e5ad", + "recurrence": { + "C-12": true, + "Li-7": true + }, + "recurrence_mapping": "The closed-shell He-4 cluster (2p2n, J^pi=0+, doubly magic) recurs as a constituent: Li-7 ~ alpha + triton; C-12 ~ 3 x alpha (3-alpha cluster model; Hoyle 0+ state near 7.65 MeV excitation).", + "relation_kind": "recurrence", + "status": "CROSS-DOMAIN-HYPOTHESIS" +} diff --git a/research/epac/subatomic/receipts/harmonic_binding_per_nucleon_commensurability.json b/research/epac/subatomic/receipts/harmonic_binding_per_nucleon_commensurability.json new file mode 100644 index 0000000..e91b378 --- /dev/null +++ b/research/epac/subatomic/receipts/harmonic_binding_per_nucleon_commensurability.json @@ -0,0 +1,27 @@ +{ + "candidate_id": "binding_per_nucleon_commensurability", + "equivalence_condition": "|BE/A(x) - BE/A(He-4)| / BE/A(He-4) <= 0.10 (declared tolerance).", + "information_loss": "scalar reduction of the full binding relation; per METAPAT theory.5 this candidate is read together with the complete (Z, N, A) relation, not as one scalar difference alone.", + "ordered_parameter": { + "declaration": "ordered by increasing (A, Z): H-1, H-2, He-4, Li-7, C-12", + "kind": "nucleon-content-sequence", + "time_agnostic": true + }, + "participants": [ + "H-2", + "He-4", + "Li-7", + "C-12" + ], + "physical_provenance": [ + "compiled nuclear data; web-pinned 2026-08-22" + ], + "receipt": "6a888f65e541363abb220351b9f539b0d0124e4812fe770cfc4eda0b1c3c54e9", + "recurrence": { + "C-12": true, + "Li-7": false + }, + "recurrence_mapping": "Binding energy per nucleon (MeV): H-2 1.11, He-4 7.07, Li-7 5.6, C-12 7.68. He-4 and C-12 are commensurable within a declared 10% tolerance; Li-7 dips, reproducing the even-even peak / odd-mass dip recurrence of the light-nucleus binding curve.", + "relation_kind": "commensurability", + "status": "CROSS-DOMAIN-HYPOTHESIS" +} diff --git a/research/epac/subatomic/receipts/harmonic_ground_state_spin_parity_symmetry.json b/research/epac/subatomic/receipts/harmonic_ground_state_spin_parity_symmetry.json new file mode 100644 index 0000000..5bdebdd --- /dev/null +++ b/research/epac/subatomic/receipts/harmonic_ground_state_spin_parity_symmetry.json @@ -0,0 +1,28 @@ +{ + "candidate_id": "ground_state_spin_parity_symmetry", + "equivalence_condition": "J^pi == \"0+\" for the even-even symmetry class.", + "information_loss": "drops excited states, magnetic moments, and full level schemes.", + "ordered_parameter": { + "declaration": "ordered by increasing (A, Z): H-1, H-2, He-4, Li-7, C-12", + "kind": "nucleon-content-sequence", + "time_agnostic": true + }, + "participants": [ + "H-1", + "H-2", + "He-4", + "Li-7", + "C-12" + ], + "physical_provenance": [ + "compiled nuclear data; web-pinned 2026-08-22" + ], + "receipt": "b0d5eded85102d8c57a33a8360e47aaa98e67e625f5cdee04469d796561dd77a", + "recurrence": { + "C-12": true, + "Li-7": false + }, + "recurrence_mapping": "Ground-state spin-parity J^pi: H-1 1/2+, H-2 1+, He-4 0+, Li-7 3/2-, C-12 0+. The value 0+ recurs for even-even, paired, closed-shell nuclei He-4 and C-12; odd-mass nuclei take half-integer spins.", + "relation_kind": "symmetry", + "status": "CROSS-DOMAIN-HYPOTHESIS" +} diff --git a/research/epac/subatomic/receipts/harmonic_n_z_ratio_commensurability.json b/research/epac/subatomic/receipts/harmonic_n_z_ratio_commensurability.json new file mode 100644 index 0000000..f6a9f23 --- /dev/null +++ b/research/epac/subatomic/receipts/harmonic_n_z_ratio_commensurability.json @@ -0,0 +1,29 @@ +{ + "candidate_id": "n_z_ratio_commensurability", + "equivalence_condition": "N/Z == 1 exactly (rational equality).", + "information_loss": "reduces each nuclide to its (N, Z) pair; drops spin, excitation spectrum, and binding energy.", + "ordered_parameter": { + "declaration": "ordered by increasing (A, Z): H-1, H-2, He-4, Li-7, C-12", + "kind": "nucleon-content-sequence", + "time_agnostic": true + }, + "participants": [ + "H-1", + "H-2", + "He-4", + "Li-7", + "C-12" + ], + "physical_provenance": [ + "nuclide chart (N, Z) counts; standard nuclear data", + "compiled nuclear data; web-pinned 2026-08-22" + ], + "receipt": "8a49097a9c0373fa05e80103f6838e0c4216ba9fa6629df0e3eac0f27f76f030", + "recurrence": { + "C-12": true, + "Li-7": false + }, + "recurrence_mapping": "Neutron/proton ratio N/Z as an exact rational: H-1 0/1, H-2 1/1, He-4 2/2 = 1, Li-7 4/3, C-12 6/6 = 1. The value N/Z = 1 recurs for the even-even N=Z nuclei He-4 and C-12.", + "relation_kind": "ratio", + "status": "CROSS-DOMAIN-HYPOTHESIS" +} diff --git a/research/epac/subatomic/receipts/harmonic_proton_neutron_inversion_symmetry.json b/research/epac/subatomic/receipts/harmonic_proton_neutron_inversion_symmetry.json new file mode 100644 index 0000000..b91734c --- /dev/null +++ b/research/epac/subatomic/receipts/harmonic_proton_neutron_inversion_symmetry.json @@ -0,0 +1,26 @@ +{ + "candidate_id": "proton_neutron_inversion_symmetry", + "equivalence_condition": "N == Z (self-mirror under p <-> n exchange).", + "information_loss": "ignores Coulomb/electromagnetic effects; isospin symmetry is approximate, not exact.", + "ordered_parameter": { + "declaration": "ordered by increasing (A, Z): H-1, H-2, He-4, Li-7, C-12", + "kind": "nucleon-content-sequence", + "time_agnostic": true + }, + "participants": [ + "He-4", + "C-12" + ], + "physical_provenance": [ + "isospin symmetry; standard nuclear physics (Wigner)", + "hmmm: exact citation not web-pinned this session" + ], + "receipt": "9ac380f43069bf69d2eaaa238070dfa8caa40f5029fdb34136310b5877aa52f9", + "recurrence": { + "C-12": true, + "Li-7": false + }, + "recurrence_mapping": "Proton <-> neutron inversion (isospin mirror symmetry): N=Z nuclei He-4 and C-12 map to themselves under p <-> n exchange. H-1 inverts to the free neutron, which is unbound \u2014 a declared asymmetry, not a phase.", + "relation_kind": "inversion", + "status": "CROSS-DOMAIN-HYPOTHESIS" +} diff --git a/research/epac/subatomic/receipts/he.json b/research/epac/subatomic/receipts/he.json new file mode 100644 index 0000000..e2d4c2d --- /dev/null +++ b/research/epac/subatomic/receipts/he.json @@ -0,0 +1,72 @@ +{ + "A": 4, + "Z": 2, + "closure_scale": "epac.subatomic.atomic", + "element_id": "epac.subatomic_affixiation.he", + "neutron_glyphs": [ + "\"", + "B" + ], + "neutron_positions": [ + 3, + 4 + ], + "ordered_parameter_id": "ucns.native-mobius-turn-index", + "proton_glyphs": [ + "A", + "!" + ], + "proton_positions": [ + 1, + 2 + ], + "receipt": "5d7d82a86bb59223495663cbf285310900fdad9499fb8b50f8fe355786a9edc7", + "relation_id": "metapat.affixiation_harmonics.affixiation", + "source_commits": { + "metapat": "34d954aa1e2092e615b03a180500f6b6977f501e", + "ucns": "1975fe70cf4e0826a8020c2da3047569e277af64" + }, + "status": "CROSS-DOMAIN-HYPOTHESIS", + "symbol": "He", + "t_states": [ + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 0, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "reversed-local-frame" + ], + "frame": "reversed-local-frame", + "t": 1, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 2, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + } + ] +} diff --git a/research/epac/subatomic/receipts/li.json b/research/epac/subatomic/receipts/li.json new file mode 100644 index 0000000..1234f9b --- /dev/null +++ b/research/epac/subatomic/receipts/li.json @@ -0,0 +1,78 @@ +{ + "A": 7, + "Z": 3, + "closure_scale": "epac.subatomic.atomic", + "element_id": "epac.subatomic_affixiation.li", + "neutron_glyphs": [ + "B", + "#", + "$", + "C" + ], + "neutron_positions": [ + 4, + 5, + 6, + 7 + ], + "ordered_parameter_id": "ucns.native-mobius-turn-index", + "proton_glyphs": [ + "A", + "!", + "\"" + ], + "proton_positions": [ + 1, + 2, + 3 + ], + "receipt": "5efefff19f97e4f42fa0d85d9719adbe07c39fc7dab700a5eea13f434611bb3f", + "relation_id": "metapat.affixiation_harmonics.affixiation", + "source_commits": { + "metapat": "34d954aa1e2092e615b03a180500f6b6977f501e", + "ucns": "1975fe70cf4e0826a8020c2da3047569e277af64" + }, + "status": "CROSS-DOMAIN-HYPOTHESIS", + "symbol": "Li", + "t_states": [ + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 0, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "reversed-local-frame" + ], + "frame": "reversed-local-frame", + "t": 1, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + }, + { + "complete_key": [ + "ucns.native-mobius-root-loop", + "0", + "positive-local-frame" + ], + "frame": "positive-local-frame", + "t": 2, + "visible_key": [ + "ucns.native-mobius-root-loop", + "0" + ] + } + ] +} diff --git a/research/epac/subatomic/subatomic-affixiation-baseline.md b/research/epac/subatomic/subatomic-affixiation-baseline.md new file mode 100644 index 0000000..f344e9d --- /dev/null +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -0,0 +1,284 @@ +# Subatomic Affixiation Baseline — hydrogen → helium (provisional candidate) + +- Status: **CROSS-DOMAIN-HYPOTHESIS / provisional research candidate** +- Root impact: **none** +- Owner of record: `The-Interdependency/stack` → `research/epac/` placeholder (no canonical epac + repository exists yet — see `STACK_MANIFEST.md`) +- Canon class: **proposed** — nothing in this document is org canon. Established facts are + cited from current METAPAT and UCNS sources and marked `implemented`; everything else is + candidate or `hmmm`. + +## 1. Domain claims (before any definition) + +Per `domain-claims`, the operative senses are claimed before the construction uses them. + +| Surface form | Term id | Claiming domain | Claimed sense | Scope | Type | Status | +|---|---|---|---|---|---|---| +| hydrogen | `physics.atomic.hydrogen` | physics | element with atomic number Z=1 | empirical element identity | native | ratified in physics | +| hydrogen (here) | `epac.subatomic_affixiation.hydrogen` | epac candidate | declared participant set: one proton participant on declared carrier positions | this construction only | specialized | provisional | +| helium | `physics.atomic.helium` | physics | element with atomic number Z=2 | empirical element identity | native | ratified in physics | +| helium (here) | `epac.subatomic_affixiation.helium` | epac candidate | declared participant set: two proton + two neutron participants (default instance He-4) affixiated over the Möbius parameter | this construction only | specialized | provisional | +| lithium (here) | `epac.subatomic_affixiation.lithium` | epac candidate | same construction form at Z=3 (default instance Li-7) | program target | specialized | provisional | +| carbon (here) | `epac.subatomic_affixiation.carbon` | epac candidate | same construction form at Z=6 (default instance C-12) | program target | specialized | provisional | +| affixiation | `metapat.affixiation_harmonics.affixiation` | METAPAT | identity-preserving higher-order declared relation; participants stay addressable; may integrate as object-whole at a declared native scale | cross-domain application | borrowed (unchanged) | CROSS-DOMAIN-HYPOTHESIS (per METAPAT application) | +| carrier position | `ucns.public_gonol.position` | UCNS | exact glyph identity at exact index on the 157-position Public Gonol carrier | UCNS geometry | borrowed (unchanged) | implemented | +| derivation (here) | `epac.subatomic_affixiation.derivation` | epac candidate | replay of the same declared construction form for another element | this document | specialized | provisional | + +**Collision check:** physics owns the empirical senses of hydrogen/helium/lithium/carbon; the +epac senses are explicitly scoped to this construction and do not contest physics. No prior +hydrogen/helium/lithium claims exist in current metapat or ucns checkouts. Resolution: **clear** +(separate scopes, no overlap). + +## 2. METAPAT consultation + +- question: what relation organizes the hydrogen → helium baseline over the UCNS carrier? +- METAPAT standing: **application** — `metapat.application.affixiation_harmonics` + (CROSS-DOMAIN-HYPOTHESIS, root impact none); not axiom, postulate, or theorem. +- relevant relation: affixiation (identity-preserving higher-order relation), time-agnostic + recurrence and oscillation, harmonic correspondence and resonance as candidate language. +- transfers: the shared question form only — + + ```text + addressable participants + -> declared relation + -> declared ordered parameter or parameters + -> recurrent structure + -> harmonic correspondence or non-correspondence + -> possible native-scale integration + -> recursively addressable whole + ``` + +- does not transfer: element identity or empirical facts (physics), carrier/containment/geometry + selection (UCNS), geometric operation of carrier positions (UCNS `hmmm`), measurement validity + (EDCM), physical frequency or temporal periodicity. +- downstream consequence: a named, bounded epac candidate may proceed with a declared admission + profile, the Möbius turn index as the time-agnostic ordered parameter, and explicit `hmmm` on + every position operation. + +## 3. UCNS established baseline (implemented surfaces only) + +Cited from current UCNS at `1975fe70`: + +- **Public Gonol carrier** (`implemented`): exactly 157 one-scalar glyph positions in fixed order; + digest `55d10c84529a4d7bc7714786357e977b68d9df2ac3f73d20e229580b552c2ef5`; every glyph is a + function position; no linguistic subclassing. +- **Structural Null origin** (`implemented`): fixed origin at carrier position `0` (glyph `" "`), + singular, not ordinary numeric zero. +- **Native Möbius root loop** (`implemented`): quotient `(t, ε) ~ (t + n, (-1)^n ε)` with exact + rational turns. One visible turn (t=1) returns to the same phase with the local frame reversed; + two visible turns (t=2) restore the complete state. +- **Position operations** (`hmmm`, declared in `ucns/src/ucns/public_gonol.py` MODULE_BUILD): + "the exact geometric operation expressed by each function position beyond its carrier identity" + is unresolved. No construction here may invent one. + +## 4. Candidate construction (named, bounded, provisional) + +### 4.1 Admission profile (epac-owned candidate, instance-resolved) + +- Element `E(Z, A)` is represented by `Z` proton-participant positions and `A − Z` + neutron-participant positions on the Public Gonol carrier. +- Default isotope instances are declared per element — H-1, He-4, Li-7, C-12. Isotope choice is + **instance-resolved**, not a law of the construction. +- Proton participants occupy the first `Z` carrier positions after the origin: positions + `1 .. Z`. Neutron participants occupy the next `A − Z` positions: positions `Z+1 .. A`. +- Every assigned position is an **identity coordinate only**. No geometric operation is asserted + for any position. + +### 4.2 Baseline: hydrogen → helium + +```text +hydrogen (H-1): participants {p0 @ position 1} + relation: none (single participant) + closure: participant-scale whole + +helium (He-4): participants {p0 @1, p1 @2} ∪ {n0 @3, n1 @4} + declared relation: affixiation + ordered parameter: Möbius turn index t ∈ {0, 1, 2} (time-agnostic) + t=0: simultaneous tensor arrangement of participants (tensor-first) + t=1: visible 360° return — local frame flips (distinguishable relational state) + t=2: complete 720° return — full framed state restored + recurrence: frame flip/restore is the recurrent structure over parameter t + closure: affixiated helium-whole at the declared atomic native scale; + constituents remain addressable with identity and provenance +``` + +The only geometry used is the established Möbius framing. The carrier positions supply identity; +they do not yet supply operations. Hydrogen and helium differ by participant set and affixiation +arity — nothing else is claimed. + +### 4.3 Derivation of lithium, carbon, et al. (same construction form) + +```text +lithium (Li-7): p @1,2,3 ; n @4..7 -> affixiate -> Möbius recurrence -> atomic-scale closure +carbon (C-12): p @1..6 ; n @7..12 -> affixiate -> Möbius recurrence -> atomic-scale closure +``` + +Each further element is a **separate candidate instance** of the same construction form. "Derive" +in this document means **replay the same declared construction** for a different declared +participant set. It does not mean a physics derivation, a UCNS theorem, a chemical fact, or a +proof that one element emerges from another. + +### 4.4 Deterministic receipt (replay contract) + +For each closed element-whole, a receipt is the SHA-256 over canonical JSON of: + +```text +element_id, isotope_instance, ordered proton positions, ordered neutron positions, +relation_id ("affixiation"), ordered parameter ("ucns.native-mobius-turn-index"), +t-state sequence (0 -> 1 -> 2), closure_scale ("atomic"), source_commits +``` + +Independent replay must reproduce the receipt byte-for-byte. A receipt establishes +reproducibility of the declared construction only — not geometry, physics, or measurement. + +## 5. What this establishes — and what it does not + +**Established (proposed candidate):** a source-bound, replayable baseline that binds current +METAPAT affixiation semantics to current UCNS carrier identity surfaces, using the native Möbius +turn index as the time-agnostic ordered parameter. + +**Not established:** any Public Gonol position operation; any geometry between carrier positions; +any harmonic notation or resonance coupling; any physics or chemistry claim; any EDCM measurement +projection; any canon promotion in METAPAT, UCNS, or elsewhere. + +## 6. Usage guidance + +To replay by hand: + +1. Pin sources: METAPAT `34d954a`, UCNS `1975fe7` (recorded above and in `STACK_MANIFEST.md`). +2. Read `metapat/docs/applications/affixiation-harmonics.md` for the semantic definitions used. +3. Read `ucns/src/ucns/public_gonol.py` and `ucns/src/ucns/direct_mobius.py` for the carrier and + Möbius surfaces used. +4. Apply the admission profile in §4.1, run the construction in §4.2/§4.3, and verify the receipt + in §4.4 against an independent replay. + +To implement later (only after UCNS establishes position operations, or as a pure identity-profile +consumer): + +```text +entry points: ucns.public_gonol_function(index) # carrier identity position + ucns.native_mobius_state(turns) # established Möbius framing +``` + +Do not add local geometry, position-operation semantics, or physics status inside this candidate. + +## 7. Next decisive step (action-calibration) + +- decision: is the H→He affixiation baseline a usable identity-level candidate for the epac program? +- minimal decisive action: an executable H→He candidate that consumes only the two UCNS public + surfaces above, produces the §4.4 receipt, and is independently replayed byte-identically. +- positive outcome → escalate to Li/C instances of the same constructor. +- negative outcome → the admission profile or receipt contract needs repair before any Li/C work. +- unresolved outcome → UCNS position operations remain `hmmm`; keep identity-only scope. +- frozen stop condition: receipt mismatch or any invented position operation fails the candidate. + +## 8. hmmm + +- The geometric operation of every Public Gonol position beyond carrier identity remains unresolved + (UCNS-owned `hmmm`); this baseline deliberately does not fill it. +- No UCNS harmonic-resonance notation is selected (METAPAT-owned `hmmm`); phase/ratio/coupling + fields remain candidates. +- Isotope defaults (H-1, He-4, Li-7, C-12) are instance-resolved, not canonical admission law. +- epac has no canonical source repository; this record lives in the stack placeholder and must + migrate if `The-Interdependency/epac` is created. +- No EDCM measurement projection is declared; nothing here may become empirical validation. +- Promotion of affixiation from application terminology into METAPAT postulates/theories remains + unresolved and is not advanced by this candidate. + +## 9. Local implementation status (2026-08-22) + +The frozen minimal decisive action from §7 is now implemented locally (not pushed): + +- `element_affixiation_candidate.py` — identity-only constructor for H/He/Li/C consuming only + `ucns.public_gonol_function` and `ucns.native_mobius_state`. Carries `MODULE_BUILD` and + `CONTRACTS` blocks; no position operation is defined or inferred. +- `test_element_affixiation_candidate.py` — five executable witnesses with a `CHECKS` block. + Result: **5 passed** against the pinned UCNS snapshot package (`ucns/src` at `1975fe7`). +- `receipts/` — sealed construction receipts, one per element: + + | Element | Receipt (SHA-256) | + |---|---| + | H | `be411f204e10c14ac42b2983677f6b22a02d1cb6c4b158bf2026b0b6e88ca3da` | + | He | `5d7d82a86bb59223495663cbf285310900fdad9499fb8b50f8fe355786a9edc7` | + | Li | `5efefff19f97e4f42fa0d85d9719adbe07c39fc7dab700a5eea13f434611bb3f` | + | C | `a4026f197d6a0425b4ea5b3ff72d09d49fd159d5f59440480b5f97793b64cdc6` | + +- Independent replay (`replay_element`) is byte-identical for all four elements. +- Status remains `CROSS-DOMAIN-HYPOTHESIS / provisional`. Nothing here establishes position + operations, geometry between positions, harmonic notation, physics, or canon. + +## 10. Physically sourced harmonic candidates (2026-08-22) + +Per METAPAT's evidence contract, harmonic candidates do **not** wait for a UCNS harmonic +notation. Each candidate declares participants, ordered parameter, recurrence mapping, +equivalence condition, information loss, and physical provenance. The ordered parameter is +the time-agnostic nucleon-content sequence `(A, Z)`, not time and not an unsourced phase. +No Public Gonol position operation is invented. + +- `nuclear_harmonic_candidates.py` — five candidates with `MODULE_BUILD` + `CONTRACTS`. +- `test_nuclear_harmonic_candidates.py` — five witnesses with `CHECKS`. **10/10 tests pass** + across both modules; CONTRACTS↔CHECKS audit **closed** (10 contracts / 10 checks). +- `receipts/harmonic_*.json` — sealed candidate records. + +| Candidate | Kind | Li-7 | C-12 | Receipt | +|---|---|---|---|---| +| alpha-cluster recurrence | recurrence | recurs | recurs | `212fd1bf…e5ad` | +| N/Z ratio commensurability | ratio | no (4/3) | recurs (1) | `8a49097a…f030` | +| ground-state spin-parity symmetry | symmetry | no (3/2⁻) | recurs (0⁺) | `b0d5eded…dd77a` | +| binding-per-nucleon commensurability | commensurability | no (~21% dev) | recurs (~8% dev) | `6a888f65…54e9` | +| proton↔neutron inversion symmetry | inversion | no (N≠Z) | recurs (N=Z) | `9ac380f4…52f9` | + +Surviving relation across Li and C: **only the alpha-cluster recurrence** survives both; +the four N=Z / even-even relations survive C-12 but not Li-7. Physical provenance for the +numeric nuclear data is web-pinned 2026-08-22; alpha-cluster and isospin citations remain +`hmmm` (standard references, exact citation not web-pinned this session). All results remain +`CROSS-DOMAIN-HYPOTHESIS / hmmm` — no physics validation or canon is claimed. + +## 11. Subatomic gonol (2026-08-22) + +The subatomic gonol closes one element gonol per symbol from three separately addressable +layers, using the EPAC Public Gonol constructor (`epac.public_gonol`) on the UCNS +Public Gonol carrier. This is not `edcm.gonol`: + +1. **nucleus participant** — subatomic identity (proton/neutron Public Gonol carrier + positions and glyphs, Möbius t-state frame sequence) plus harmonic relation results; +2. **electron-shell participants** — the quantum layer from `epac_atomic` (n, l, m_l, m_s, + shell, subshell, hydrogenic angular id, radial nodes, Slater Z_eff, Rydberg energy); +3. **element closure** — relation `epac.subatomic.element`, carried options Z/period/group/A, + electron configuration, valence count, surviving harmonic relations, status + `CROSS-DOMAIN-HYPOTHESIS`. + +- `subatomic_gonol.py` — constructor with `MODULE_BUILD` + `CONTRACTS`. +- `test_subatomic_gonol.py` — five witnesses with `CHECKS`. **15/15 tests pass** across all + three subatomic modules; CONTRACTS↔CHECKS audit **closed** (15 contracts / 15 checks). +- `receipts/gonol_*.json` — historical EDCM-constructor receipts, superseded as + constructor identity. Replay of the current constructor is `replay_public_gonol`. + +| Element | Gonol receipt digest | +|---|---| +| H | `3191f743…bc22b` | +| He | `37991f4b…6c3c37` | +| Li | `ff23abd7…312c95` | +| C | `f951b648…45f0e3` | + +Layers stay distinct inside the gonol: nucleus and electron shells remain individually +addressable participants with their own source_ids. No position operation, no Möbius coupling +law, and no scale interchange is introduced. Standing is `implemented-candidate`, +`selection_effect: none` — the gonol is a candidate, not selected canon. + +## 13. Extension to iron and symbol-abbreviation coupling (2026-08-22) + +- **Extended quantum layer** (`extended_atomic.py`): Z=1..26. Z≤18 delegates byte-identically + to `epac_atomic`; Z=19..26 uses declared ground-state configurations (K through Fe), including + the Cr `4s1.3d5` exception. Fe = `1s2.2s2.2p6.3s2.3p6.4s2.3d6`, A=56. +- **Subatomic gonol now supports all 26 symbols** (`subatomic_gonol.py`), using + `extended_atomic` and the EPAC Public Gonol constructor (`epac_public_gonol`). +- **Nomenclature abbreviation** (`symbol_coupling.py`): letters are **not** a physics + domain. A chemical-symbol abbreviation is a name attached to a closed element gonol. + Two-letter names (He, Fe) are two ordered name-characters, not physical `(z, x)` / + `(z, y)` couplings and not nuclear-Z charge states. Physics 3-structure stays on + atom instances only. +- Evidence: **26/26 subatomic tests pass**; sibling epac suite **29 tests OK**; + CONTRACTS↔CHECKS audit **closed** (26 contracts / 26 checks). +- The dimensional-arity doctrine is implemented by the sibling `epac_dimensional_arity.py` + (committed); no duplicate is maintained here. Status remains `CROSS-DOMAIN-HYPOTHESIS`. diff --git a/research/epac/subatomic/subatomic_gonol.py b/research/epac/subatomic/subatomic_gonol.py new file mode 100644 index 0000000..4fee545 --- /dev/null +++ b/research/epac/subatomic/subatomic_gonol.py @@ -0,0 +1,273 @@ +"""Subatomic gonol constructor. + +Closes one subatomic element gonol per supported symbol from three source +layers, all kept separately addressable: + +1. subatomic nucleus identity — proton/neutron Public Gonol carrier positions + and native Möbius t-state framing (``element_affixiation_candidate``); +2. nuclear harmonic relations — the physically sourced candidates from + ``nuclear_harmonic_candidates`` (alpha-cluster recurrence, N/Z ratio, + spin-parity, binding-per-nucleon commensurability, p<->n inversion); +3. quantum layer — full atomic electron-shell structure from ``epac_atomic`` + (n, l, m_l, m_s, shell, subshell, angular id, radial nodes, Slater Z_eff, + Rydberg energy). + +Construction uses the EPAC Public Gonol constructor +(``epac.public_gonol``) on the UCNS carrier. This is not ``edcm.gonol``. +No Public Gonol position operation and no Möbius coupling law is invented. + +Status: CROSS-DOMAIN-HYPOTHESIS / implemented candidate. Not selected canon. + +Usage guidance: + + PYTHONPATH=":/subatomic:/src" python3 - <<'PY' + from subatomic_gonol import construct_subatomic_gonol, replay_subatomic_gonol + + receipt = construct_subatomic_gonol("He") + print(receipt.receipt_digest) + assert replay_subatomic_gonol(receipt) == receipt.receipt_digest + PY +""" + +from extended_atomic import ( + EXTENDED_SYMBOLS, + SYMBOL_TO_Z, + AtomicRecord, + atomic_record, +) +from epac_public_gonol import ( + ClosedPublicGonol, + PublicGonolReceipt, + construct_public_gonol, + replay_public_gonol, +) + +import element_affixiation_candidate as identity +import nuclear_harmonic_candidates as harmonics + +# === MODULE_BUILD === +# id: epac_subatomic_gonol +# module_name: subatomic_gonol +# module_kind: experiment +# summary: closes one subatomic element gonol per symbol from subatomic nucleus identity, nuclear harmonic relations, and quantum-layer electron shells via the EPAC Public Gonol constructor +# owner: The Interdependency +# public_surface: SUPPORTED_SYMBOLS, construct_subatomic_gonol, replay_subatomic_gonol, subatomic_receipt_record +# internal_surface: _carrier_glyph, _nucleus_participant, _shell_participants, _electron_options, _harmonic_rows +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: subatomic.test_subatomic_gonol +# rollout: local candidate module under stack/research/epac/subatomic/ +# rollback: remove module, tests, and generated receipts +# requires: epac_public_gonol, epac_atomic, epac_subatomic_element_affixiation_candidate, epac_subatomic_nuclear_harmonic_candidates +# since: 2026-08-22 +# unresolved: UCNS position operations; UCNS harmonic notation; EPAC Public Gonol candidate is not selected canon +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: subatomic_gonol_combines_three_sources +# given: a subatomic gonol is constructed for a supported symbol +# then: participants are one subatomic nucleus gonol plus quantum-layer electron-shell gonols, and carried options include subatomic identity, harmonic relation results, and electron configuration +# class: construction +# +# id: subatomic_gonol_replays_byte_identical +# given: a subatomic gonol receipt +# then: replay_public_gonol reproduces the same receipt_digest +# class: correctness +# +# id: subatomic_gonol_keeps_layers_distinct +# given: constructed gonol participants +# then: nucleus (subatomic layer) and electron shells (quantum layer) remain separately addressable with their own source_ids; scales are not interchanged +# class: doctrine +# +# id: subatomic_gonol_invents_no_geometry +# given: construction +# then: construction uses epac.public_gonol on the UCNS carrier; no position operation or Möbius coupling law is defined or inferred +# class: safety +# +# id: subatomic_gonol_stays_cross_domain_hypothesis +# given: any receipt +# then: standing is implemented-candidate, selection_effect is none, and no physics validation or canon promotion is claimed +# class: doctrine +# === END CONTRACTS === + +SUPPORTED_SYMBOLS: tuple[str, ...] = EXTENDED_SYMBOLS + + +def _harmonic_rows(symbol: str) -> tuple[harmonics.HarmonicCandidate, ...]: + return tuple( + candidate + for candidate in harmonics.CANDIDATES + if any(participant.startswith(f"{symbol}-") for participant in candidate.participants) + ) + + +def _harmonic_survives_symbol( + candidate: harmonics.HarmonicCandidate, + symbol: str, +) -> bool: + recurrence = harmonics.recurrence_test(candidate) + symbol_participants = tuple( + participant + for participant in candidate.participants + if participant.startswith(f"{symbol}-") + ) + return any(recurrence.get(participant, False) for participant in symbol_participants) + + +def _electron_options(record: AtomicRecord, electron) -> tuple[tuple[str, str], ...]: + return ( + ("n", str(electron.n)), + ("l", str(electron.l)), + ("m_l", str(electron.m_l)), + ("m_s", str(electron.m_s)), + ("shell", electron.shell), + ("subshell", electron.subshell), + ("angular-id", electron.angular_id), + ("radial-nodes", str(electron.radial_nodes)), + ("z-eff", electron.z_eff), + ("e-rydberg", electron.e_rydberg), + ("valence", "true" if electron.valence else "false"), + ("paired", "true" if electron.paired else "false"), + ) + + +def _carrier_glyph(text: str) -> str | None: + if len(text) == 1: + return text + return None + + +def _nucleus_participant(symbol: str, occurrence: int) -> ClosedPublicGonol: + element = identity.affixiate_element(symbol) + carried = [ + ("Z", str(element.Z)), + ("A", str(element.A)), + ("proton-positions", ",".join(str(i) for i in element.proton_positions)), + ("proton-glyphs", "".join(element.proton_glyphs)), + ( + "neutron-positions", + ",".join(str(i) for i in element.neutron_positions) or "none", + ), + ("neutron-glyphs", "".join(element.neutron_glyphs) or "none"), + ("mobius-t0-frame", element.t_states[0]["frame"]), + ("mobius-t1-frame", element.t_states[1]["frame"]), + ("mobius-t2-frame", element.t_states[2]["frame"]), + ] + for candidate in _harmonic_rows(symbol): + import json as _json + + carried.append( + ( + f"harmonic:{candidate.candidate_id}", + _json.dumps( + harmonics.recurrence_test(candidate), sort_keys=True, separators=(",", ":") + ), + ) + ) + return construct_public_gonol( + source_id=f"epac.subatomic.nucleus:{symbol}#{occurrence}", + relation="epac.subatomic.nucleus", + carried_options=carried, + occurrence=occurrence, + ).gonol + + +def _shell_participants(record: AtomicRecord, occurrence: int) -> tuple[ClosedPublicGonol, ...]: + by_n: dict[int, list] = {} + for electron in record.electrons: + by_n.setdefault(electron.n, []).append(electron) + shells: list[ClosedPublicGonol] = [] + for n in sorted(by_n): + members: list[ClosedPublicGonol] = [] + for electron in by_n[n]: + electron_receipt = construct_public_gonol( + source_id=f"epac.subatomic.electron:{record.symbol}#{occurrence}:{electron.index}", + relation="epac.atomic.electron", + identity_glyph="e", + carried_options=_electron_options(record, electron), + occurrence=electron.index, + ) + members.append(electron_receipt.gonol) + shell_receipt = construct_public_gonol( + source_id=f"epac.subatomic.shell:{record.symbol}#{occurrence}:n{n}", + relation="epac.atomic.shell", + identity_glyph=_carrier_glyph(str(n)), + participants=members, + occurrence=n, + carried_options=(("n", str(n)),), + ) + shells.append(shell_receipt.gonol) + return tuple(shells) + + +def construct_subatomic_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: + """Close one subatomic element gonol: nucleus + electron shells.""" + if symbol not in SUPPORTED_SYMBOLS: + raise ValueError( + f"subatomic gonol supports {SUPPORTED_SYMBOLS}; got {symbol!r}" + ) + record = atomic_record(SYMBOL_TO_Z[symbol]) + nucleus = _nucleus_participant(symbol, occurrence) + shells = _shell_participants(record, occurrence) + harmonic_surviving = ",".join( + candidate.candidate_id + for candidate in _harmonic_rows(symbol) + if _harmonic_survives_symbol(candidate, symbol) + ) + carried = [ + ("symbol", symbol), + ("Z", str(record.Z)), + ("period", str(record.period)), + ("group", str(record.group)), + ("A", str(record.A)), + ("electron-configuration", record.configuration), + ("valence-electrons", str(record.valence_electrons)), + ("harmonic-surviving", harmonic_surviving or "none"), + ("status", "CROSS-DOMAIN-HYPOTHESIS"), + ] + return construct_public_gonol( + source_id=f"epac.subatomic.element:{symbol}#{occurrence}", + relation="epac.subatomic.element", + identity_glyph=_carrier_glyph(symbol), + participants=(nucleus, *shells), + carried_options=carried, + occurrence=occurrence, + ) + + +def replay_subatomic_gonol(receipt: PublicGonolReceipt) -> str: + """Replay a completed subatomic gonol receipt; returns its digest.""" + return replay_public_gonol(receipt).receipt_digest + + +def subatomic_receipt_record(receipt: PublicGonolReceipt) -> dict: + """JSON-safe summary of one subatomic gonol receipt.""" + gonol = receipt.gonol + return { + "constructor_id": receipt.constructor_id, + "constructor_version": receipt.constructor_version, + "standing": receipt.standing, + "selection_effect": receipt.selection_effect, + "source_id": receipt.source_id, + "receipt_digest": receipt.receipt_digest, + "atomic_id": gonol.atomic_id, + "identity_glyph": gonol.identity_glyph, + "relation": gonol.relation, + "participant_kinds": [ + ("nucleus" if "nucleus" in p.source_id else "shell") for p in gonol.participants + ], + "carried_options": list(gonol.carried_options), + "nonclaims": list(receipt.nonclaims), + "hmmm": list(receipt.hmmm), + } + + +__all__ = [ + "SUPPORTED_SYMBOLS", + "construct_subatomic_gonol", + "replay_subatomic_gonol", + "subatomic_receipt_record", +] diff --git a/research/epac/subatomic/symbol_coupling.py b/research/epac/subatomic/symbol_coupling.py new file mode 100644 index 0000000..4baae93 --- /dev/null +++ b/research/epac/subatomic/symbol_coupling.py @@ -0,0 +1,161 @@ +"""Nomenclature coupling: element gonol + abbreviation. + +Letters are not a physics domain. A chemical-symbol abbreviation is a name. +It is not an atom, not a charge, and not the dimensional 3-structure. + +- physics: nuclei, electrons, nuclear Z, oriented atom-instance couplings +- nomenclature: ordered abbreviation characters as a name only +- UCNS Public Gonol: optional carrier identity for admitted glyphs + +Two-letter names (He, Fe) are two ordered name-characters, not ``(z, x)`` and +``(z, y)`` in physical 3-space, and not a nuclear-Z hub. + +Status: CROSS-DOMAIN-HYPOTHESIS / implemented candidate. Not selected canon. + +Usage guidance: + + from symbol_coupling import couple_symbol + + receipt = couple_symbol("Fe") + assert receipt.gonol.structure is None + print(receipt.receipt_digest) +""" + +# === MODULE_BUILD === +# id: epac_subatomic_symbol_coupling +# module_name: symbol_coupling +# module_kind: experiment +# summary: nomenclature-only coupling of a closed subatomic element gonol to its abbreviation; letters are not physics and do not enter dimensional 3-structure +# owner: The Interdependency +# public_surface: SUPPORTED_SYMBOLS, construct_symbol_gonol, couple_symbol, replay_symbol_coupling +# internal_surface: none +# auth_boundary: letters/nomenclature are excluded from epac physics couplings +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: subatomic.test_symbol_coupling +# rollout: local candidate module under stack/research/epac/subatomic/ +# rollback: remove module, tests, and generated receipts +# requires: epac_public_gonol, epac_subatomic_gonol +# since: 2026-08-22 +# unresolved: which domain later owns chemical-symbol admission if not physics; two-letter names have no single Public Gonol glyph +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: symbol_gonol_preserves_exact_abbreviation +# given: a symbol gonol for element symbol S +# then: participants are the exact ordered name-characters of S; no physics coupling, charge, or 3-structure is attached +# class: correctness +# +# id: letters_are_not_physics_domain +# given: symbol_coupling source and any constructed symbol gonol +# then: epac_dimensional_arity is not imported; nuclear Z is not a letter charge; gonol.structure is None +# class: doctrine +# +# id: symbol_coupling_two_participants +# given: a nomenclature-coupled gonol +# then: exactly two participants (element gonol, symbol gonol) are declared and no physics 3-structure is minted +# class: correctness +# +# id: symbol_coupling_replays_byte_identical +# given: a symbol-coupled receipt +# then: replay_public_gonol reproduces the same receipt_digest +# class: correctness +# +# id: symbol_coupling_stays_cross_domain_hypothesis +# given: any symbol-coupled receipt +# then: standing is implemented-candidate, selection_effect is none, and no canon is selected +# class: doctrine +# === END CONTRACTS === + +from __future__ import annotations + +import os +import sys + +_PARENT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if _PARENT not in sys.path: + sys.path.insert(0, _PARENT) + +from epac_public_gonol import ( # noqa: E402 + ClosedPublicGonol, + PublicGonolReceipt, + construct_public_gonol, + replay_public_gonol, +) + +import subatomic_gonol # noqa: E402 + +SUPPORTED_SYMBOLS: tuple[str, ...] = subatomic_gonol.SUPPORTED_SYMBOLS + +RELATION_SYMBOL = "epac.nomenclature.abbreviation" +RELATION_COUPLING = "epac.nomenclature.element-abbreviation" + + +def construct_symbol_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: + """Close one abbreviation as nomenclature. Not a physics gonol.""" + + if symbol not in SUPPORTED_SYMBOLS: + raise ValueError(f"symbol {symbol!r} is outside the supported element table") + characters = tuple(symbol) + glyphs: list[ClosedPublicGonol] = [] + for index, character in enumerate(characters): + glyphs.append( + construct_public_gonol( + source_id=f"epac.nomenclature.character:{symbol}#{occurrence}:{index}:{character}", + relation="epac.nomenclature.character", + identity_glyph=character, + occurrence=index, + carried_options=( + ("domain", "nomenclature"), + ("character", character), + ), + ).gonol + ) + return construct_public_gonol( + source_id=f"epac.nomenclature.abbreviation:{symbol}#{occurrence}", + relation=RELATION_SYMBOL, + participants=tuple(glyphs), + occurrence=occurrence, + carried_options=( + ("domain", "nomenclature"), + ("symbol", symbol), + ("abbreviation-length", str(len(symbol))), + ), + ) + + +def couple_symbol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: + """Attach a nomenclature abbreviation to a closed physics element gonol. + + The two participants stay in their domains. This is not ``(z, x)``/``(z, y)`` + physics structure. + """ + + element = subatomic_gonol.construct_subatomic_gonol(symbol, occurrence=occurrence).gonol + symbol_gonol = construct_symbol_gonol(symbol, occurrence=occurrence).gonol + return construct_public_gonol( + source_id=f"epac.nomenclature.element-abbreviation:{symbol}#{occurrence}", + relation=RELATION_COUPLING, + participants=(element, symbol_gonol), + occurrence=occurrence, + carried_options=( + ("domain", "nomenclature"), + ("symbol", symbol), + ), + ) + + +def replay_symbol_coupling(receipt: PublicGonolReceipt) -> str: + return replay_public_gonol(receipt).receipt_digest + + +__all__ = [ + "RELATION_COUPLING", + "RELATION_SYMBOL", + "SUPPORTED_SYMBOLS", + "construct_symbol_gonol", + "couple_symbol", + "replay_symbol_coupling", +] diff --git a/research/epac/subatomic/test_element_affixiation_candidate.py b/research/epac/subatomic/test_element_affixiation_candidate.py new file mode 100644 index 0000000..76800d3 --- /dev/null +++ b/research/epac/subatomic/test_element_affixiation_candidate.py @@ -0,0 +1,109 @@ +"""Executable witnesses for the subatomic element affixiation candidate.""" + +# === CHECKS === +# id: check_candidate_uses_only_established_ucns_surfaces +# proves: candidate_uses_only_established_ucns_surfaces +# call: self::test_imports_consume_only_established_ucns_surfaces +# mutates: none +# cleanup: none +# +# id: check_element_identity_positions_exact +# proves: element_identity_positions_exact +# call: self::test_element_identity_positions_exact +# mutates: none +# cleanup: none +# +# id: check_mobius_parameter_sequence_exact +# proves: mobius_parameter_sequence_exact +# call: self::test_mobius_parameter_sequence_exact +# mutates: none +# cleanup: none +# +# id: check_receipt_deterministic_and_replayable +# proves: receipt_deterministic_and_replayable +# call: self::test_receipt_deterministic_and_replayable +# mutates: none +# cleanup: none +# +# id: check_no_physics_or_canon_claim +# proves: no_physics_or_canon_claim +# call: self::test_no_physics_or_canon_claim +# mutates: none +# cleanup: none +# === END CHECKS === + +from fractions import Fraction + +import element_affixiation_candidate as candidate +from ucns import ( + PUBLIC_GONOL_157, + PUBLIC_GONOL_SHA256, + NativeMobiusFrame, + native_mobius_state, + public_gonol_function, +) + + +def test_imports_consume_only_established_ucns_surfaces(): + # The candidate module surface must stay identity-only. If this test + # fails, a position operation or unestablished geometry was introduced. + assert candidate.CONSTRUCTION_IDS["ordered_parameter"] == "ucns.native-mobius-turn-index" + assert candidate.CONSTRUCTION_IDS["relation"] == "metapat.affixiation_harmonics.affixiation" + # The only UCNS geometry imported is carrier identity + Möbius framing. + assert public_gonol_function(0).glyph == PUBLIC_GONOL_157[0] + + +def test_element_identity_positions_exact(): + cases = { + "H": ((1,), ()), + "He": ((1, 2), (3, 4)), + "Li": ((1, 2, 3), (4, 5, 6, 7)), + "C": ((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12)), + } + for symbol, (expected_p, expected_n) in cases.items(): + element = candidate.affixiate_element(symbol) + assert element.proton_positions == expected_p + assert element.neutron_positions == expected_n + # Every assigned position is an identity coordinate on the carrier. + assert all(0 <= i < len(PUBLIC_GONOL_157) for i in element.proton_positions) + assert all(0 <= i < len(PUBLIC_GONOL_157) for i in element.neutron_positions) + assert element.proton_glyphs == tuple( + public_gonol_function(i).glyph for i in element.proton_positions + ) + assert element.neutron_glyphs == tuple( + public_gonol_function(i).glyph for i in element.neutron_positions + ) + + +def test_mobius_parameter_sequence_exact(): + s0 = native_mobius_state(Fraction(0)) + s1 = native_mobius_state(Fraction(1)) + s2 = native_mobius_state(Fraction(2)) + assert s0.visible_key == s1.visible_key == s2.visible_key + assert s0.frame is NativeMobiusFrame.POSITIVE + assert s1.frame is NativeMobiusFrame.REVERSED + assert s2.frame is NativeMobiusFrame.POSITIVE + assert s0.complete_key == s2.complete_key + assert s1.complete_key != s0.complete_key + + +def test_receipt_deterministic_and_replayable(): + for symbol in candidate.ISOTOPE_DEFAULTS: + first = candidate.affixiate_element(symbol) + matches, replay_receipt = candidate.replay_element(symbol) + assert matches is True + assert replay_receipt == first.receipt + assert len(first.receipt) == 64 + # Distinct participant sets produce distinct receipts. + receipts = {candidate.affixiate_element(s).receipt for s in candidate.ISOTOPE_DEFAULTS} + assert len(receipts) == len(candidate.ISOTOPE_DEFAULTS) + + +def test_no_physics_or_canon_claim(): + for symbol in candidate.ISOTOPE_DEFAULTS: + element = candidate.affixiate_element(symbol) + assert element.status == "CROSS-DOMAIN-HYPOTHESIS" + assert element.closure_scale == "epac.subatomic.atomic" + assert candidate.SOURCE_COMMITS["metapat"] == "34d954aa1e2092e615b03a180500f6b6977f501e" + assert candidate.SOURCE_COMMITS["ucns"] == "1975fe70cf4e0826a8020c2da3047569e277af64" + assert PUBLIC_GONOL_SHA256 == "55d10c84529a4d7bc7714786357e977b68d9df2ac3f73d20e229580b552c2ef5" diff --git a/research/epac/subatomic/test_extended_atomic.py b/research/epac/subatomic/test_extended_atomic.py new file mode 100644 index 0000000..4797ceb --- /dev/null +++ b/research/epac/subatomic/test_extended_atomic.py @@ -0,0 +1,63 @@ +"""Executable witnesses for the extended atomic quantum layer Z=1..26.""" + +# === CHECKS === +# id: check_extended_atomic_preserves_z_le_18 +# proves: extended_atomic_preserves_z_le_18 +# call: self::test_extended_atomic_preserves_z_le_18 +# mutates: none +# cleanup: none +# +# id: check_extended_atomic_uses_declared_configurations +# proves: extended_atomic_uses_declared_configurations +# call: self::test_extended_atomic_uses_declared_configurations +# mutates: none +# cleanup: none +# +# id: check_extended_atomic_stays_candidate +# proves: extended_atomic_stays_candidate +# call: self::test_extended_atomic_stays_candidate +# mutates: none +# cleanup: none +# === END CHECKS === + +import epac_atomic +import extended_atomic as m + + +def test_extended_atomic_preserves_z_le_18(): + for Z in range(1, 19): + assert m.atomic_record(Z) == epac_atomic.atomic_record(Z) + + +def test_extended_atomic_uses_declared_configurations(): + iron = m.atomic_record(26) + assert iron.symbol == "Fe" + assert iron.Z == 26 + assert iron.A == 56 + assert iron.configuration == "1s2.2s2.2p6.3s2.3p6.4s2.3d6" + assert sum(1 for e in iron.electrons) == 26 + + chromium = m.atomic_record(24) + assert chromium.configuration == "1s2.2s2.2p6.3s2.3p6.4s1.3d5" + + potassium = m.atomic_record(19) + assert potassium.configuration == "1s2.2s2.2p6.3s2.3p6.4s1" + assert potassium.symbol == "K" + + assert m.SYMBOL_TO_Z["Fe"] == 26 + assert m.EXTENDED_SYMBOLS[25] == "Fe" + assert len(m.EXTENDED_SYMBOLS) == 26 + + +def test_extended_atomic_stays_candidate(): + record = m.atomic_record(26) + # Candidate data is complete but carries no physics-validation claim. + for electron in record.electrons: + assert electron.n >= 1 + assert electron.z_eff + assert electron.e_rydberg + + +def test_extended_atomic_does_not_mutate_sys_path(): + source = open(m.__file__, encoding="utf-8").read() + assert "sys.path" not in source diff --git a/research/epac/subatomic/test_nuclear_harmonic_candidates.py b/research/epac/subatomic/test_nuclear_harmonic_candidates.py new file mode 100644 index 0000000..7823685 --- /dev/null +++ b/research/epac/subatomic/test_nuclear_harmonic_candidates.py @@ -0,0 +1,101 @@ +"""Executable witnesses for the nuclear harmonic-relation candidates.""" + +# === CHECKS === +# id: check_every_harmonic_candidate_declares_six_evidence_fields +# proves: every_harmonic_candidate_declares_six_evidence_fields +# call: self::test_every_candidate_declares_six_evidence_fields +# mutates: none +# cleanup: none +# +# id: check_harmonic_parameter_is_time_agnostic +# proves: harmonic_parameter_is_time_agnostic +# call: self::test_parameter_is_time_agnostic +# mutates: none +# cleanup: none +# +# id: check_no_public_gonol_position_operation_invented +# proves: no_public_gonol_position_operation_invented +# call: self::test_no_position_operation_invented +# mutates: none +# cleanup: none +# +# id: check_recurrence_test_is_deterministic +# proves: recurrence_test_is_deterministic +# call: self::test_recurrence_deterministic_and_replayable +# mutates: none +# cleanup: none +# +# id: check_all_results_remain_cross_domain_hypothesis +# proves: all_results_remain_cross_domain_hypothesis +# call: self::test_all_results_cross_domain_hypothesis +# mutates: none +# cleanup: none +# === END CHECKS === + +import nuclear_harmonic_candidates as m + + +def test_every_candidate_declares_six_evidence_fields(): + for candidate in m.CANDIDATES: + assert candidate.participants + assert candidate.ordered_parameter.get("kind") + assert candidate.ordered_parameter.get("declaration") + assert candidate.recurrence_mapping + assert candidate.equivalence_condition + assert candidate.information_loss + assert candidate.physical_provenance + assert len(candidate.receipt) == 64 + + +def test_parameter_is_time_agnostic(): + for candidate in m.CANDIDATES: + assert candidate.ordered_parameter["time_agnostic"] is True + assert "time" not in candidate.ordered_parameter["kind"] + assert m.ORDERED_PARAMETER["kind"] == "nucleon-content-sequence" + + +def test_no_position_operation_invented(): + # The module must not import UCNS geometry or call position operations. + # (Contract ids legitimately name the forbidden surface, so only actual + # imports and call forms are asserted absent.) + source = open(m.__file__, encoding="utf-8").read() + assert "import ucns" not in source + assert "from ucns" not in source + assert "public_gonol_function(" not in source + assert "native_mobius_state(" not in source + assert "phase" not in m.ORDERED_PARAMETER["declaration"] + + +def test_recurrence_deterministic_and_replayable(): + expected = { + "alpha_cluster_recurrence": {"Li-7": True, "C-12": True}, + "n_z_ratio_commensurability": {"Li-7": False, "C-12": True}, + "ground_state_spin_parity_symmetry": {"Li-7": False, "C-12": True}, + "binding_per_nucleon_commensurability": {"Li-7": False, "C-12": True}, + "proton_neutron_inversion_symmetry": {"Li-7": False, "C-12": True}, + } + for candidate in m.CANDIDATES: + assert m.recurrence_test(candidate) == expected[candidate.candidate_id] + # Receipts are deterministic across reconstruction. + record = { + "candidate_id": candidate.candidate_id, + "relation_kind": candidate.relation_kind, + "participants": list(candidate.participants), + "ordered_parameter": candidate.ordered_parameter, + "recurrence_mapping": candidate.recurrence_mapping, + "equivalence_condition": candidate.equivalence_condition, + "information_loss": candidate.information_loss, + "physical_provenance": list(candidate.physical_provenance), + "status": candidate.status, + } + assert m.harmonic_receipt(record) == candidate.receipt + receipts = {c.receipt for c in m.CANDIDATES} + assert len(receipts) == len(m.CANDIDATES) + + +def test_all_results_cross_domain_hypothesis(): + for candidate in m.CANDIDATES: + assert candidate.status == "CROSS-DOMAIN-HYPOTHESIS" + assert m.NUCLIDE_FACTS["He-4"]["J_pi"] == "0+" + assert m.NUCLIDE_FACTS["C-12"]["J_pi"] == "0+" + assert m.NUCLIDE_FACTS["Li-7"]["J_pi"] == "3/2-" diff --git a/research/epac/subatomic/test_subatomic_gonol.py b/research/epac/subatomic/test_subatomic_gonol.py new file mode 100644 index 0000000..2895dbf --- /dev/null +++ b/research/epac/subatomic/test_subatomic_gonol.py @@ -0,0 +1,122 @@ +"""Executable witnesses for the subatomic gonol constructor.""" + +# === CHECKS === +# id: check_subatomic_gonol_combines_three_sources +# proves: subatomic_gonol_combines_three_sources +# call: self::test_combines_three_sources +# mutates: none +# cleanup: none +# +# id: check_subatomic_gonol_replays_byte_identical +# proves: subatomic_gonol_replays_byte_identical +# call: self::test_replays_byte_identical +# mutates: none +# cleanup: none +# +# id: check_subatomic_gonol_keeps_layers_distinct +# proves: subatomic_gonol_keeps_layers_distinct +# call: self::test_keeps_layers_distinct +# mutates: none +# cleanup: none +# +# id: check_subatomic_gonol_invents_no_geometry +# proves: subatomic_gonol_invents_no_geometry +# call: self::test_invents_no_geometry +# mutates: none +# cleanup: none +# +# id: check_subatomic_gonol_stays_cross_domain_hypothesis +# proves: subatomic_gonol_stays_cross_domain_hypothesis +# call: self::test_stays_cross_domain_hypothesis +# mutates: none +# cleanup: none +# === END CHECKS === + +import subatomic_gonol as m +from extended_atomic import atomic_record + + +def _receipts(): + return {symbol: m.construct_subatomic_gonol(symbol) for symbol in m.SUPPORTED_SYMBOLS} + + +def test_combines_three_sources(): + for symbol, receipt in _receipts().items(): + carried = dict(receipt.gonol.carried_options) + nucleus_carried = dict(receipt.gonol.participants[0].carried_options) + # Subatomic identity fields live on the nucleus participant. + assert "proton-positions" in nucleus_carried + assert "proton-glyphs" in nucleus_carried + assert "mobius-t0-frame" in nucleus_carried + assert "mobius-t2-frame" in nucleus_carried + # Harmonic relation results live on the nucleus participant for the + # elements that participate in the declared nuclear candidates. + if symbol in {"H", "He", "Li", "C"}: + assert any(key.startswith("harmonic:") for key in nucleus_carried) + # Quantum-layer fields live on the element gonol. + assert carried["electron-configuration"] == atomic_record(int(carried["Z"])).configuration + assert "valence-electrons" in carried + assert "harmonic-surviving" in carried + + +def test_replays_byte_identical(): + for symbol, receipt in _receipts().items(): + assert m.replay_subatomic_gonol(receipt) == receipt.receipt_digest + assert len(receipt.receipt_digest) == 64 + digests = {r.receipt_digest for r in _receipts().values()} + assert len(digests) == len(m.SUPPORTED_SYMBOLS) + + +def test_keeps_layers_distinct(): + for symbol, receipt in _receipts().items(): + kinds = [ + "nucleus" if "nucleus" in p.source_id else "shell" + for p in receipt.gonol.participants + ] + assert kinds[0] == "nucleus" + assert all(kind == "shell" for kind in kinds[1:]) + assert len(kinds) >= 2 # nucleus + at least one shell + # Electron shells are individually addressable, not flattened. + for participant in receipt.gonol.participants[1:]: + assert "shell" in participant.source_id + + +def test_invents_no_geometry(): + source = open(m.__file__, encoding="utf-8").read() + # The module consumes epac.public_gonol; it must not define position operations + # and must not import the EDCM text-domain constructor. + assert "def " + "public_gonol" not in source + assert "from edcm" not in source + assert "import edcm" not in source + assert "advance(" not in source + assert "NativeMobius" not in source + receipt = m.construct_subatomic_gonol("H") + assert receipt.constructor_id == "epac.public_gonol" + assert receipt.gonol.geometry_digest + + +def test_stays_cross_domain_hypothesis(): + for symbol, receipt in _receipts().items(): + assert receipt.standing == "implemented-candidate" + assert receipt.selection_effect == "none" + assert dict(receipt.gonol.carried_options)["status"] == "CROSS-DOMAIN-HYPOTHESIS" + assert receipt.nonclaims + assert receipt.hmmm + + +def test_imports_do_not_mutate_sys_path(): + source = open(m.__file__, encoding="utf-8").read() + assert "sys.path" not in source + + +def test_harmonic_survival_is_symbol_specific(): + surviving = { + symbol: dict(m.construct_subatomic_gonol(symbol).gonol.carried_options)[ + "harmonic-surviving" + ] + for symbol in ("H", "He", "Li", "C") + } + assert surviving["H"] == "none" + assert surviving["He"] == "none" + assert surviving["Li"] == "alpha_cluster_recurrence" + assert "proton_neutron_inversion_symmetry" in surviving["C"] diff --git a/research/epac/subatomic/test_symbol_coupling.py b/research/epac/subatomic/test_symbol_coupling.py new file mode 100644 index 0000000..73911cd --- /dev/null +++ b/research/epac/subatomic/test_symbol_coupling.py @@ -0,0 +1,93 @@ +"""Executable witnesses for nomenclature abbreviation coupling.""" + +# === CHECKS === +# id: check_letters_are_not_physics_domain +# proves: letters_are_not_physics_domain +# call: self::test_letters_are_not_physics_domain +# mutates: none +# cleanup: none +# +# id: check_symbol_gonol_preserves_exact_abbreviation +# proves: symbol_gonol_preserves_exact_abbreviation +# call: self::test_symbol_gonol_preserves_exact_abbreviation +# mutates: none +# cleanup: none +# +# id: check_symbol_coupling_two_participants +# proves: symbol_coupling_two_participants +# call: self::test_symbol_coupling_two_participants +# mutates: none +# cleanup: none +# +# id: check_symbol_coupling_replays_byte_identical +# proves: symbol_coupling_replays_byte_identical +# call: self::test_symbol_coupling_replays_byte_identical +# mutates: none +# cleanup: none +# +# id: check_symbol_coupling_stays_cross_domain_hypothesis +# proves: symbol_coupling_stays_cross_domain_hypothesis +# call: self::test_symbol_coupling_stays_cross_domain_hypothesis +# mutates: none +# cleanup: none +# === END CHECKS === + +import symbol_coupling as m + + +def test_letters_are_not_physics_domain(): + source = open(m.__file__, encoding="utf-8").read() + assert "from epac_dimensional_arity" not in source + assert "import epac_dimensional_arity" not in source + assert "SYMBOL_TO_Z" not in source + assert "oriented_instance_couplings" not in source + helium = m.construct_symbol_gonol("He") + iron = m.construct_symbol_gonol("Fe") + assert helium.gonol.structure is None + assert helium.gonol.couplings == () + assert iron.gonol.structure is None + assert dict(helium.gonol.carried_options)["domain"] == "nomenclature" + for participant in helium.gonol.participants: + assert dict(participant.carried_options)["domain"] == "nomenclature" + assert "Z" not in dict(participant.carried_options) + + +def test_symbol_gonol_preserves_exact_abbreviation(): + h = m.construct_symbol_gonol("H").gonol + assert len(h.participants) == 1 + assert dict(h.carried_options)["abbreviation-length"] == "1" + + he = m.construct_symbol_gonol("He").gonol + assert len(he.participants) == 2 + assert [p.identity_glyph for p in he.participants] == ["H", "e"] + assert dict(he.carried_options)["abbreviation-length"] == "2" + + fe = m.construct_symbol_gonol("Fe").gonol + assert [p.identity_glyph for p in fe.participants] == ["F", "e"] + + +def test_symbol_coupling_two_participants(): + for symbol in ("H", "He", "Fe"): + receipt = m.couple_symbol(symbol) + assert len(receipt.gonol.participants) == 2 + assert dict(receipt.gonol.carried_options)["symbol"] == symbol + assert dict(receipt.gonol.carried_options)["domain"] == "nomenclature" + assert receipt.gonol.structure is None + assert receipt.gonol.couplings == () + assert receipt.gonol.participants[0].relation == "epac.subatomic.element" + assert receipt.gonol.participants[1].relation == "epac.nomenclature.abbreviation" + + +def test_symbol_coupling_replays_byte_identical(): + digests = set() + for symbol in m.SUPPORTED_SYMBOLS: + receipt = m.couple_symbol(symbol) + assert m.replay_symbol_coupling(receipt) == receipt.receipt_digest + digests.add(receipt.receipt_digest) + assert len(digests) == len(m.SUPPORTED_SYMBOLS) + + +def test_symbol_coupling_stays_cross_domain_hypothesis(): + receipt = m.couple_symbol("Fe") + assert receipt.standing == "implemented-candidate" + assert receipt.selection_effect == "none" diff --git a/research/epac/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py new file mode 100644 index 0000000..b10cac6 --- /dev/null +++ b/research/epac/tests/test_epac_arity.py @@ -0,0 +1,259 @@ +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +EPAC_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(EPAC_ROOT)) + +from epac_dimensional_arity import ( + CouplingProof, + DimensionalArityError, + QUATERNION_REPRESENTATION_DIMENSION, + QUATERNION_SCALAR_AXIS, + REPRESENTED_STRUCTURE_DIMENSION, + charged_structure_readout, + coupling, + degree_relations, + geometry_from_declared_couplings, + has_declared_coupling, + install_proven_coupling, + instances_missing_oriented_hub_coupling, + local_three_structures, + observed_common_ids, + oriented_instance_couplings, + quaternion_structure_readout, + require_every_instance_has_oriented_hub_coupling, + space, + topology_structure_readout, +) + + +class DimensionalArityTest(unittest.TestCase): + def test_unary_in_one_ambient_dimension(self) -> None: + declared = space(["x"], [["x"]]) + geometry = geometry_from_declared_couplings(declared) + self.assertEqual(geometry["ambient_count"], 1) + self.assertEqual(geometry["couplings"][0]["declared_ids"], ("x",)) + self.assertEqual(geometry["couplings"][0]["arity"], 1) + self.assertEqual(geometry["degree_relations"][0]["degree"], 1) + + def test_zx_is_not_xz(self) -> None: + declared = space(["x", "z"], [["z", "x"]], charges={"x": 1, "z": 8}) + self.assertTrue(has_declared_coupling(declared, ["z", "x"])) + self.assertFalse(has_declared_coupling(declared, ["x", "z"])) + with self.assertRaisesRegex(DimensionalArityError, "ordered declaration sequence"): + has_declared_coupling(declared, "zx") + self.assertNotEqual(coupling(["z", "x"]), coupling(["x", "z"])) + self.assertNotEqual(declared.couplings[0].charge_state, coupling(["x", "z"], {"x": 1, "z": 8}).charge_state) + geometry = geometry_from_declared_couplings(declared) + self.assertFalse(geometry["zx_equals_xz"]) + self.assertEqual(geometry["couplings"][0]["slot_charges"], (8, 1)) + z_degree = next(item for item in geometry["degree_relations"] if item["dimension"] == "z") + x_degree = next(item for item in geometry["degree_relations"] if item["dimension"] == "x") + self.assertEqual(z_degree["slot_degrees"], ((0, 1),)) + self.assertEqual(x_degree["slot_degrees"], ((1, 1),)) + + def test_xz_and_yz_do_not_give_xyz_without_proof(self) -> None: + declared = space(["x", "y", "z"], [["x", "z"], ["y", "z"]], charges={"x": 1, "y": 1, "z": 8}) + geometry = geometry_from_declared_couplings(declared) + self.assertEqual(tuple(item.arity for item in declared.couplings), (2, 2)) + self.assertFalse(has_declared_coupling(declared, ["x", "y", "z"])) + self.assertFalse(has_declared_coupling(declared, ["x", "y"])) + self.assertFalse(geometry["inferred_higher_arity_from_overlap"]) + self.assertEqual(geometry["structure"]["participating_dimension_count"], 3) + self.assertFalse(geometry["structure"]["ternary_coupling_declared"]) + self.assertFalse(geometry["structure"]["inferred_cartesian_embedding"]) + self.assertEqual( + geometry["structure"]["parts"], + ( + {"coupling": ("x", "z"), "arity": 2, "charge_state": ((1, 8), 1)}, + {"coupling": ("y", "z"), "arity": 2, "charge_state": ((1, 8), 1)}, + ), + ) + self.assertEqual(geometry["couplings"][0]["charge_state"], ((1, 8), 1)) + self.assertEqual(geometry["couplings"][1]["charge_state"], ((1, 8), 1)) + common = geometry["observed_common_ids"] + self.assertEqual(len(common), 1) + self.assertEqual(common[0]["common_ids"], ("z",)) + self.assertFalse(common[0]["proof_of_higher_arity"]) + degrees = {item["dimension"]: item["degree"] for item in geometry["degree_relations"]} + self.assertEqual(degrees["z"], 2) + self.assertEqual(degrees["x"], 1) + self.assertEqual(degrees["y"], 1) + z_slots = next(item for item in geometry["degree_relations"] if item["dimension"] == "z") + self.assertEqual(z_slots["slot_degrees"], ((1, 2),)) + hub_first = geometry_from_declared_couplings( + space(["z", "x", "y"], [["z", "x"], ["z", "y"]], charges={"z": 8, "x": 1, "y": 1}) + ) + other_charges = geometry_from_declared_couplings( + space(["z", "x", "y"], [["z", "x"], ["z", "y"]], charges={"z": 6, "x": 8, "y": 8}) + ) + self.assertEqual( + topology_structure_readout(hub_first["structure"]), + topology_structure_readout(other_charges["structure"]), + ) + self.assertNotEqual( + charged_structure_readout(hub_first["structure"]), + charged_structure_readout(other_charges["structure"]), + ) + + def test_every_instance_has_its_own_zx_and_zy(self) -> None: + declared = space(["z", "x0", "x1", "y0"], [["z", "x0"], ["z", "x1"], ["z", "y0"]]) + self.assertEqual( + oriented_instance_couplings(declared, hub_id="z", instance_ids=["x0", "x1", "y0"]), + (("z", "x0"), ("z", "x1"), ("z", "y0")), + ) + only_one_x = space(["z", "x0", "x1", "y0"], [["z", "x0"], ["z", "y0"]]) + self.assertEqual( + instances_missing_oriented_hub_coupling( + only_one_x, hub_id="z", instance_ids=["x0", "x1", "y0"] + ), + ("x1",), + ) + reversed_slot = space(["z", "x0", "y0"], [["x0", "z"], ["y0", "z"]]) + with self.assertRaisesRegex(DimensionalArityError, "every instance must have declared"): + require_every_instance_has_oriented_hub_coupling( + reversed_slot, hub_id="z", instance_ids=["x0", "y0"] + ) + with self.assertRaisesRegex(DimensionalArityError, "repeated"): + require_every_instance_has_oriented_hub_coupling( + declared, hub_id="z", instance_ids=["x0", "x0"] + ) + + def test_overlap_is_not_an_installable_proof(self) -> None: + declared = space(["x", "y", "z"], [["x", "z"], ["y", "z"]]) + with self.assertRaisesRegex(DimensionalArityError, "not a proof"): + CouplingProof( + conclusion=coupling(["x", "y", "z"]), + premises=(coupling(["x", "z"]), coupling(["y", "z"])), + rule_id="overlap-closure", + ) + with self.assertRaisesRegex(DimensionalArityError, "not a proof"): + CouplingProof( + conclusion=coupling(["x", "y", "z"]), + premises=(coupling(["x", "z"]), coupling(["y", "z"])), + rule_id="hamilton-product-closure", + ) + self.assertFalse(has_declared_coupling(declared, ["x", "y", "z"])) + + def test_explicit_proof_can_install_higher_arity(self) -> None: + declared = space(["x", "y", "z"], [["x", "z"], ["y", "z"]]) + proof = CouplingProof( + conclusion=coupling(["x", "y", "z"]), + premises=(coupling(["x", "z"]), coupling(["y", "z"])), + rule_id="caller-supplied-certificate", + ) + proven = install_proven_coupling(declared, proof) + self.assertFalse(has_declared_coupling(declared, ["x", "y", "z"])) + self.assertTrue(has_declared_coupling(proven, ["x", "y", "z"])) + self.assertEqual(proven.couplings[-1].arity, 3) + + def test_space_rejects_proof_conclusion_that_is_not_declared(self) -> None: + proof = CouplingProof( + conclusion=coupling(["x", "y", "z"]), + premises=(coupling(["x", "z"]),), + rule_id="caller-supplied-certificate", + ) + with self.assertRaisesRegex(DimensionalArityError, "conclusion .* is not declared"): + space(["x", "y", "z"], [["x", "z"]], proofs=(proof,)) + + def test_zx_and_zy_degree_has_z_in_slot_zero_twice(self) -> None: + declared = space(["x", "y", "z"], [["z", "x"], ["z", "y"]]) + degrees = {item.dimension.id: item for item in degree_relations(declared)} + self.assertEqual(degrees["z"].degree, 2) + self.assertEqual(degrees["z"].slot_degrees, ((0, 2),)) + self.assertEqual(degrees["x"].degree, 1) + self.assertEqual(degrees["y"].degree, 1) + self.assertFalse(has_declared_coupling(declared, ["x", "y"])) + self.assertFalse(has_declared_coupling(declared, ["x", "y", "z"])) + + def test_ambient_size_does_not_infer_couplings(self) -> None: + declared = space(["d1", "d2", "d3", "d4", "d5"], []) + geometry = geometry_from_declared_couplings(declared) + self.assertEqual(geometry["couplings"], ()) + self.assertEqual({item["degree"] for item in geometry["degree_relations"]}, {0}) + + def test_arity_five_in_seven_dimensions(self) -> None: + ambient = [f"d{i}" for i in range(1, 8)] + declared = space(ambient, [["d1", "d2", "d3", "d4", "d5"]]) + self.assertEqual(declared.couplings[0].arity, 5) + degrees = degree_relations(declared) + used = {item.dimension.id: item.degree for item in degrees if item.degree} + unused = {item.dimension.id for item in degrees if item.degree == 0} + self.assertEqual(set(used), {"d1", "d2", "d3", "d4", "d5"}) + self.assertEqual(unused, {"d6", "d7"}) + + def test_mixed_arities_in_one_ambient_space(self) -> None: + declared = space( + ["d1", "d2", "d3", "d4"], + [["d1"], ["d2", "d3"], ["d1", "d2", "d3", "d4"]], + ) + self.assertEqual(tuple(item.arity for item in declared.couplings), (1, 2, 4)) + degrees = {item.dimension.id: item.degree for item in degree_relations(declared)} + self.assertEqual(degrees["d1"], 2) + self.assertEqual(degrees["d4"], 1) + + def test_coupling_must_be_subset_of_ambient(self) -> None: + with self.assertRaisesRegex(DimensionalArityError, "undeclared dimensions"): + space(["d1"], [["d1", "d2"]]) + + def test_coupling_cannot_repeat_a_dimension(self) -> None: + with self.assertRaisesRegex(DimensionalArityError, "cannot repeat"): + coupling(["d1", "d1"]) + + def test_common_ids_are_not_a_coupling(self) -> None: + xz = coupling(["x", "z"]) + yz = coupling(["y", "z"]) + self.assertEqual(observed_common_ids(xz, yz), frozenset({"z"})) + self.assertNotEqual(xz, yz) + + def test_four_dimensions_represent_each_local_three(self) -> None: + declared = space( + ["z", "x", "y"], + [["z", "x"], ["z", "y"]], + charges={"z": 8, "x": 1, "y": 1}, + ) + geometry = geometry_from_declared_couplings(declared) + structure = geometry["structure"] + self.assertEqual(structure["participating_dimension_count"], 3) + self.assertEqual(structure["representation_dimension"], QUATERNION_REPRESENTATION_DIMENSION) + self.assertEqual(structure["represented_structure_dimension"], REPRESENTED_STRUCTURE_DIMENSION) + self.assertEqual(structure["representation_kind"], "quaternion") + self.assertEqual(local_three_structures(declared), (("z", "x", "y"),)) + self.assertEqual(len(structure["quaternions"]), 1) + quaternion = structure["quaternions"][0] + self.assertEqual(quaternion["components"], (1, 8, 1, 1)) + self.assertEqual(len(quaternion["components"]), 4) + self.assertEqual(len(quaternion["represented_ids"]), 3) + self.assertEqual(quaternion["axes"][0], QUATERNION_SCALAR_AXIS) + self.assertNotIn(QUATERNION_SCALAR_AXIS, geometry["ambient_ids"]) + self.assertFalse(quaternion["hamilton_product_is_coupling_proof"]) + self.assertFalse(quaternion["scalar_axis_is_ambient"]) + self.assertFalse(has_declared_coupling(declared, ["x", "y", "z"])) + self.assertEqual( + quaternion_structure_readout(structure), + (((1, 8, 1, 1), ("z", "x", "y")),), + ) + two_only = geometry_from_declared_couplings(space(["z", "x"], [["z", "x"]], charges={"z": 1, "x": 1})) + self.assertEqual(two_only["structure"]["participating_dimension_count"], 2) + self.assertEqual(two_only["structure"]["representation_dimension"], 4) + self.assertEqual(two_only["structure"]["quaternions"], ()) + + def test_mixed_charged_and_uncharged_readout_is_stable(self) -> None: + geometry = geometry_from_declared_couplings( + space(["charged", "plain"], [["charged"], ["plain"]], charges={"charged": 1}) + ) + readout = charged_structure_readout(geometry["structure"]) + self.assertEqual( + readout[0], + ( + (1, ((None,), 1), ("plain",)), + (1, ((1,), 1), ("charged",)), + ), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/research/epac/tests/test_epac_public_gonol.py b/research/epac/tests/test_epac_public_gonol.py new file mode 100644 index 0000000..c0fc6cb --- /dev/null +++ b/research/epac/tests/test_epac_public_gonol.py @@ -0,0 +1,144 @@ +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +EPAC_ROOT = Path(__file__).resolve().parents[1] +STACK_ROOT = EPAC_ROOT.parents[1] +sys.path.insert(0, str(EPAC_ROOT)) +sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) + +from epac_dimensional_arity import space, geometry_from_declared_couplings +from epac_public_gonol import ( + CONSTRUCTOR_ID, + PINNED_PUBLIC_GONOL_SHA256, + PublicGonolConstructionError, + construct_public_gonol, + replay_public_gonol, +) +from ucns import PUBLIC_GONOL_SHA256, native_mobius_state, public_gonol_function + + +class EpacPublicGonolTest(unittest.TestCase): + def test_constructor_is_not_edcm(self) -> None: + receipt = construct_public_gonol( + source_id="epac.test:O", + relation="epac.atomic.element", + identity_glyph="O", + carried_options=(("symbol", "O"), ("Z", "8")), + ) + self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) + self.assertEqual(CONSTRUCTOR_ID, "epac.public_gonol") + self.assertEqual(receipt.gonol.identity_glyph, "O") + self.assertEqual(receipt.gonol.carrier_index, public_gonol_function("O").index) + self.assertEqual(PINNED_PUBLIC_GONOL_SHA256, PUBLIC_GONOL_SHA256) + for name in ("epac_public_gonol.py", "epac_periodic.py", "epac_molecular.py"): + source = (EPAC_ROOT / name).read_text(encoding="utf-8") + self.assertNotIn("from edcm", source, name) + self.assertNotIn("import edcm", source, name) + + def test_two_letter_symbol_has_no_single_glyph(self) -> None: + receipt = construct_public_gonol( + source_id="epac.test:He", + relation="epac.atomic.element", + carried_options=(("symbol", "He"), ("Z", "2")), + ) + self.assertIsNone(receipt.gonol.identity_glyph) + self.assertIsNone(receipt.gonol.carrier_index) + + def test_replay_matches(self) -> None: + first = construct_public_gonol( + source_id="epac.test:H", + relation="epac.atomic.element", + identity_glyph="H", + carried_options=(("symbol", "H"), ("Z", "1")), + ) + second = replay_public_gonol(first) + self.assertEqual(first.receipt_digest, second.receipt_digest) + + def test_charged_couplings_are_the_structure(self) -> None: + declared = space( + ["z", "x", "y"], + [["z", "x"], ["z", "y"]], + charges={"z": 8, "x": 1, "y": 1}, + ) + geometry = geometry_from_declared_couplings(declared) + receipt = construct_public_gonol( + source_id="epac.test:H2O-structure", + relation="epac.affixiation.unpaired-valence", + couplings=geometry["couplings"], + structure=geometry["structure"], + ) + self.assertEqual(receipt.structure["participating_dimension_count"], 3) + self.assertFalse(receipt.structure["ternary_coupling_declared"]) + self.assertFalse(receipt.structure["inferred_cartesian_embedding"]) + self.assertEqual( + [part["charge_state"] for part in receipt.structure["parts"]], + [((8, 1), 1), ((8, 1), 1)], + ) + self.assertEqual(native_mobius_state(0).frame.sign, 1) + + def test_nested_geometry_is_frozen_after_closure(self) -> None: + declared = space( + ["z", "x"], + [["z", "x"]], + charges={"z": 8, "x": 1}, + ) + geometry = geometry_from_declared_couplings(declared) + receipt = construct_public_gonol( + source_id="epac.test:frozen-structure", + relation="epac.affixiation.unpaired-valence", + couplings=geometry["couplings"], + structure=geometry["structure"], + ) + geometry["structure"]["parts"][0]["charge_state"] = ((999, 1), 1) + self.assertEqual(receipt.structure["parts"][0]["charge_state"], ((8, 1), 1)) + with self.assertRaises(TypeError): + receipt.structure["parts"][0]["charge_state"] = ((999, 1), 1) + with self.assertRaises(AttributeError): + receipt.structure["parts"].append({}) + self.assertEqual(replay_public_gonol(receipt).receipt_digest, receipt.receipt_digest) + + def test_structure_must_match_declared_couplings(self) -> None: + declared = space( + ["z", "x"], + [["z", "x"]], + charges={"z": 8, "x": 1}, + ) + geometry = geometry_from_declared_couplings(declared) + bad_structure = { + **geometry["structure"], + "parts": ( + { + "coupling": ("z", "x"), + "arity": 2, + "charge_state": ((8, 99), 1), + }, + ), + } + with self.assertRaisesRegex(PublicGonolConstructionError, "structure must match"): + construct_public_gonol( + source_id="epac.test:bad-structure", + relation="epac.affixiation.unpaired-valence", + couplings=geometry["couplings"], + structure=bad_structure, + ) + with self.assertRaisesRegex(PublicGonolConstructionError, "supplied together"): + construct_public_gonol( + source_id="epac.test:missing-structure", + relation="epac.affixiation.unpaired-valence", + couplings=geometry["couplings"], + ) + + def test_unknown_glyph_fails_closed(self) -> None: + with self.assertRaises(PublicGonolConstructionError): + construct_public_gonol( + source_id="epac.test:bad", + relation="epac.atomic.element", + identity_glyph="He", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/research/epac/tests/test_geometry_comparison_after_construction.py b/research/epac/tests/test_geometry_comparison_after_construction.py new file mode 100644 index 0000000..051e206 --- /dev/null +++ b/research/epac/tests/test_geometry_comparison_after_construction.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import json +import sys +import unittest +from pathlib import Path + +EPAC_ROOT = Path(__file__).resolve().parents[1] +STACK_ROOT = EPAC_ROOT.parents[1] +sys.path.insert(0, str(EPAC_ROOT)) +sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) + +from epac_comparison import compare_after_construction, construction_sources_omit_sealed_labels +from epac_dimensional_arity import charged_structure_readout, topology_structure_readout +from epac_molecular import construct_declared_molecules, matched_information_control + + +SEALED = EPAC_ROOT / "data" / "sealed_known_molecular_geometry.json" + + +class GeometryComparisonAfterConstructionTest(unittest.TestCase): + def test_construction_omits_sealed_shape_labels(self) -> None: + self.assertEqual(construction_sources_omit_sealed_labels(), ()) + + def test_charged_couplings_are_the_three_dimensional_structure(self) -> None: + constructions = construct_declared_molecules() + water = constructions["H2O"].receipt.structure + carbon_dioxide = constructions["CO2"].receipt.structure + self.assertIsNotNone(water) + self.assertIsNotNone(carbon_dioxide) + self.assertEqual(water["participating_dimension_count"], 3) + self.assertEqual(carbon_dioxide["participating_dimension_count"], 3) + self.assertFalse(water["ternary_coupling_declared"]) + self.assertEqual( + topology_structure_readout(water), + topology_structure_readout(carbon_dioxide), + ) + water_charged = charged_structure_readout(water) + co2_charged = charged_structure_readout(carbon_dioxide) + self.assertNotEqual(water_charged, co2_charged) + self.assertEqual( + water_charged[0], + ( + (2, ((8, 1), 1), ("O#2", "H#0")), + (2, ((8, 1), 1), ("O#2", "H#1")), + ), + ) + self.assertEqual( + co2_charged[0], + ( + (2, ((6, 8), 1), ("C#0", "O#1")), + (2, ((6, 8), 1), ("C#0", "O#2")), + ), + ) + + def test_sealed_shape_comparison_uses_charged_structure(self) -> None: + constructions = construct_declared_molecules() + self.assertEqual(set(constructions), {"H2", "H2O", "NH3", "CH4", "CO2"}) + record = compare_after_construction() + sealed = json.loads(SEALED.read_text(encoding="utf-8"))["molecules"] + known_shapes = {formula: sealed[formula]["known_shape"] for formula in constructions} + + self.assertTrue(record["opened_after_construction"]) + self.assertTrue(record["construction_omits_sealed_labels"]) + self.assertEqual(record["known_shapes"], known_shapes) + self.assertGreater(len(set(known_shapes.values())), 1) + self.assertEqual(known_shapes["H2O"], "bent") + self.assertEqual(known_shapes["CO2"], "linear") + self.assertEqual(known_shapes["H2"], "linear") + + self.assertTrue(record["topology_collapses_h2o_with_co2"]) + self.assertTrue(record["charged_distinguishes_h2o_from_co2"]) + self.assertTrue(record["linear_class_split_by_charged_structure"]) + + standings = record["standings"] + self.assertEqual(standings["charged_3_structure_as_sealed_shape_prediction"], "FALSIFIED") + self.assertEqual(standings["topology_3_structure_as_sealed_shape_prediction"], "FALSIFIED") + self.assertEqual(standings["ucns_mobius_as_sealed_shape_prediction"], "FALSIFIED") + self.assertEqual(standings["atomic_shells_as_sealed_shape_prediction"], "FALSIFIED") + + control = {f: matched_information_control(c.invariants) for f, c in constructions.items()} + self.assertNotEqual(control["H2O"], control["CO2"]) + self.assertEqual(len(set(control.values())), len(constructions)) + + +if __name__ == "__main__": + unittest.main() diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py new file mode 100644 index 0000000..fa601d6 --- /dev/null +++ b/research/epac/tests/test_molecular_affixiation.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +EPAC_ROOT = Path(__file__).resolve().parents[1] +STACK_ROOT = EPAC_ROOT.parents[1] +sys.path.insert(0, str(EPAC_ROOT)) +sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) + +from epac_dimensional_arity import quaternion_structure_readout +from epac_molecular import construct_declared_molecules, replay_molecule + + +class MolecularAffixiationTest(unittest.TestCase): + def test_declared_formulas_close_and_replay(self) -> None: + molecules = construct_declared_molecules() + self.assertEqual(set(molecules), {"H2", "H2O", "NH3", "CH4", "CO2"}) + for formula, construction in molecules.items(): + replayed = replay_molecule(construction) + self.assertEqual(construction.receipt.receipt_digest, replayed.receipt_digest, formula) + + def test_unpaired_valence_and_shells_are_used(self) -> None: + molecules = construct_declared_molecules() + water = molecules["H2O"].invariants + methane = molecules["CH4"].invariants + carbon_dioxide = molecules["CO2"].invariants + self.assertEqual(water["center_symbol"], "O") + self.assertEqual(water["center_configuration"], "1s2.2s2.2p4") + self.assertEqual(water["center_unpaired_lm"], ["1:0", "1:-1"]) + self.assertFalse(water["ligand_has_p"]) + self.assertEqual(water["center_used_atomic_promotion"], False) + self.assertEqual(methane["center_used_atomic_promotion"], True) + self.assertEqual(methane["center_unpaired_lm"], ["0:0", "1:-1", "1:1", "1:0"]) + self.assertTrue(carbon_dioxide["ligand_has_p"]) + self.assertEqual(carbon_dioxide["center_unpaired_lm"], ["0:0", "1:-1", "1:1", "1:0"]) + self.assertEqual(carbon_dioxide["center_attachment_site_count"], 4) + self.assertEqual(carbon_dioxide["ligand_attachment_site_count"], 4) + + def test_declared_couplings_are_binary_and_do_not_fill_ambient(self) -> None: + molecules = construct_declared_molecules() + water = molecules["H2O"].invariants["dimensional_geometry"] + self.assertEqual(water["ambient_count"], 3) + self.assertEqual([c["arity"] for c in water["couplings"]], [2, 2]) + ids = [c["declared_ids"] for c in water["couplings"]] + self.assertEqual(len(ids), 2) + self.assertTrue(all(len(item) == 2 for item in ids)) + methane = molecules["CH4"].invariants["dimensional_geometry"] + self.assertEqual(methane["ambient_count"], 5) + self.assertEqual([c["arity"] for c in methane["couplings"]], [2, 2, 2, 2]) + self.assertFalse(any(c["arity"] == 5 for c in methane["couplings"])) + self.assertFalse(methane["inferred_from_ambient"]) + self.assertFalse(methane["inferred_higher_arity_from_overlap"]) + self.assertEqual(water["structure"]["participating_dimension_count"], 3) + self.assertFalse(water["structure"]["ternary_coupling_declared"]) + self.assertFalse(water["structure"]["inferred_cartesian_embedding"]) + self.assertEqual(water["couplings"][0]["slot_charges"], (8, 1)) + self.assertEqual(methane["couplings"][0]["slot_charges"], (6, 1)) + water_receipt = molecules["H2O"].receipt + self.assertEqual(water_receipt.constructor_id, "epac.public_gonol") + self.assertEqual(len(water_receipt.structure["parts"]), 2) + water_instances = molecules["H2O"].invariants["oriented_instance_couplings"] + self.assertEqual(len(water_instances), 2) + self.assertEqual({item[0] for item in water_instances}, {"O#2"}) + self.assertEqual([item[1] for item in water_instances], ["H#0", "H#1"]) + methane_instances = molecules["CH4"].invariants["oriented_instance_couplings"] + self.assertEqual(len(methane_instances), 4) + self.assertTrue(all(item[0] == "C#0" for item in methane_instances)) + self.assertEqual([item[1] for item in methane_instances], ["H#1", "H#2", "H#3", "H#4"]) + self.assertEqual(molecules["H2"].invariants["oriented_instance_couplings"], ()) + water_ids = {name for part in water_receipt.structure["parts"] for name in part["coupling"]} + self.assertEqual(water_ids, {"O#2", "H#0", "H#1"}) + self.assertFalse(any(name.startswith("epac.electron:") for name in water_ids)) + oxygen = next( + item + for item in water_receipt.gonol.participants + if dict(item.carried_options).get("symbol") == "O" + ) + self.assertEqual(len(oxygen.structure["parts"]), 8) + self.assertTrue( + all(part["coupling"][0] == "epac.nucleus:O#2" for part in oxygen.structure["parts"]) + ) + o_nucleus = next(item for item in oxygen.participants if item.relation == "epac.atomic.nucleus") + self.assertEqual( + sum(1 for item in o_nucleus.participants if item.relation == "epac.atomic.neutron"), + 8, + ) + self.assertEqual( + sum(1 for item in o_nucleus.participants if item.relation == "epac.atomic.proton"), + 8, + ) + self.assertFalse(any(name.startswith("epac.neutron:") for name in water_ids)) + self.assertEqual(water_receipt.structure["representation_dimension"], 4) + self.assertEqual(water_receipt.structure["participating_dimension_count"], 3) + self.assertEqual( + quaternion_structure_readout(water_receipt.structure), + (((1, 8, 1, 1), ("O#2", "H#0", "H#1")),), + ) + self.assertEqual( + quaternion_structure_readout(molecules["CO2"].receipt.structure), + (((1, 6, 8, 8), ("C#0", "O#1", "O#2")),), + ) + self.assertEqual(quaternion_structure_readout(molecules["H2"].receipt.structure), ()) + self.assertEqual(len(quaternion_structure_readout(molecules["CH4"].receipt.structure)), 6) + + def test_ucns_coupling_binds_declared_attachments(self) -> None: + molecules = construct_declared_molecules() + signatures = {formula: item.invariants["ucns_coupling_signature"] for formula, item in molecules.items()} + self.assertEqual(len(set(signatures.values())), len(molecules)) + self.assertEqual({signature[0] for signature in signatures.values()}, {"ucns.native-mobius-root-loop"}) + self.assertEqual(len(signatures["CO2"][2]), 4) + self.assertEqual(len(signatures["H2O"][2]), 2) + + def test_construction_text_avoids_sealed_labels(self) -> None: + source = (EPAC_ROOT / "epac_molecular.py").read_text(encoding="utf-8").lower() + for term in ("bent", "tetrahedral", "trigonal-pyramidal", "vsepr", "linear"): + self.assertNotIn(term, source) + + +if __name__ == "__main__": + unittest.main() diff --git a/research/epac/tests/test_periodic_element_gonols.py b/research/epac/tests/test_periodic_element_gonols.py new file mode 100644 index 0000000..a2c80ca --- /dev/null +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -0,0 +1,188 @@ +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +EPAC_ROOT = Path(__file__).resolve().parents[1] +STACK_ROOT = EPAC_ROOT.parents[1] +sys.path.insert(0, str(EPAC_ROOT)) +sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) + +from epac_dimensional_arity import ( + charged_structure_readout, + has_declared_coupling, + quaternion_structure_readout, + space, +) +from epac_periodic import construct_element_gonol, construct_periodic_table, replay_element_gonol + + +class PeriodicElementGonolTest(unittest.TestCase): + def test_constructs_z1_to_z18(self) -> None: + table = construct_periodic_table() + self.assertEqual(len(table), 18) + self.assertEqual(set(table), { + "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", + "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", + }) + carbon = table["C"] + options = dict(carbon.gonol.carried_options) + self.assertEqual(options["Z"], "6") + self.assertEqual(options["electron-configuration"], "1s2.2s2.2p2") + self.assertEqual(options["valence-electrons"], "4") + self.assertEqual(options["unpaired-valence-count"], "2") + self.assertEqual(options["promoted-unpaired-count"], "4") + self.assertEqual(carbon.constructor_id, "epac.public_gonol") + self.assertEqual(len(carbon.gonol.participants), 3) + shells = [item for item in carbon.gonol.participants if item.relation == "epac.atomic.shell"] + electrons = [e for shell in shells for e in shell.participants] + self.assertEqual(len(electrons), 6) + quantum = {(dict(e.carried_options)["n"], dict(e.carried_options)["l"], dict(e.carried_options)["m_l"], dict(e.carried_options)["m_s"]) for e in electrons} + self.assertEqual(len(quantum), 6) + oxygen = table["O"] + self.assertEqual(dict(oxygen.gonol.carried_options)["unpaired-valence-lm"], "1:0,1:-1") + + def test_replay_matches(self) -> None: + first = construct_element_gonol("O") + second = replay_element_gonol(first) + self.assertEqual(first.receipt_digest, second.receipt_digest) + + def test_hund_unpaired_and_shells(self) -> None: + from epac_atomic import atomic_record + + carbon = atomic_record(6) + oxygen = atomic_record(8) + nitrogen = atomic_record(7) + self.assertEqual(len(carbon.electrons), 6) + self.assertEqual(tuple((e.l, e.m_l) for e in carbon.unpaired_valence), ((1, 1), (1, 0))) + self.assertEqual(len(carbon.promoted_unpaired_valence), 4) + self.assertEqual( + len({e.index for e in carbon.promoted_unpaired_valence}), + len(carbon.promoted_unpaired_valence), + ) + self.assertEqual(tuple((e.l, e.m_l) for e in oxygen.unpaired_valence), ((1, 0), (1, -1))) + self.assertEqual(len(nitrogen.unpaired_valence), 3) + self.assertEqual({e.m_l for e in nitrogen.unpaired_valence}, {1, 0, -1}) + + def test_every_electron_instance_has_nucleus_coupling(self) -> None: + oxygen = construct_element_gonol("O") + helium = construct_element_gonol("He") + self.assertIsNotNone(oxygen.structure) + oxygen_readout = charged_structure_readout(oxygen.structure) + self.assertEqual( + oxygen_readout[0], + tuple( + (2, ((8, -1), 1), ("epac.nucleus:O#0", f"epac.electron:O#0:{index}")) + for index in range(8) + ), + ) + nucleus_degree = next( + item for item in oxygen.structure["degree"] if item["dimension"] == "epac.nucleus:O#0" + ) + self.assertEqual(nucleus_degree["degree"], 8) + self.assertEqual(nucleus_degree["charge"], 8) + helium_readout = charged_structure_readout(helium.structure) + self.assertEqual( + helium_readout[0], + ( + (2, ((2, -1), 1), ("epac.nucleus:He#0", "epac.electron:He#0:0")), + (2, ((2, -1), 1), ("epac.nucleus:He#0", "epac.electron:He#0:1")), + ), + ) + ids = {name for part in helium_readout[0] for name in part[2]} + self.assertNotIn("H", ids) + self.assertNotIn("e", ids) + self.assertNotIn("He", ids) + self.assertFalse(helium.structure["ternary_coupling_declared"]) + self.assertEqual(helium.structure["representation_dimension"], 4) + self.assertEqual(helium.structure["participating_dimension_count"], 3) + self.assertEqual( + quaternion_structure_readout(helium.structure), + ( + ( + (1, 2, -1, -1), + ("epac.nucleus:He#0", "epac.electron:He#0:0", "epac.electron:He#0:1"), + ), + ), + ) + hydrogen = construct_element_gonol("H") + self.assertEqual(hydrogen.structure["participating_dimension_count"], 2) + self.assertEqual(hydrogen.structure["representation_dimension"], 4) + self.assertEqual(quaternion_structure_readout(hydrogen.structure), ()) + + def test_nucleus_is_affixiation_of_proton_and_neutron_gonols(self) -> None: + hydrogen = construct_element_gonol("H") + helium = construct_element_gonol("He") + oxygen = construct_element_gonol("O") + h_nucleus = next( + item for item in hydrogen.gonol.participants if item.relation == "epac.atomic.nucleus" + ) + he_nucleus = next( + item for item in helium.gonol.participants if item.relation == "epac.atomic.nucleus" + ) + o_nucleus = next( + item for item in oxygen.gonol.participants if item.relation == "epac.atomic.nucleus" + ) + self.assertEqual([item.relation for item in h_nucleus.participants], ["epac.atomic.proton"]) + self.assertEqual(dict(h_nucleus.carried_options)["neutrons"], "0") + self.assertEqual(h_nucleus.couplings, ()) + self.assertIsNone(h_nucleus.structure) + self.assertEqual( + [item.relation for item in he_nucleus.participants], + [ + "epac.atomic.proton", + "epac.atomic.proton", + "epac.atomic.neutron", + "epac.atomic.neutron", + ], + ) + self.assertEqual(dict(he_nucleus.participants[2].carried_options)["charge"], "0") + self.assertEqual(dict(he_nucleus.participants[0].carried_options)["charge"], "1") + he_ids = {name for part in he_nucleus.structure["parts"] for name in part["coupling"]} + self.assertTrue(all(name.startswith("epac.proton:") or name.startswith("epac.neutron:") for name in he_ids)) + self.assertNotIn("H", he_ids) + self.assertNotIn("e", he_ids) + self.assertFalse(has_declared_coupling( + space( + ["epac.proton:He#0:0", "epac.proton:He#0:1", "epac.neutron:He#0:0", "epac.neutron:He#0:1"], + [part["coupling"] for part in he_nucleus.structure["parts"]], + ), + ["epac.proton:He#0:0", "epac.proton:He#0:1"], + )) + self.assertEqual(len(he_nucleus.structure["parts"]), 4) + self.assertEqual( + quaternion_structure_readout(he_nucleus.structure), + ( + ( + (1, 1, 0, 0), + ("epac.proton:He#0:0", "epac.neutron:He#0:0", "epac.neutron:He#0:1"), + ), + ( + (1, 1, 0, 0), + ("epac.proton:He#0:1", "epac.neutron:He#0:0", "epac.neutron:He#0:1"), + ), + ), + ) + self.assertEqual(len(o_nucleus.participants), 16) + self.assertEqual( + sum(1 for item in o_nucleus.participants if item.relation == "epac.atomic.neutron"), + 8, + ) + electron_ids = { + name + for part in oxygen.structure["parts"] + for name in part["coupling"] + } + self.assertFalse(any(name.startswith("epac.proton:") for name in electron_ids)) + self.assertFalse(any(name.startswith("epac.neutron:") for name in electron_ids)) + + def test_construction_does_not_carry_shape_labels(self) -> None: + receipt = construct_element_gonol("N") + blob = str(receipt.gonol.carried_options) + receipt.gonol.relation + for term in ("bent", "tetrahedral", "trigonal-pyramidal", "vsepr"): + self.assertNotIn(term, blob.lower()) + + +if __name__ == "__main__": + unittest.main()