From c6bca323700af48b5f3c24e07c5870f19b742ba0 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 11:51:37 +0000 Subject: [PATCH 01/21] Construct element gonols and test molecular affixiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recognize stack/research/epac as the research surface for energy particle affixiation coupling. Close Z=1-18 element gonols from atomic structure only, affixiate H2, H2O, NH3, CH4, and CO2 through valence arity and implemented UCNS Möbius coupling, and compare to sealed known chemistry only after construction. --- research/epac/README.md | 21 ++ research/epac/data/periodic_table_z1_18.json | 31 +++ .../data/sealed_known_molecular_geometry.json | 12 ++ ...-molecular-geometry-from-element-gonols.md | 99 ++++++++++ research/epac/epac_molecular.py | 157 +++++++++++++++ research/epac/epac_periodic.py | 98 +++++++++ .../subatomic-affixiation-baseline.md | 186 ++++++++++++++++++ ..._geometry_comparison_after_construction.py | 61 ++++++ .../epac/tests/test_molecular_affixiation.py | 51 +++++ .../tests/test_periodic_element_gonols.py | 43 ++++ 10 files changed, 759 insertions(+) create mode 100644 research/epac/data/periodic_table_z1_18.json create mode 100644 research/epac/data/sealed_known_molecular_geometry.json create mode 100644 research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md create mode 100644 research/epac/epac_molecular.py create mode 100644 research/epac/epac_periodic.py create mode 100644 research/epac/subatomic/subatomic-affixiation-baseline.md create mode 100644 research/epac/tests/test_geometry_comparison_after_construction.py create mode 100644 research/epac/tests/test_molecular_affixiation.py create mode 100644 research/epac/tests/test_periodic_element_gonols.py diff --git a/research/epac/README.md b/research/epac/README.md index 9a1d902..2b4b855 100644 --- a/research/epac/README.md +++ b/research/epac/README.md @@ -5,3 +5,24 @@ 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. +- [`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 valence arity and UCNS Möbius + coupling. Known chemistry is sealed until after construction. + +## Usage + +From this directory: + +```bash +PYTHONPATH=".:../edcm:../ucns/src" python3 -m unittest discover -s tests -q +``` + +Do not open `data/sealed_known_molecular_geometry.json` during construction. 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/preregistration-molecular-geometry-from-element-gonols.md b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md new file mode 100644 index 0000000..024f823 --- /dev/null +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -0,0 +1,99 @@ +# Preregistration: molecular geometry from element gonols + +- Status: **CROSS-DOMAIN-HYPOTHESIS / provisional research candidate** +- Owner of record: `The-Interdependency/stack` → `research/epac/` +- Constructor: `edcm.gonol` from the pinned EDCM snapshot +- 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 +element gonols + -> valence arity + -> affixiation + -> UCNS coupling geometry (native Möbius only) + -> molecular gonol + -> construction invariants + -> (only then) compare to sealed known chemistry +``` + +## Inputs allowed in construction + +- atomic number Z +- element symbol +- period, IUPAC group +- ground-state electron configuration +- typical main-group valence (hydride valence) +- 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 UCNS coupling invariants distinguish formulas that chemistry + distinguishes by shape. +5. Matched-information control: atom count plus valence/stoichiometry without + Möbius. If the construction's distinguishing power equals that control, UCNS + coupling did not add shape information. + +## 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 UCNS coupling invariants distinguish + sealed shape classes after subtracting the matched-information control. +- `FALSIFIED` as prediction — if those invariants are identical across sealed + shape classes, 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_molecular.py b/research/epac/epac_molecular.py new file mode 100644 index 0000000..01ac1e4 --- /dev/null +++ b/research/epac/epac_molecular.py @@ -0,0 +1,157 @@ +"""Molecular gonols by affixiation of element gonols plus UCNS Möbius coupling. + +Usage guidance +-------------- +Construction consumes element gonols, typical valence, and a stoichiometric +formula. It applies only the implemented UCNS Möbius root loop. It does not +open the sealed comparison file. + + from epac_molecular import construct_molecule + + water = construct_molecule("H2O") + print(water.invariants) +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Mapping + +from edcm.gonol import ClosedGonol, GonolReceipt, construct_gonol, replay_gonol +from ucns.direct_mobius import native_mobius_state + +from epac_periodic import construct_element_gonol, symbol_of, typical_valence_of + + +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.valence-coupling" +SCALE = "recursive" + + +@dataclass(frozen=True, slots=True) +class MolecularConstruction: + formula: str + receipt: GonolReceipt + invariants: Mapping[str, Any] + + +def _instantiate(composition: tuple[tuple[str, int], ...]) -> tuple[ClosedGonol, ...]: + instances: list[ClosedGonol] = [] + 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 _choose_center(participants: tuple[ClosedGonol, ...]) -> ClosedGonol | None: + ranked = sorted( + participants, + key=lambda item: (typical_valence_of(item), symbol_of(item)), + reverse=True, + ) + top = ranked[0] + if typical_valence_of(top) <= 0: + return None + if len(ranked) >= 2 and typical_valence_of(ranked[0]) == typical_valence_of(ranked[1]): + return None + return top + + +def _slot_occupancy(center: ClosedGonol, ligands: tuple[ClosedGonol, ...]) -> tuple[int, ...]: + valence = typical_valence_of(center) + if not ligands: + raise ValueError("affixiation requires ligands when a center exists") + if valence % len(ligands) != 0: + raise ValueError("valence arity does not divide ligand count") + occupancy = valence // len(ligands) + return tuple(occupancy for _ in ligands) + + +def _mobius_coupling() -> Mapping[str, Any]: + origin = native_mobius_state(0) + one = origin.advance(1) + two = origin.advance(2) + return { + "law": "ucns.native-mobius-root-loop", + "parameter": "turn-index", + "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: + """Affixiate element gonols for one declared formula.""" + + 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: + occupancy: tuple[int, ...] = () + ligands: tuple[ClosedGonol, ...] = () + if len(participants) != 2: + raise ValueError("symmetric affixiation is declared only for two equal participants") + else: + ligands = tuple(item for item in participants if item is not center) + occupancy = _slot_occupancy(center, ligands) + receipt = construct_gonol( + scale=SCALE, + source_id=f"epac.molecule:{formula}", + participants=participants, + relation=RELATION, + geometry_authority=__import__("ucns.public_gonol", fromlist=["public_gonol"]), + ) + mobius = _mobius_coupling() + invariants = { + "formula": formula, + "atom_count": len(participants), + "center_symbol": None if center is None else symbol_of(center), + "center_typical_valence": None if center is None else typical_valence_of(center), + "ligand_symbols": [symbol_of(item) for item in ligands] if center is not None else [], + "slot_occupancy": list(occupancy), + "participant_symbols": [symbol_of(item) for item in participants], + "mobius": mobius, + "ucns_coupling_signature": ( + mobius["law"], + tuple(mobius["t"]), + tuple(mobius["frame"]), + mobius["complete_restored"], + ), + } + return MolecularConstruction(formula=formula, receipt=receipt, invariants=invariants) + + +def replay_molecule(construction: MolecularConstruction) -> GonolReceipt: + return replay_gonol(receipt=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 that uses only formula composition and valence occupancy.""" + + return ( + invariants["atom_count"], + invariants["center_symbol"], + invariants["center_typical_valence"], + tuple(invariants["ligand_symbols"]), + tuple(invariants["slot_occupancy"]), + ) diff --git a/research/epac/epac_periodic.py b/research/epac/epac_periodic.py new file mode 100644 index 0000000..63bbad3 --- /dev/null +++ b/research/epac/epac_periodic.py @@ -0,0 +1,98 @@ +"""Element gonols for Z=1-18 from atomic structure only. + +Usage guidance +-------------- +This candidate closes one gonol per periodic-table element using the pinned +``edcm.gonol`` constructor. Carried options are Z, configuration, and typical +valence. Do not load the sealed comparison file from this module. + + from epac_periodic import construct_element_gonol, construct_periodic_table + + carbon = construct_element_gonol("C") + table = construct_periodic_table() + assert "C" in table +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, Mapping + +from edcm.gonol import ClosedGonol, GonolReceipt, construct_gonol, replay_gonol + + +ROOT = Path(__file__).resolve().parent +TABLE_PATH = ROOT / "data" / "periodic_table_z1_18.json" +RELATION = "epac.periodic.element" +SCALE = "word" + + +def load_atomic_table() -> tuple[dict[str, Mapping[str, Any]], ...]: + payload = json.loads(TABLE_PATH.read_text(encoding="utf-8")) + elements = tuple(payload["elements"]) + if len(elements) != 18: + raise RuntimeError("periodic table candidate requires Z=1-18") + return elements + + +def _geometry_authority() -> Any: + from ucns import public_gonol + + return public_gonol + + +def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt: + """Close one element gonol from the frozen atomic-structure table.""" + + record = None + for element in load_atomic_table(): + if element["symbol"] == symbol: + record = element + break + if record is None: + raise ValueError(f"no atomic-structure record for symbol {symbol!r}") + carried = ( + ("Z", str(record["Z"])), + ("period", str(record["period"])), + ("group", str(record["group"])), + ("electron-configuration", str(record["electron_configuration"])), + ("valence-electrons", str(record["valence_electrons"])), + ("typical-valence", str(record["typical_valence"])), + ) + return construct_gonol( + scale=SCALE, + source=symbol, + source_id=f"epac.periodic:{symbol}#{occurrence}", + relation=None, + carried_options=carried, + geometry_authority=_geometry_authority(), + occurrence=occurrence, + ) + + +def construct_periodic_table() -> dict[str, GonolReceipt]: + """Close element gonols for every Z=1-18 record.""" + + table: dict[str, GonolReceipt] = {} + for element in load_atomic_table(): + symbol = str(element["symbol"]) + table[symbol] = construct_element_gonol(symbol) + return table + + +def replay_element_gonol(receipt: GonolReceipt) -> GonolReceipt: + return replay_gonol(receipt=receipt) + + +def typical_valence_of(gonol: ClosedGonol) -> int: + for key, value in gonol.carried_options: + if key == "typical-valence": + return int(value) + raise KeyError("typical-valence is missing from element gonol") + + +def symbol_of(gonol: ClosedGonol) -> str: + if gonol.source_units: + return "".join(gonol.source_units) + raise KeyError("element gonol has no symbol source units") diff --git a/research/epac/subatomic/subatomic-affixiation-baseline.md b/research/epac/subatomic/subatomic-affixiation-baseline.md new file mode 100644 index 0000000..74c87d9 --- /dev/null +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -0,0 +1,186 @@ +# 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. 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..fa3f01f --- /dev/null +++ b/research/epac/tests/test_geometry_comparison_after_construction.py @@ -0,0 +1,61 @@ +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" / "edcm")) +sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) + +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_ucns_coupling_does_not_predict_sealed_shapes(self) -> None: + constructions = construct_declared_molecules() + sealed = json.loads(SEALED.read_text(encoding="utf-8"))["molecules"] + + ucns_signatures = { + formula: item.invariants["ucns_coupling_signature"] + for formula, item in constructions.items() + } + controls = { + formula: matched_information_control(item.invariants) + for formula, item in constructions.items() + } + known_shapes = {formula: sealed[formula]["known_shape"] for formula in constructions} + + # Construction finished. Comparison opens sealed labels only now. + distinct_shapes = set(known_shapes.values()) + distinct_ucns = set(ucns_signatures.values()) + distinct_controls = set(controls.values()) + + water_co2_same_ucns = ucns_signatures["H2O"] == ucns_signatures["CO2"] + water_co2_same_shape = known_shapes["H2O"] == known_shapes["CO2"] + water_co2_same_control = controls["H2O"] == controls["CO2"] + + self.assertGreater(len(distinct_shapes), 1) + self.assertEqual(len(distinct_ucns), 1) + self.assertTrue(water_co2_same_ucns) + self.assertFalse(water_co2_same_shape) + self.assertFalse(water_co2_same_control) + self.assertGreater(len(distinct_controls), 1) + + standing = ( + "FALSIFIED-as-prediction" + if len(distinct_ucns) == 1 and len(distinct_shapes) > 1 + else "UNRESOLVED" + ) + self.assertEqual(standing, "FALSIFIED-as-prediction") + self.assertEqual(len(distinct_controls), 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..ee14925 --- /dev/null +++ b/research/epac/tests/test_molecular_affixiation.py @@ -0,0 +1,51 @@ +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" / "edcm")) +sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) + +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) + self.assertEqual(construction.receipt.gonol.participants[0].scale, "word") + + def test_valence_occupancy_from_inputs_only(self) -> None: + molecules = construct_declared_molecules() + self.assertEqual(molecules["H2"].invariants["center_symbol"], None) + self.assertEqual(molecules["H2O"].invariants["center_symbol"], "O") + self.assertEqual(molecules["H2O"].invariants["slot_occupancy"], [1, 1]) + self.assertEqual(molecules["NH3"].invariants["slot_occupancy"], [1, 1, 1]) + self.assertEqual(molecules["CH4"].invariants["slot_occupancy"], [1, 1, 1, 1]) + self.assertEqual(molecules["CO2"].invariants["slot_occupancy"], [2, 2]) + + def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: + molecules = construct_declared_molecules() + signatures = {formula: item.invariants["ucns_coupling_signature"] for formula, item in molecules.items()} + unique = set(signatures.values()) + self.assertEqual(len(unique), 1) + law, turns, _frames, restored = next(iter(unique)) + self.assertEqual(law, "ucns.native-mobius-root-loop") + self.assertEqual(turns, (0, 1, 2)) + self.assertTrue(restored) + + 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"): + 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..dc4e97c --- /dev/null +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -0,0 +1,43 @@ +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" / "edcm")) +sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) + +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["typical-valence"], "4") + + 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_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() From 438922bf5a07a627dad1752f46db4423a51788f1 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 12:04:54 +0000 Subject: [PATCH 02/21] Encode full atomic shells in element gonols Close each Z=1-18 element from nucleus plus every electron's n, l, m_l, m_s, hydrogenic angular id, Slater Z_eff, and Rydberg energy. Molecular affixiation uses unpaired valence (l, m_l) and atomic s-to-p promotion, not a scalar typical-valence shortcut. --- ...-molecular-geometry-from-element-gonols.md | 17 +- research/epac/epac_atomic.py | 274 ++++++++++++++++++ research/epac/epac_molecular.py | 118 +++++--- research/epac/epac_periodic.py | 168 +++++++---- ...parison_after_construction.cpython-312.pyc | Bin 0 -> 4252 bytes ...test_molecular_affixiation.cpython-312.pyc | Bin 0 -> 4375 bytes ...st_periodic_element_gonols.cpython-312.pyc | Bin 0 -> 4755 bytes ..._geometry_comparison_after_construction.py | 56 ++-- .../epac/tests/test_molecular_affixiation.py | 28 +- .../tests/test_periodic_element_gonols.py | 25 +- 10 files changed, 534 insertions(+), 152 deletions(-) create mode 100644 research/epac/epac_atomic.py create mode 100644 research/epac/tests/__pycache__/test_geometry_comparison_after_construction.cpython-312.pyc create mode 100644 research/epac/tests/__pycache__/test_molecular_affixiation.cpython-312.pyc create mode 100644 research/epac/tests/__pycache__/test_periodic_element_gonols.cpython-312.pyc diff --git a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md index 024f823..7dd6982 100644 --- a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -35,11 +35,12 @@ element gonols ## Inputs allowed in construction -- atomic number Z -- element symbol -- period, IUPAC group -- ground-state electron configuration -- typical main-group valence (hydride valence) +- atomic number Z, default isotope A, proton and neutron counts +- 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 @@ -74,9 +75,9 @@ Opened only after molecular gonols exist: 3. Open `data/sealed_known_molecular_geometry.json`. 4. Ask whether UCNS coupling invariants distinguish formulas that chemistry distinguishes by shape. -5. Matched-information control: atom count plus valence/stoichiometry without - Möbius. If the construction's distinguishing power equals that control, UCNS - coupling did not add shape information. +5. Compare three signatures: UCNS Möbius coupling; atomic unpaired (l, m_l) + plus ligand shell content; and a matched-information control of formula + symbols only. ## Terminal standings for the hmmm question diff --git a/research/epac/epac_atomic.py b/research/epac/epac_atomic.py new file mode 100644 index 0000000..da265be --- /dev/null +++ b/research/epac/epac_atomic.py @@ -0,0 +1,274 @@ +"""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 = [e for e in valence if e.l == 0 and e.paired and e.m_s == 1] + if not s_pairs or not empty_p_m: + return tuple(unpaired) + # Promote one valence s electron into the first empty p m, unpairing s and adding p. + promoted_from_s = s_pairs[0] + new_p = ElectronState( + index=promoted_from_s.index, + n=valence_n, + l=1, + m_l=empty_p_m[0], + m_s=1, + 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=promoted_from_s.index, + n=promoted_from_s.n, + l=0, + m_l=0, + m_s=1, + shell=promoted_from_s.shell, + subshell=promoted_from_s.subshell, + angular_id=promoted_from_s.angular_id, + radial_nodes=promoted_from_s.radial_nodes, + z_eff=promoted_from_s.z_eff, + e_rydberg=promoted_from_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_molecular.py b/research/epac/epac_molecular.py index 01ac1e4..062ee6d 100644 --- a/research/epac/epac_molecular.py +++ b/research/epac/epac_molecular.py @@ -1,15 +1,9 @@ -"""Molecular gonols by affixiation of element gonols plus UCNS Möbius coupling. +"""Molecular gonols from atomic electron-shell gonols plus UCNS Möbius coupling. -Usage guidance --------------- -Construction consumes element gonols, typical valence, and a stoichiometric -formula. It applies only the implemented UCNS Möbius root loop. It does not -open the sealed comparison file. - - from epac_molecular import construct_molecule - - water = construct_molecule("H2O") - print(water.invariants) +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. No sealed molecular-shape file is opened here. """ from __future__ import annotations @@ -20,7 +14,8 @@ from edcm.gonol import ClosedGonol, GonolReceipt, construct_gonol, replay_gonol from ucns.direct_mobius import native_mobius_state -from epac_periodic import construct_element_gonol, symbol_of, typical_valence_of +from epac_atomic import AtomicRecord, atomic_record +from epac_periodic import atomic_of, carried, construct_element_gonol, symbol_of MOLECULE_COMPOSITIONS: Mapping[str, tuple[tuple[str, int], ...]] = { @@ -31,7 +26,7 @@ "CO2": (("C", 1), ("O", 2)), } -RELATION = "epac.affixiation.valence-coupling" +RELATION = "epac.affixiation.unpaired-valence" SCALE = "recursive" @@ -52,28 +47,37 @@ def _instantiate(composition: tuple[tuple[str, int], ...]) -> tuple[ClosedGonol, return tuple(instances) -def _choose_center(participants: tuple[ClosedGonol, ...]) -> ClosedGonol | None: - ranked = sorted( - participants, - key=lambda item: (typical_valence_of(item), symbol_of(item)), - reverse=True, - ) - top = ranked[0] - if typical_valence_of(top) <= 0: - return None - if len(ranked) >= 2 and typical_valence_of(ranked[0]) == typical_valence_of(ranked[1]): - return None - return top +def _record_for(gonol: ClosedGonol) -> AtomicRecord: + return atomic_of(symbol_of(gonol)) -def _slot_occupancy(center: ClosedGonol, ligands: tuple[ClosedGonol, ...]) -> tuple[int, ...]: - valence = typical_valence_of(center) - if not ligands: - raise ValueError("affixiation requires ligands when a center exists") - if valence % len(ligands) != 0: - raise ValueError("valence arity does not divide ligand count") - occupancy = valence // len(ligands) - return tuple(occupancy for _ in ligands) +def _choose_center(participants: tuple[ClosedGonol, ...]) -> ClosedGonol | 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 _mobius_coupling() -> Mapping[str, Any]: @@ -96,20 +100,31 @@ def _mobius_coupling() -> Mapping[str, Any]: def construct_molecule(formula: str) -> MolecularConstruction: - """Affixiate element gonols for one declared formula.""" - 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: - occupancy: tuple[int, ...] = () - ligands: tuple[ClosedGonol, ...] = () + ligands = () + center_sites: tuple[tuple[int, int], ...] = () if len(participants) != 2: - raise ValueError("symmetric affixiation is declared only for two equal participants") + 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) - occupancy = _slot_occupancy(center, ligands) + needed = len(ligands) + center_record = _record_for(center) + ground = tuple((e.l, e.m_l) for e in center_record.unpaired_valence) + used_promotion = needed > len(ground) + center_sites = _attachment_set(center_record, needed) + ligand_sites = tuple( + tuple((e.l, e.m_l) for e in _record_for(item).unpaired_valence) for item in ligands + ) receipt = construct_gonol( scale=SCALE, source_id=f"epac.molecule:{formula}", @@ -118,14 +133,29 @@ def construct_molecule(formula: str) -> MolecularConstruction: geometry_authority=__import__("ucns.public_gonol", fromlist=["public_gonol"]), ) mobius = _mobius_coupling() + 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_typical_valence": None if center is None else typical_valence_of(center), - "ligand_symbols": [symbol_of(item) for item in ligands] if center is not None else [], - "slot_occupancy": list(occupancy), + "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_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"], @@ -146,12 +176,10 @@ def construct_declared_molecules() -> dict[str, MolecularConstruction]: def matched_information_control(invariants: Mapping[str, Any]) -> tuple[Any, ...]: - """Control that uses only formula composition and valence occupancy.""" + """Control: stoichiometric symbols only, no shells or wave identities.""" return ( invariants["atom_count"], invariants["center_symbol"], - invariants["center_typical_valence"], tuple(invariants["ligand_symbols"]), - tuple(invariants["slot_occupancy"]), ) diff --git a/research/epac/epac_periodic.py b/research/epac/epac_periodic.py index 63bbad3..9895086 100644 --- a/research/epac/epac_periodic.py +++ b/research/epac/epac_periodic.py @@ -1,39 +1,24 @@ -"""Element gonols for Z=1-18 from atomic structure only. +"""Element gonols closed from full atomic electron-shell structure. Usage guidance -------------- -This candidate closes one gonol per periodic-table element using the pinned -``edcm.gonol`` constructor. Carried options are Z, configuration, and typical -valence. Do not load the sealed comparison file from this module. +Each electron is a closed gonol with (n, l, m_l, m_s), shell, subshell, +hydrogenic angular id, radial nodes, Slater Z_eff, and Rydberg energy. +Electrons of one n affixiate as a shell. Shells plus a nucleus gonol +affixiate as the element. Nothing molecular is encoded. from epac_periodic import construct_element_gonol, construct_periodic_table - carbon = construct_element_gonol("C") - table = construct_periodic_table() - assert "C" in table + oxygen = construct_element_gonol("O") """ from __future__ import annotations -import json -from pathlib import Path -from typing import Any, Mapping +from typing import Any, Iterable from edcm.gonol import ClosedGonol, GonolReceipt, construct_gonol, replay_gonol - -ROOT = Path(__file__).resolve().parent -TABLE_PATH = ROOT / "data" / "periodic_table_z1_18.json" -RELATION = "epac.periodic.element" -SCALE = "word" - - -def load_atomic_table() -> tuple[dict[str, Mapping[str, Any]], ...]: - payload = json.loads(TABLE_PATH.read_text(encoding="utf-8")) - elements = tuple(payload["elements"]) - if len(elements) != 18: - raise RuntimeError("periodic table candidate requires Z=1-18") - return elements +from epac_atomic import AtomicRecord, ElectronState, atomic_record, iter_table def _geometry_authority() -> Any: @@ -42,29 +27,111 @@ def _geometry_authority() -> Any: return public_gonol +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) -> ClosedGonol: + receipt = construct_gonol( + scale="word", + source="e", + source_id=f"epac.electron:{symbol}#{atom_occurrence}:{electron.index}", + carried_options=_electron_options(electron), + geometry_authority=_geometry_authority(), + occurrence=electron.index, + ) + return receipt.gonol + + +def _construct_shell( + n: int, + electrons: Iterable[ElectronState], + *, + symbol: str, + atom_occurrence: int, +) -> ClosedGonol: + members = tuple(_construct_electron(e, symbol=symbol, atom_occurrence=atom_occurrence) for e in electrons) + return construct_gonol( + scale="word", + source=f"n{n}", + source_id=f"epac.shell:{symbol}#{atom_occurrence}:n{n}", + participants=members, + relation="epac.atomic.shell", + geometry_authority=_geometry_authority(), + occurrence=n, + ).gonol + + +def _construct_nucleus(record: AtomicRecord, *, atom_occurrence: int) -> ClosedGonol: + return construct_gonol( + scale="word", + source="nuc", + source_id=f"epac.nucleus:{record.symbol}#{atom_occurrence}", + carried_options=( + ("Z", str(record.Z)), + ("A", str(record.A)), + ("protons", str(record.proton_count)), + ("neutrons", str(record.neutron_count)), + ), + geometry_authority=_geometry_authority(), + occurrence=0, + ).gonol + + def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt: - """Close one element gonol from the frozen atomic-structure table.""" + """Close one element gonol whose participants are nucleus + electron shells.""" record = None - for element in load_atomic_table(): - if element["symbol"] == symbol: - record = element + for item in iter_table(): + if item.symbol == symbol: + record = item break if record is None: - raise ValueError(f"no atomic-structure record for symbol {symbol!r}") + raise ValueError(f"no atomic record for symbol {symbol!r}") + shells: list[ClosedGonol] = [] + 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 = ( - ("Z", str(record["Z"])), - ("period", str(record["period"])), - ("group", str(record["group"])), - ("electron-configuration", str(record["electron_configuration"])), - ("valence-electrons", str(record["valence_electrons"])), - ("typical-valence", str(record["typical_valence"])), + ("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)), ) return construct_gonol( - scale=SCALE, + scale="word", source=symbol, source_id=f"epac.periodic:{symbol}#{occurrence}", - relation=None, + participants=(nucleus, *shells), + relation="epac.atomic.element", carried_options=carried, geometry_authority=_geometry_authority(), occurrence=occurrence, @@ -72,27 +139,26 @@ def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt def construct_periodic_table() -> dict[str, GonolReceipt]: - """Close element gonols for every Z=1-18 record.""" - - table: dict[str, GonolReceipt] = {} - for element in load_atomic_table(): - symbol = str(element["symbol"]) - table[symbol] = construct_element_gonol(symbol) - return table + return {record.symbol: construct_element_gonol(record.symbol) for record in iter_table()} def replay_element_gonol(receipt: GonolReceipt) -> GonolReceipt: return replay_gonol(receipt=receipt) -def typical_valence_of(gonol: ClosedGonol) -> int: - for key, value in gonol.carried_options: - if key == "typical-valence": - return int(value) - raise KeyError("typical-valence is missing from element gonol") +def atomic_of(symbol: str) -> AtomicRecord: + for record in iter_table(): + if record.symbol == symbol: + return record + raise ValueError(symbol) def symbol_of(gonol: ClosedGonol) -> str: - if gonol.source_units: - return "".join(gonol.source_units) - raise KeyError("element gonol has no symbol source units") + return "".join(gonol.source_units) + + +def carried(gonol: ClosedGonol, 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/tests/__pycache__/test_geometry_comparison_after_construction.cpython-312.pyc b/research/epac/tests/__pycache__/test_geometry_comparison_after_construction.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8ff9092aaa029075a6d3c7f04fcb4a24ba185c4 GIT binary patch literal 4252 zcmb_fU2GHC6~5yckH>#;oRGx%asFVtjTGVy1W4GBG)jVD^S4`~Ewu>K!E=+?jK^VS z3{8|sAW@N3D-pC0uo4f6s6-HbNTrpURgoIn$G&(vZXI2ArCsq*ZJ*G>3q1AS8BY?x zprG9=`Tm~s-E)r5J?G3n{eBO??Zc(dlK{b6FGW=?e;gn?M92C4m$bqbM?W zNRF7p9O)Qsj!cZfBP}^oY>cIV13(tK?qgokl0j?S+21cLl4U!0t+~il<4Mc|J#RVn zLOL0Goi|zg`kqMSDS;kap8F1qh63-5;T4^8UI0Xv^gkh<%S7dhqhiatr9i4QR+>&g=rQTNq>q(3Kop3+t3dQaCi%11^}kPBd$B#RLM%D|a{v^~gK1lf&=$5RFzc9g zv;@8<>lO-m1zApOf|f|j>L^|_9}C*__v8;ooB~xSDDi2;Krx;|OeQX?h*p(&iv!ig zF@vUL;uhfTf!9Q z<@6=lW)&2E3Z_#~Q)b(eYFdsLq&5fp-+j6VFK+?}Vw4E5ztSRgm5I?W?(h8==PS2P;FLfGg zFOzP2wmpi}pB!5*RlpW$@)Q;v^V9&iPRAVy5O+-AKYf9or{^8>=Bf2uK6m`V&wvF6 zpHN9m@oKYW#5m6DY@9ZcDgZcx&9&fMVCS9lY|{5C7j|_gkpQDtVRV;C`P1UFJMg)8 z#K(_)TYfEIOJ7_Q+qPJjFxJIw-c>X>Y2`MOhAm{Zc_vw9&$mb0sy4PgimvS-GY^tP z#aCLnG$mQwh)Kwq+E^a8u!eDl1;J38mgXp{Rp zasK0=98Zf0c?!9=)0x*wQtGrY15p)8gE*OGW+Y6;tBEOD&@u`{)q<8zCE`Vn6r5K8 zqE8$cLv;Vx0pybsQ-Un=(}K#+w3I5OUPBB?c*rTG1yM!pslmSC!2#q}pdj)Z{H=zZ z2@R%HO;t!bKz>10p`t}GB~EmRmY|H2G}I7#QIIk?8G9{)!3!BdLM7H> zG_6@{kF_3GGSDVGE=VeD@gYWq(j@Y4C3;dKAl6FT$VKvcMiP*3hiz0uO(H6;kc5g@ z%N)pM=lycia5K14NW?Xjx2#!FFd8J%5wn^#f=Uur9B>_$IqR;0014kgKhN2J6{2 zGmAbTT>PbJLE{TDs?$)yG76%|UxbR9QTdl#Vea%Ui;uQcQbfc602^f2t-nqPJYjm8-6mhPANX z)T@VjvsDv|KHPm99nVK&dNgK4&*TFWUuo+XHky{GrS_ZMH@dTpU3&RZz4T}{FtNz! zfr-tb)A^yZ`p{Wp=r{SG@UUZ}eM4AkTKeSXnHy)a&ByeL9=)t58x$5%+V;Eac11JsjBR+qkgQo~vwMYx+y;pIeQ}UcJ0GTXt^IrI(%imh0Ib``R!B7d+ye*)ZPgj;fPWF!CF&}>oCu2 zb*}c-@M?u#7g?)YhkE-!jvL%Qi7pR*F}fOE8@xA~<38NpKfZkEi;>mCYwz7VmgBnc z{`J6Tfm{A%sE1nfp(A?e$a;w$isrZzCJ&L~R(Lt1SMSePck0!h>-Bo|i5z$G84DVk z|G0NqynBA-{GF81++oxodQkr0)J9#tZ(Q#iH~Ky`j{VB$I<42A&T_R|XxZx49Jl`& z2b%URAHO@YGID3kXzX}EuSfqj`qik>J#2K2=#3*;F1&-KEyqQ+mZnuI?}k^xcN&av zyHS1M%l>u0(Q!P-MPDYWx?8_eZ#1ddN zUw`X@?r*y6*#z`8?h1D; za3!!r-8i^3`*}|eG{3q#xBQV_)wWitR~<3RJ9D7xEd=ZD=~cZ(`7d&y@4sOD2@5>s zOP}iQ+HC!R?jE?zK4WXClho>TzO7ep>wN~u>Pa_Xe2a7cqN_KV;N=t7My`zfZtMvS Jm>Sar{|C0q!vz2U literal 0 HcmV?d00001 diff --git a/research/epac/tests/__pycache__/test_molecular_affixiation.cpython-312.pyc b/research/epac/tests/__pycache__/test_molecular_affixiation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7be26fe5deb3e131808753dd75b5e2349dd60da GIT binary patch literal 4375 zcmb_fOKcm*8J>MnON!Lfde9Ha670mZY`y%5>o|^O`5`+sl{RV$D-IT`-4V6)a+jW6 z+9o0S0l2V&7_f>QtfEG%wuiQG4?g;+V~bq0rVI+(=At>+8eU{u4MCcp(Q7SclhgtnQgU}QhL&W#-&WR7iXX{_%2B_s@=@rF2XXKXvmwkTTmN%y zk}h3WRBBHI2TS%mVSNDx{m(i=s*tNiNDa{X|ERCV(<*MP6>c`D*QM&m>r)Nm4fJ-; zBQqpccCgpxO1Ch>OrvD+_PiCv$`(pr z(@g2rr@$9}$CR-&%_NK7Qj(=@N%I->=@3F!;EXfu3}=I%`DL0b>6_s<-g}x$dj45j z;kU_f^*8;R<~JN=OL$<=gJB+JQH+hBqz7`@kwKRm9ytuvDD*}KM_n#Hawt|q=#GE^ z%fiGydo3gD@V?k~1qI04$0m3J0uO)STV4yq5Z=%oOgsA!jX&iKH1eCMr&2L&b&-qmoGyeSoz-%a)a^ zeI+dR;k2yu*|5Lm)g_3@M3T#h=uKzcx2aGWoJGqlNzqLUOR}L#zE!zXn_;SViy9i? zP}Wtb?x3%>_2;(@&IVV+a6xQ$#P-L{^Ot|w@jUjyviQT5y}iGeek0}g9{nD%L6vdB zU9;!su6)(@*1~kY?eOf#O1PmA-syyQ&R;3)8gOS2%syIemHQlZh8$ z3JvzcUoYFBHvASrs1K{b?MtE4w@OR%7pN|&6EL7Q3{_OwQ-G;VYzPW2`0 zwjk+Qnm}s(Zz~A|CD&-7;;#5DTFR^b`5V+=<@v$g8{?!26cQS02?eDaQNWqNyR2+# z!qi=6)ZJDb0soxT5*gwVXs&j^8tfgUzw}|ZsVI^$(y~T*T~EE{29Ec?<_C@+9B{+B zmZ07c)2|oLT zG~>EVG}*8%vW*&Dq@#1PZecg%bJG~fU|2w(h-{}q!VMV?=GLUJJtZ61ty5$&VH&_q z=1on-WuhS+R6ORn^4f{6%Q`j`p9QQbtm~Ep5lo_o$en&O9c)EMJ8WqoaKj2hXo$}5 zbsBaT8hV_Do>^g~X~+Cor>VQpbkJ!!I9sz4?Or(IL}P{MVJCWcwr-6;=3C}(JJG(U z&CBAEiovB$kUsh>vMipf8a)K7pJtv-J~gXn=5IOC-es|`O87k|dU8>y5^P)Qdf$nj zTHLcNo-fP7DBbS)v7ao9AH(p&`p@d;>lSV~?MIizcgk`dOZ!eZ(f1aG#muu?FT^<6 z4L4E6QX&j^EcH+5Y{pU>h30aAeaJrK6i!15`)Nx#)K0SyKzUHQuK0}i1^b|lM>9MP z%D?~{Fhx03+cfNX1p;lP)GxOsOG=zlYSjuh%7S$aQn(pzhP{DeT-gkpORR#8?se)uUl989vg_di7N&Pvo z^wUq~67%-2Z~x-TI$eYLuEY6VNAdu9 z=7o6tU#lEyC5(60Uu_@i4OC%lEfU@XGXP^y`$c7F;=K{NRQ6A63ED=Htn}tHjLJib z2d@M1azh*e4lB6+L93UD3GrMbO6Mo;FFoZfdF}!M_2Wh68y5 z>sIc}o58X*E%YydH4GJ?rD?}f$AHr`P-r~iG@e*YEH$3UH^vK%V@~5(zVYMPz{<9Z z-yLS7@v)aK4@4gCg zf^UW=N;gi)njuNC7P|FhhB~|?5qjy6XlE@~%ov(YZ{4j)!yhkQn;<-$gq4$;XC|cI zmZ@LIRDb|9EOL?xggzP@8vek4cb+bF1DZjzc+hJTx2_s(UZnNYd2tMO8rDCe2u!lL7~5*`ma&@-@*-Z?VraB;ch3~ zoe%FRgbz631Aq9D6YjecT#2+6A{|boWB!^G>ADkILEL@uu6V!xZv7nd^FwplFHSC_ z?)9U&g^NyG@6%SN?VWt{v1RmwHwZl2>$JU}Z$7n*hW>#@UIjXtp81=F&VHw}{}qCd hmuDEnhi7xox#$YtcxUAPrMs7YHu{o7{C4lK{|3{#5t#r0 literal 0 HcmV?d00001 diff --git a/research/epac/tests/__pycache__/test_periodic_element_gonols.cpython-312.pyc b/research/epac/tests/__pycache__/test_periodic_element_gonols.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f5b6575b8f8b6789ffb3dad5813bdf48134526c GIT binary patch literal 4755 zcmb^!TWlN0aqmI$NRgH((voS|PW@_4$9h_^6GchoNVX)mmgQJ>VZrI;#9K*6ACJ`D zQ4R?buz?g&Q3H|F0#@JxQILNov(mbUBxBM>-p&Lj=DyO3aH^Omj z%#+Jsa7=giv7ki(v>cf1z z;Yr>&V_nx&rcVHSEBf5xO9UKxVHrNvm90TYbu;*Xl;?4zN*hZB%X;-KiQ19cMBPYT zA~+Ic7Vbl;?|r1!U?IM5-GO0xODN$+LO87YFC9RL)Pog-_`wZm%P0d$oSZ7KK+f|P)6Qu*KJMJp%DmLa z{cGoJR?#IdY^*^}DBUpC^*mjH(#`ZxJ)5AE97^vdD18oP%_b=Qo0GZ96B}D;%hRo- z23F~@Y3^S1p%~-GQH+151^gtcTjIed_sEIy`X{j!|HQXGxu(`uR!#0&b56X@UuzRl z-jx?~F4)ukp(iFdy8>_CopZ-GI9V@x2O>G|$$72|*W4ny)(8@$yc;mji({#>yIQx( zDiZAMiSZNcwC^&>tD@5LtO!(Vng2FT9eg3``WK=OG3qh#BmUYbSi`#sTU7JK8e4E` zm`Td}o?^?D`|5kq`>JkblGTP4%V)si9Q^a*<!G&J zrnYDefrN}8Wtk=>*73=-0?eQrm|gdWMw-s^oi_acr1O_wTK=@06B_WCFKbRx&2WMAi%o6BVbhp+YhR ztehm{Es$(mK(E-PmP#^i!6_xyVnKp49Vl0`EbP6Kht1|xhDI2fTusQPoNbe>B*0g* z?N3Xa%}?cFZ=cx)Ir<8^S9hRLcW~OZB>9WdHe1^E>(K1Eo7?B!UX%{3!#za8_kE9$ z;H`Neq58%d^ZNE__r3atnW-DO>$zg%VY~5gp}u3AZ7lQ98K>+hIpuxec_hufaOBJ&RKJ8f>Q>dHv4WMd?@-3wodb2n zeG9=$({2Du`wkYveY3CJK3?oNVRxMPGTb*m`O%q$aNi6A(_TB=w|egjH+Rj$hA%(h zq0EW#|3N6E@(&O?$Dika)jJz3Mh@DMgN5+R^F4OB^>_PjPZp2%+eiBg?E_!zwc7_5 zhlUrn6ehqQ~yP1itFRQEp^KXO>+HA^Z-A|kdxeL{vkr) zA%=rZr#+>9wRS(bxnpi}QHm}F8%l0lSq#sPeWHJ?7k72qyE+Sz*B83Z*pasvhJSij zk{>?|pn9Eq{8hN`@k5abH`$x_r^c26N&K@%WUzkwK*0T%Eywl`gv2$48YHz+p_YTe z>Ou{Qu5!_q=d5a>wo+c@d3da=#Tqb|TaJsf*JfRVLTdx;>PI=s4?2%hR3xkd{Yf@4@qS+c5)!~Kd1l zBecfI?C78$x4a00%@{e(+_J81Xuz_Iz~~!sNb5wvGzEN}NoXqM z5!aN7QzYtfoDB)ZB7|9+90tI?gmfn}R&vi^(i$+ROHW?L1OmkIzo#4)u%n(Fr6N4% z36oiYz4@guXj=QW!Z(+!Cb22Q7nsZvRz@}_loXbAWfbdX_QbOTr24KuM@|#yU=9O- z(5a8k?=93fPkZjw*3TTh@y7KxilJ6J)LN)*yWRKuQ@=e`>^Nz|ulD4$xD-D55OLnN z>E0!OUD3b8_V1V-p6mF$^V81Hy6*a0zZGDSoCLvxQJ%U9w$j9);$YIi@LeX!vY{lf zEK{E>Cz5JfXJfxCv$S1WA-%v$1{gRG;5Wz-=9r4GlGg!sFfc;^-bc&)R@b}SaB5UjdYJmo4j=3JQE4nP0o1J-tPUZknUnxa%hg<5dNNob zJMRXShGE|x1<0LqU0@s{)0bRiG|Yz1vA>hLN~U^~=zwFl$^1Kj2Li`&U!y(WAm3N0 z_G`55pZ>a;ZNC^S`uEuWJq7>1qW_TXKQw>F_P1Q|E(IEjfn9cB*X*Pn*nP#fgoF>I ztI`LxS8Hdu>xXADzkG8M?OBh_&YiRyn{PMRjYkWiR~ON1&mg$7-){V2A=JHyj{O@c zHc}qBcXMsC?-gHawO?v|gkbWZpF^U5I{T#%SrUU+`ad{z_0-Q!KM;_(-8t|70O)ru A-2eap literal 0 HcmV?d00001 diff --git a/research/epac/tests/test_geometry_comparison_after_construction.py b/research/epac/tests/test_geometry_comparison_after_construction.py index fa3f01f..ef0836d 100644 --- a/research/epac/tests/test_geometry_comparison_after_construction.py +++ b/research/epac/tests/test_geometry_comparison_after_construction.py @@ -18,43 +18,33 @@ class GeometryComparisonAfterConstructionTest(unittest.TestCase): - def test_ucns_coupling_does_not_predict_sealed_shapes(self) -> None: + def test_what_atomic_shells_add_versus_ucns_coupling(self) -> None: constructions = construct_declared_molecules() sealed = json.loads(SEALED.read_text(encoding="utf-8"))["molecules"] - - ucns_signatures = { - formula: item.invariants["ucns_coupling_signature"] - for formula, item in constructions.items() - } - controls = { - formula: matched_information_control(item.invariants) - for formula, item in constructions.items() - } known_shapes = {formula: sealed[formula]["known_shape"] for formula in constructions} - # Construction finished. Comparison opens sealed labels only now. - distinct_shapes = set(known_shapes.values()) - distinct_ucns = set(ucns_signatures.values()) - distinct_controls = set(controls.values()) - - water_co2_same_ucns = ucns_signatures["H2O"] == ucns_signatures["CO2"] - water_co2_same_shape = known_shapes["H2O"] == known_shapes["CO2"] - water_co2_same_control = controls["H2O"] == controls["CO2"] - - self.assertGreater(len(distinct_shapes), 1) - self.assertEqual(len(distinct_ucns), 1) - self.assertTrue(water_co2_same_ucns) - self.assertFalse(water_co2_same_shape) - self.assertFalse(water_co2_same_control) - self.assertGreater(len(distinct_controls), 1) - - standing = ( - "FALSIFIED-as-prediction" - if len(distinct_ucns) == 1 and len(distinct_shapes) > 1 - else "UNRESOLVED" - ) - self.assertEqual(standing, "FALSIFIED-as-prediction") - self.assertEqual(len(distinct_controls), len(constructions)) + ucns = {f: c.invariants["ucns_coupling_signature"] for f, c in constructions.items()} + atomic = {f: c.invariants["atomic_coupling_signature"] for f, c in constructions.items()} + control = {f: matched_information_control(c.invariants) for f, c in constructions.items()} + + self.assertGreater(len(set(known_shapes.values())), 1) + self.assertEqual(len(set(ucns.values())), 1) + self.assertNotEqual(known_shapes["H2O"], known_shapes["CO2"]) + self.assertNotEqual(control["H2O"], control["CO2"]) + self.assertNotEqual(atomic["H2O"], atomic["CO2"]) + self.assertTrue(constructions["CO2"].invariants["ligand_has_p"]) + self.assertFalse(constructions["H2O"].invariants["ligand_has_p"]) + + # UCNS Möbius is identical across sealed shape classes. + ucns_predicts_shape = len(set(ucns.values())) == len(set(known_shapes.values())) + self.assertFalse(ucns_predicts_shape) + + # Atomic shell/unpaired-(l,m) signatures distinguish the formulas, but + # they are functions of the atoms already named in the formula. + atomic_equals_control = set(atomic.values()) == set(control.values()) + self.assertFalse(atomic_equals_control) + self.assertEqual(len(set(atomic.values())), len(constructions)) + self.assertEqual(len(set(control.values())), len(constructions)) if __name__ == "__main__": diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index ee14925..2978612 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -20,26 +20,26 @@ def test_declared_formulas_close_and_replay(self) -> None: for formula, construction in molecules.items(): replayed = replay_molecule(construction) self.assertEqual(construction.receipt.receipt_digest, replayed.receipt_digest, formula) - self.assertEqual(construction.receipt.gonol.participants[0].scale, "word") - def test_valence_occupancy_from_inputs_only(self) -> None: + def test_unpaired_valence_and_shells_are_used(self) -> None: molecules = construct_declared_molecules() - self.assertEqual(molecules["H2"].invariants["center_symbol"], None) - self.assertEqual(molecules["H2O"].invariants["center_symbol"], "O") - self.assertEqual(molecules["H2O"].invariants["slot_occupancy"], [1, 1]) - self.assertEqual(molecules["NH3"].invariants["slot_occupancy"], [1, 1, 1]) - self.assertEqual(molecules["CH4"].invariants["slot_occupancy"], [1, 1, 1, 1]) - self.assertEqual(molecules["CO2"].invariants["slot_occupancy"], [2, 2]) + 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"], ["1:1", "1:0"]) def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: molecules = construct_declared_molecules() signatures = {formula: item.invariants["ucns_coupling_signature"] for formula, item in molecules.items()} - unique = set(signatures.values()) - self.assertEqual(len(unique), 1) - law, turns, _frames, restored = next(iter(unique)) - self.assertEqual(law, "ucns.native-mobius-root-loop") - self.assertEqual(turns, (0, 1, 2)) - self.assertTrue(restored) + self.assertEqual(len(set(signatures.values())), 1) def test_construction_text_avoids_sealed_labels(self) -> None: source = (EPAC_ROOT / "epac_molecular.py").read_text(encoding="utf-8").lower() diff --git a/research/epac/tests/test_periodic_element_gonols.py b/research/epac/tests/test_periodic_element_gonols.py index dc4e97c..8b4ac02 100644 --- a/research/epac/tests/test_periodic_element_gonols.py +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -25,13 +25,36 @@ def test_constructs_z1_to_z18(self) -> None: options = dict(carbon.gonol.carried_options) self.assertEqual(options["Z"], "6") self.assertEqual(options["electron-configuration"], "1s2.2s2.2p2") - self.assertEqual(options["typical-valence"], "4") + self.assertEqual(options["valence-electrons"], "4") + self.assertEqual(options["unpaired-valence-count"], "2") + self.assertEqual(options["promoted-unpaired-count"], "4") + self.assertEqual(len(carbon.gonol.participants), 3) + shells = [item for item in carbon.gonol.participants if "".join(item.source_units).startswith("n")] + 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(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_construction_does_not_carry_shape_labels(self) -> None: receipt = construct_element_gonol("N") blob = str(receipt.gonol.carried_options) + receipt.gonol.relation From 95ceddbbbc9751a1dd23e9e2b296b3b656dfeefb Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 12:05:03 +0000 Subject: [PATCH 03/21] Stop tracking epac test bytecode --- .gitignore | 1 + ...comparison_after_construction.cpython-312.pyc | Bin 4252 -> 0 bytes .../test_molecular_affixiation.cpython-312.pyc | Bin 4375 -> 0 bytes .../test_periodic_element_gonols.cpython-312.pyc | Bin 4755 -> 0 bytes 4 files changed, 1 insertion(+) create mode 100644 .gitignore delete mode 100644 research/epac/tests/__pycache__/test_geometry_comparison_after_construction.cpython-312.pyc delete mode 100644 research/epac/tests/__pycache__/test_molecular_affixiation.cpython-312.pyc delete mode 100644 research/epac/tests/__pycache__/test_periodic_element_gonols.cpython-312.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/research/epac/tests/__pycache__/test_geometry_comparison_after_construction.cpython-312.pyc b/research/epac/tests/__pycache__/test_geometry_comparison_after_construction.cpython-312.pyc deleted file mode 100644 index e8ff9092aaa029075a6d3c7f04fcb4a24ba185c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4252 zcmb_fU2GHC6~5yckH>#;oRGx%asFVtjTGVy1W4GBG)jVD^S4`~Ewu>K!E=+?jK^VS z3{8|sAW@N3D-pC0uo4f6s6-HbNTrpURgoIn$G&(vZXI2ArCsq*ZJ*G>3q1AS8BY?x zprG9=`Tm~s-E)r5J?G3n{eBO??Zc(dlK{b6FGW=?e;gn?M92C4m$bqbM?W zNRF7p9O)Qsj!cZfBP}^oY>cIV13(tK?qgokl0j?S+21cLl4U!0t+~il<4Mc|J#RVn zLOL0Goi|zg`kqMSDS;kap8F1qh63-5;T4^8UI0Xv^gkh<%S7dhqhiatr9i4QR+>&g=rQTNq>q(3Kop3+t3dQaCi%11^}kPBd$B#RLM%D|a{v^~gK1lf&=$5RFzc9g zv;@8<>lO-m1zApOf|f|j>L^|_9}C*__v8;ooB~xSDDi2;Krx;|OeQX?h*p(&iv!ig zF@vUL;uhfTf!9Q z<@6=lW)&2E3Z_#~Q)b(eYFdsLq&5fp-+j6VFK+?}Vw4E5ztSRgm5I?W?(h8==PS2P;FLfGg zFOzP2wmpi}pB!5*RlpW$@)Q;v^V9&iPRAVy5O+-AKYf9or{^8>=Bf2uK6m`V&wvF6 zpHN9m@oKYW#5m6DY@9ZcDgZcx&9&fMVCS9lY|{5C7j|_gkpQDtVRV;C`P1UFJMg)8 z#K(_)TYfEIOJ7_Q+qPJjFxJIw-c>X>Y2`MOhAm{Zc_vw9&$mb0sy4PgimvS-GY^tP z#aCLnG$mQwh)Kwq+E^a8u!eDl1;J38mgXp{Rp zasK0=98Zf0c?!9=)0x*wQtGrY15p)8gE*OGW+Y6;tBEOD&@u`{)q<8zCE`Vn6r5K8 zqE8$cLv;Vx0pybsQ-Un=(}K#+w3I5OUPBB?c*rTG1yM!pslmSC!2#q}pdj)Z{H=zZ z2@R%HO;t!bKz>10p`t}GB~EmRmY|H2G}I7#QIIk?8G9{)!3!BdLM7H> zG_6@{kF_3GGSDVGE=VeD@gYWq(j@Y4C3;dKAl6FT$VKvcMiP*3hiz0uO(H6;kc5g@ z%N)pM=lycia5K14NW?Xjx2#!FFd8J%5wn^#f=Uur9B>_$IqR;0014kgKhN2J6{2 zGmAbTT>PbJLE{TDs?$)yG76%|UxbR9QTdl#Vea%Ui;uQcQbfc602^f2t-nqPJYjm8-6mhPANX z)T@VjvsDv|KHPm99nVK&dNgK4&*TFWUuo+XHky{GrS_ZMH@dTpU3&RZz4T}{FtNz! zfr-tb)A^yZ`p{Wp=r{SG@UUZ}eM4AkTKeSXnHy)a&ByeL9=)t58x$5%+V;Eac11JsjBR+qkgQo~vwMYx+y;pIeQ}UcJ0GTXt^IrI(%imh0Ib``R!B7d+ye*)ZPgj;fPWF!CF&}>oCu2 zb*}c-@M?u#7g?)YhkE-!jvL%Qi7pR*F}fOE8@xA~<38NpKfZkEi;>mCYwz7VmgBnc z{`J6Tfm{A%sE1nfp(A?e$a;w$isrZzCJ&L~R(Lt1SMSePck0!h>-Bo|i5z$G84DVk z|G0NqynBA-{GF81++oxodQkr0)J9#tZ(Q#iH~Ky`j{VB$I<42A&T_R|XxZx49Jl`& z2b%URAHO@YGID3kXzX}EuSfqj`qik>J#2K2=#3*;F1&-KEyqQ+mZnuI?}k^xcN&av zyHS1M%l>u0(Q!P-MPDYWx?8_eZ#1ddN zUw`X@?r*y6*#z`8?h1D; za3!!r-8i^3`*}|eG{3q#xBQV_)wWitR~<3RJ9D7xEd=ZD=~cZ(`7d&y@4sOD2@5>s zOP}iQ+HC!R?jE?zK4WXClho>TzO7ep>wN~u>Pa_Xe2a7cqN_KV;N=t7My`zfZtMvS Jm>Sar{|C0q!vz2U diff --git a/research/epac/tests/__pycache__/test_molecular_affixiation.cpython-312.pyc b/research/epac/tests/__pycache__/test_molecular_affixiation.cpython-312.pyc deleted file mode 100644 index f7be26fe5deb3e131808753dd75b5e2349dd60da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4375 zcmb_fOKcm*8J>MnON!Lfde9Ha670mZY`y%5>o|^O`5`+sl{RV$D-IT`-4V6)a+jW6 z+9o0S0l2V&7_f>QtfEG%wuiQG4?g;+V~bq0rVI+(=At>+8eU{u4MCcp(Q7SclhgtnQgU}QhL&W#-&WR7iXX{_%2B_s@=@rF2XXKXvmwkTTmN%y zk}h3WRBBHI2TS%mVSNDx{m(i=s*tNiNDa{X|ERCV(<*MP6>c`D*QM&m>r)Nm4fJ-; zBQqpccCgpxO1Ch>OrvD+_PiCv$`(pr z(@g2rr@$9}$CR-&%_NK7Qj(=@N%I->=@3F!;EXfu3}=I%`DL0b>6_s<-g}x$dj45j z;kU_f^*8;R<~JN=OL$<=gJB+JQH+hBqz7`@kwKRm9ytuvDD*}KM_n#Hawt|q=#GE^ z%fiGydo3gD@V?k~1qI04$0m3J0uO)STV4yq5Z=%oOgsA!jX&iKH1eCMr&2L&b&-qmoGyeSoz-%a)a^ zeI+dR;k2yu*|5Lm)g_3@M3T#h=uKzcx2aGWoJGqlNzqLUOR}L#zE!zXn_;SViy9i? zP}Wtb?x3%>_2;(@&IVV+a6xQ$#P-L{^Ot|w@jUjyviQT5y}iGeek0}g9{nD%L6vdB zU9;!su6)(@*1~kY?eOf#O1PmA-syyQ&R;3)8gOS2%syIemHQlZh8$ z3JvzcUoYFBHvASrs1K{b?MtE4w@OR%7pN|&6EL7Q3{_OwQ-G;VYzPW2`0 zwjk+Qnm}s(Zz~A|CD&-7;;#5DTFR^b`5V+=<@v$g8{?!26cQS02?eDaQNWqNyR2+# z!qi=6)ZJDb0soxT5*gwVXs&j^8tfgUzw}|ZsVI^$(y~T*T~EE{29Ec?<_C@+9B{+B zmZ07c)2|oLT zG~>EVG}*8%vW*&Dq@#1PZecg%bJG~fU|2w(h-{}q!VMV?=GLUJJtZ61ty5$&VH&_q z=1on-WuhS+R6ORn^4f{6%Q`j`p9QQbtm~Ep5lo_o$en&O9c)EMJ8WqoaKj2hXo$}5 zbsBaT8hV_Do>^g~X~+Cor>VQpbkJ!!I9sz4?Or(IL}P{MVJCWcwr-6;=3C}(JJG(U z&CBAEiovB$kUsh>vMipf8a)K7pJtv-J~gXn=5IOC-es|`O87k|dU8>y5^P)Qdf$nj zTHLcNo-fP7DBbS)v7ao9AH(p&`p@d;>lSV~?MIizcgk`dOZ!eZ(f1aG#muu?FT^<6 z4L4E6QX&j^EcH+5Y{pU>h30aAeaJrK6i!15`)Nx#)K0SyKzUHQuK0}i1^b|lM>9MP z%D?~{Fhx03+cfNX1p;lP)GxOsOG=zlYSjuh%7S$aQn(pzhP{DeT-gkpORR#8?se)uUl989vg_di7N&Pvo z^wUq~67%-2Z~x-TI$eYLuEY6VNAdu9 z=7o6tU#lEyC5(60Uu_@i4OC%lEfU@XGXP^y`$c7F;=K{NRQ6A63ED=Htn}tHjLJib z2d@M1azh*e4lB6+L93UD3GrMbO6Mo;FFoZfdF}!M_2Wh68y5 z>sIc}o58X*E%YydH4GJ?rD?}f$AHr`P-r~iG@e*YEH$3UH^vK%V@~5(zVYMPz{<9Z z-yLS7@v)aK4@4gCg zf^UW=N;gi)njuNC7P|FhhB~|?5qjy6XlE@~%ov(YZ{4j)!yhkQn;<-$gq4$;XC|cI zmZ@LIRDb|9EOL?xggzP@8vek4cb+bF1DZjzc+hJTx2_s(UZnNYd2tMO8rDCe2u!lL7~5*`ma&@-@*-Z?VraB;ch3~ zoe%FRgbz631Aq9D6YjecT#2+6A{|boWB!^G>ADkILEL@uu6V!xZv7nd^FwplFHSC_ z?)9U&g^NyG@6%SN?VWt{v1RmwHwZl2>$JU}Z$7n*hW>#@UIjXtp81=F&VHw}{}qCd hmuDEnhi7xox#$YtcxUAPrMs7YHu{o7{C4lK{|3{#5t#r0 diff --git a/research/epac/tests/__pycache__/test_periodic_element_gonols.cpython-312.pyc b/research/epac/tests/__pycache__/test_periodic_element_gonols.cpython-312.pyc deleted file mode 100644 index 8f5b6575b8f8b6789ffb3dad5813bdf48134526c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4755 zcmb^!TWlN0aqmI$NRgH((voS|PW@_4$9h_^6GchoNVX)mmgQJ>VZrI;#9K*6ACJ`D zQ4R?buz?g&Q3H|F0#@JxQILNov(mbUBxBM>-p&Lj=DyO3aH^Omj z%#+Jsa7=giv7ki(v>cf1z z;Yr>&V_nx&rcVHSEBf5xO9UKxVHrNvm90TYbu;*Xl;?4zN*hZB%X;-KiQ19cMBPYT zA~+Ic7Vbl;?|r1!U?IM5-GO0xODN$+LO87YFC9RL)Pog-_`wZm%P0d$oSZ7KK+f|P)6Qu*KJMJp%DmLa z{cGoJR?#IdY^*^}DBUpC^*mjH(#`ZxJ)5AE97^vdD18oP%_b=Qo0GZ96B}D;%hRo- z23F~@Y3^S1p%~-GQH+151^gtcTjIed_sEIy`X{j!|HQXGxu(`uR!#0&b56X@UuzRl z-jx?~F4)ukp(iFdy8>_CopZ-GI9V@x2O>G|$$72|*W4ny)(8@$yc;mji({#>yIQx( zDiZAMiSZNcwC^&>tD@5LtO!(Vng2FT9eg3``WK=OG3qh#BmUYbSi`#sTU7JK8e4E` zm`Td}o?^?D`|5kq`>JkblGTP4%V)si9Q^a*<!G&J zrnYDefrN}8Wtk=>*73=-0?eQrm|gdWMw-s^oi_acr1O_wTK=@06B_WCFKbRx&2WMAi%o6BVbhp+YhR ztehm{Es$(mK(E-PmP#^i!6_xyVnKp49Vl0`EbP6Kht1|xhDI2fTusQPoNbe>B*0g* z?N3Xa%}?cFZ=cx)Ir<8^S9hRLcW~OZB>9WdHe1^E>(K1Eo7?B!UX%{3!#za8_kE9$ z;H`Neq58%d^ZNE__r3atnW-DO>$zg%VY~5gp}u3AZ7lQ98K>+hIpuxec_hufaOBJ&RKJ8f>Q>dHv4WMd?@-3wodb2n zeG9=$({2Du`wkYveY3CJK3?oNVRxMPGTb*m`O%q$aNi6A(_TB=w|egjH+Rj$hA%(h zq0EW#|3N6E@(&O?$Dika)jJz3Mh@DMgN5+R^F4OB^>_PjPZp2%+eiBg?E_!zwc7_5 zhlUrn6ehqQ~yP1itFRQEp^KXO>+HA^Z-A|kdxeL{vkr) zA%=rZr#+>9wRS(bxnpi}QHm}F8%l0lSq#sPeWHJ?7k72qyE+Sz*B83Z*pasvhJSij zk{>?|pn9Eq{8hN`@k5abH`$x_r^c26N&K@%WUzkwK*0T%Eywl`gv2$48YHz+p_YTe z>Ou{Qu5!_q=d5a>wo+c@d3da=#Tqb|TaJsf*JfRVLTdx;>PI=s4?2%hR3xkd{Yf@4@qS+c5)!~Kd1l zBecfI?C78$x4a00%@{e(+_J81Xuz_Iz~~!sNb5wvGzEN}NoXqM z5!aN7QzYtfoDB)ZB7|9+90tI?gmfn}R&vi^(i$+ROHW?L1OmkIzo#4)u%n(Fr6N4% z36oiYz4@guXj=QW!Z(+!Cb22Q7nsZvRz@}_loXbAWfbdX_QbOTr24KuM@|#yU=9O- z(5a8k?=93fPkZjw*3TTh@y7KxilJ6J)LN)*yWRKuQ@=e`>^Nz|ulD4$xD-D55OLnN z>E0!OUD3b8_V1V-p6mF$^V81Hy6*a0zZGDSoCLvxQJ%U9w$j9);$YIi@LeX!vY{lf zEK{E>Cz5JfXJfxCv$S1WA-%v$1{gRG;5Wz-=9r4GlGg!sFfc;^-bc&)R@b}SaB5UjdYJmo4j=3JQE4nP0o1J-tPUZknUnxa%hg<5dNNob zJMRXShGE|x1<0LqU0@s{)0bRiG|Yz1vA>hLN~U^~=zwFl$^1Kj2Li`&U!y(WAm3N0 z_G`55pZ>a;ZNC^S`uEuWJq7>1qW_TXKQw>F_P1Q|E(IEjfn9cB*X*Pn*nP#fgoF>I ztI`LxS8Hdu>xXADzkG8M?OBh_&YiRyn{PMRjYkWiR~ON1&mg$7-){V2A=JHyj{O@c zHc}qBcXMsC?-gHawO?v|gkbWZpF^U5I{T#%SrUU+`ad{z_0-Q!KM;_(-8t|70O)ru A-2eap From 61804b92fe6c32cc09ebf91d824744425976ac1f Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 12:06:59 +0000 Subject: [PATCH 04/21] Add subatomic element affixiation candidate with receipts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Identity-only H/He/Li/C element-gonol candidates over established UCNS carrier identity and native Möbius framing. 5 tests pass; CONTRACTS vs CHECKS audit closed; sealed receipts replay byte-identically. Status: CROSS-DOMAIN-HYPOTHESIS / provisional. Signed-off-by: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> --- .../element_affixiation_candidate.py | 264 ++++++++++++++++++ research/epac/subatomic/receipts/c.json | 88 ++++++ research/epac/subatomic/receipts/h.json | 64 +++++ research/epac/subatomic/receipts/he.json | 72 +++++ research/epac/subatomic/receipts/li.json | 78 ++++++ .../subatomic-affixiation-baseline.md | 22 ++ .../test_element_affixiation_candidate.py | 109 ++++++++ 7 files changed, 697 insertions(+) create mode 100644 research/epac/subatomic/element_affixiation_candidate.py create mode 100644 research/epac/subatomic/receipts/c.json create mode 100644 research/epac/subatomic/receipts/h.json create mode 100644 research/epac/subatomic/receipts/he.json create mode 100644 research/epac/subatomic/receipts/li.json create mode 100644 research/epac/subatomic/test_element_affixiation_candidate.py diff --git a/research/epac/subatomic/element_affixiation_candidate.py b/research/epac/subatomic/element_affixiation_candidate.py new file mode 100644 index 0000000..43e5d24 --- /dev/null +++ b/research/epac/subatomic/element_affixiation_candidate.py @@ -0,0 +1,264 @@ +"""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. +ISOTOPE_DEFAULTS = { + "H": (1, 1), + "He": (2, 4), + "Li": (3, 7), + "C": (6, 12), +} + + +@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/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/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/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 index 74c87d9..c8bd75c 100644 --- a/research/epac/subatomic/subatomic-affixiation-baseline.md +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -184,3 +184,25 @@ Do not add local geometry, position-operation semantics, or physics status insid - 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. 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" From 49161f790cf28225bd7afd98104cd78519967ae2 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 17:47:44 +0000 Subject: [PATCH 05/21] Add subatomic gonol construction with nuclear harmonic layer Subatomic gonol closes nucleus identity + Mobius framing + nuclear harmonic relations + quantum-layer electron shells via edcm.gonol/v1. 15/15 tests pass; 15 contracts / 15 checks audit closed; replay byte-identical. Standing: implemented-candidate, CROSS-DOMAIN-HYPOTHESIS. Signed-off-by: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> --- .../subatomic/nuclear_harmonic_candidates.py | 328 ++++++++++++++++++ research/epac/subatomic/receipts/gonol_c.json | 66 ++++ research/epac/subatomic/receipts/gonol_h.json | 65 ++++ .../epac/subatomic/receipts/gonol_he.json | 65 ++++ .../epac/subatomic/receipts/gonol_li.json | 66 ++++ .../harmonic_alpha_cluster_recurrence.json | 27 ++ ..._binding_per_nucleon_commensurability.json | 27 ++ ...nic_ground_state_spin_parity_symmetry.json | 28 ++ .../harmonic_n_z_ratio_commensurability.json | 29 ++ ...nic_proton_neutron_inversion_symmetry.json | 26 ++ .../subatomic-affixiation-baseline.md | 58 ++++ research/epac/subatomic/subatomic_gonol.py | 271 +++++++++++++++ .../test_nuclear_harmonic_candidates.py | 101 ++++++ .../epac/subatomic/test_subatomic_gonol.py | 99 ++++++ 14 files changed, 1256 insertions(+) create mode 100644 research/epac/subatomic/nuclear_harmonic_candidates.py create mode 100644 research/epac/subatomic/receipts/gonol_c.json create mode 100644 research/epac/subatomic/receipts/gonol_h.json create mode 100644 research/epac/subatomic/receipts/gonol_he.json create mode 100644 research/epac/subatomic/receipts/gonol_li.json create mode 100644 research/epac/subatomic/receipts/harmonic_alpha_cluster_recurrence.json create mode 100644 research/epac/subatomic/receipts/harmonic_binding_per_nucleon_commensurability.json create mode 100644 research/epac/subatomic/receipts/harmonic_ground_state_spin_parity_symmetry.json create mode 100644 research/epac/subatomic/receipts/harmonic_n_z_ratio_commensurability.json create mode 100644 research/epac/subatomic/receipts/harmonic_proton_neutron_inversion_symmetry.json create mode 100644 research/epac/subatomic/subatomic_gonol.py create mode 100644 research/epac/subatomic/test_nuclear_harmonic_candidates.py create mode 100644 research/epac/subatomic/test_subatomic_gonol.py 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/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/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/subatomic-affixiation-baseline.md b/research/epac/subatomic/subatomic-affixiation-baseline.md index c8bd75c..dea5a33 100644 --- a/research/epac/subatomic/subatomic-affixiation-baseline.md +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -206,3 +206,61 @@ The frozen minimal decisive action from §7 is now implemented locally (not push - 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 EDCM gonol candidate constructor (`edcm.gonol/v1`) with +`ucns.public_gonol` supplied as the explicit geometry authority: + +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` — sealed gonol receipts; `replay_gonol` byte-identical for all. + +| 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. diff --git a/research/epac/subatomic/subatomic_gonol.py b/research/epac/subatomic/subatomic_gonol.py new file mode 100644 index 0000000..0ab10c3 --- /dev/null +++ b/research/epac/subatomic/subatomic_gonol.py @@ -0,0 +1,271 @@ +"""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 EDCM gonol candidate constructor +(``edcm.gonol.construct_gonol``) with ``ucns.public_gonol`` supplied as the +explicit geometry authority. 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="::/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 +""" + +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 edcm.gonol import ( # noqa: E402 + ClosedGonol, + GonolReceipt, + construct_gonol, + replay_gonol, +) +from epac_atomic import AtomicRecord, atomic_record # noqa: E402 + +import element_affixiation_candidate as identity # noqa: E402 +import nuclear_harmonic_candidates as harmonics # noqa: E402 + +# === 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 EDCM gonol candidate constructor +# owner: The Interdependency +# public_surface: SUPPORTED_SYMBOLS, construct_subatomic_gonol, replay_subatomic_gonol, subatomic_receipt_record +# internal_surface: _geometry_authority, _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: edcm_gonol, epac_atomic, epac_subatomic_element_affixiation_candidate, epac_subatomic_nuclear_harmonic_candidates +# since: 2026-08-22 +# unresolved: UCNS position operations; UCNS harmonic notation; EDCM 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_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: UCNS geometry is consumed only as the explicit public_gonol authority; 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, ...] = ("H", "He", "Li", "C") + + +def _geometry_authority(): + from ucns import public_gonol + + return public_gonol + + +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 _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 _nucleus_participant(symbol: str, occurrence: int) -> ClosedGonol: + 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=(",", ":") + ), + ) + ) + receipt = construct_gonol( + scale="word", + source="nuc", + source_id=f"epac.subatomic.nucleus:{symbol}#{occurrence}", + relation="epac.subatomic.nucleus", + carried_options=carried, + geometry_authority=_geometry_authority(), + occurrence=occurrence, + ) + return receipt.gonol + + +def _shell_participants(record: AtomicRecord, occurrence: int) -> tuple[ClosedGonol, ...]: + by_n: dict[int, list] = {} + for electron in record.electrons: + by_n.setdefault(electron.n, []).append(electron) + shells: list[ClosedGonol] = [] + for n in sorted(by_n): + members: list[ClosedGonol] = [] + for electron in by_n[n]: + electron_receipt = construct_gonol( + scale="word", + source="e", + source_id=f"epac.subatomic.electron:{record.symbol}#{occurrence}:{electron.index}", + relation="epac.atomic.electron", + carried_options=_electron_options(record, electron), + geometry_authority=_geometry_authority(), + occurrence=electron.index, + ) + members.append(electron_receipt.gonol) + shell_receipt = construct_gonol( + scale="word", + source=f"n{n}", + source_id=f"epac.subatomic.shell:{record.symbol}#{occurrence}:n{n}", + relation="epac.atomic.shell", + participants=members, + geometry_authority=_geometry_authority(), + occurrence=n, + ) + shells.append(shell_receipt.gonol) + return tuple(shells) + + +def construct_subatomic_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt: + """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(identity.ISOTOPE_DEFAULTS[symbol][0]) + nucleus = _nucleus_participant(symbol, occurrence) + shells = _shell_participants(record, occurrence) + harmonic_surviving = ",".join( + candidate.candidate_id + for candidate in _harmonic_rows(symbol) + if any(harmonics.recurrence_test(candidate).values()) + ) + carried = [ + ("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_gonol( + scale="word", + source=symbol, + source_id=f"epac.subatomic.element:{symbol}#{occurrence}", + relation="epac.subatomic.element", + participants=(nucleus, *shells), + carried_options=carried, + geometry_authority=_geometry_authority(), + occurrence=occurrence, + ) + + +def replay_subatomic_gonol(receipt: GonolReceipt) -> str: + """Replay a completed subatomic gonol receipt; returns its digest.""" + return replay_gonol(receipt=receipt).receipt_digest + + +def subatomic_receipt_record(receipt: GonolReceipt) -> 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, + "scale": gonol.scale, + "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/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..cb3e76e --- /dev/null +++ b/research/epac/subatomic/test_subatomic_gonol.py @@ -0,0 +1,99 @@ +"""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 epac_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. + 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 EDCM gonol construction and supplies the explicit + # UCNS public_gonol authority; it must not define position operations. + assert "def " + "public_gonol" not in source + assert "advance(" not in source + assert "NativeMobius" not in source + receipt = m.construct_subatomic_gonol("H") + assert receipt.gonol.geometry_digest # geometry observed from the supplied authority + + +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 From d0b291a1e0779c7e2ae1104407ab769eeffdd293 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 17:55:09 +0000 Subject: [PATCH 06/21] Declare dimensional arity separately from ambient size Arity is the number of dimensions in one explicit coupling, not how many axes exist. Geometry is generated only from declared couplings. Overlapping binaries such as zx and zy do not create xy or xyz. Coupling order identity remains hmmm. --- research/epac/README.md | 7 +- research/epac/docs/arity.md | 25 +++ research/epac/epac_dimensional_arity.py | 201 ++++++++++++++++++ research/epac/epac_molecular.py | 25 ++- research/epac/tests/test_epac_arity.py | 97 +++++++++ .../epac/tests/test_molecular_affixiation.py | 15 ++ 6 files changed, 367 insertions(+), 3 deletions(-) create mode 100644 research/epac/docs/arity.md create mode 100644 research/epac/epac_dimensional_arity.py create mode 100644 research/epac/tests/test_epac_arity.py diff --git a/research/epac/README.md b/research/epac/README.md index 2b4b855..7546903 100644 --- a/research/epac/README.md +++ b/research/epac/README.md @@ -12,10 +12,13 @@ until a canonical `The-Interdependency/epac` source is created and pinned in 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. +- [`docs/arity.md`](docs/arity.md) — arity is declared dimensional coupling, + not ambient dimension count. - [`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 valence arity and UCNS Möbius - coupling. Known chemistry is sealed until after construction. + 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 diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md new file mode 100644 index 0000000..67c60f1 --- /dev/null +++ b/research/epac/docs/arity.md @@ -0,0 +1,25 @@ +# Dimensional arity + +Status: **CROSS-DOMAIN-HYPOTHESIS / provisional**. Not org canon. + +Dimension tells where. Arity tells what intersects at once. + +```text +ambient_dimensions: declared independent axes +couplings: only explicitly declared intersections +Coupling.arity = number of dimensions in that one coupling +``` + +Ambient size and arity are independent. Three ambient dimensions do not create +a ternary coupling. `{z,x}` and `{z,y}` remain two arity-2 declarations that +share `z`. They do not declare `{x,y}` or `{x,y,z}`. + +Geometry is generated from declared couplings, not from the power set of +available axes. The same ambient space may hold mixed arities. There is no +x/y/z special case; ids may be `d1…dk`. + +`hmmm`: whether `{x,z}` and `{z,x}` are the same coupling or oriented +declarations is undeclared. The implementation stores declaration sequence +only. + +See `epac_dimensional_arity.py`. diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py new file mode 100644 index 0000000..1cb2933 --- /dev/null +++ b/research/epac/epac_dimensional_arity.py @@ -0,0 +1,201 @@ +"""Declared dimensional arity. + +Dimension tells where. Arity tells what intersects at once. + +Ambient dimension count and coupling arity are independent. Geometry is +generated only from explicitly declared couplings. Overlapping lower-arity +couplings do not create a higher-arity coupling. + +Domain claims (provisional, this candidate): + +- surface_form: dimension + term_id: epac.dimensional.dimension + claiming_domain: epac candidate + claimed_sense: an independent coordinate axis in a declared ambient space + excluded: coupling arity; participant count; x/y/z as the general model + +- surface_form: arity + term_id: epac.dimensional.arity + claiming_domain: epac candidate + claimed_sense: number of dimensions participating in one declared coupling + excluded: ambient dimension count; gonol participant-count policies + (edcm.gonol arity_policy is a different sense) + +- surface_form: coupling + term_id: epac.dimensional.coupling + claiming_domain: epac candidate + claimed_sense: one explicit intersection of exactly k declared dimensions + excluded: the power set of ambient dimensions; inferred closures + +Collision: edcm.gonol ScaleOptionSet.arity_policy counts closed gonol +participants, not dimensional intersections. Resolution: different term ids. + +hmmm: whether a coupling is intrinsically unordered ({x,z} = {z,x}) or may +carry orientation (zx ≠ xz) is undeclared. This module stores the declared +sequence as declaration identity only. It does not quotient by permutation +and does not treat order as geometric orientation. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Iterable, Mapping, Sequence + + +class DimensionalArityError(ValueError): + """Fail-closed dimensional arity error.""" + + +@dataclass(frozen=True, slots=True) +class Dimension: + """One independent coordinate axis.""" + + id: str + + 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") + + +@dataclass(frozen=True, slots=True) +class Coupling: + """One explicitly declared 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 member_ids(self) -> frozenset[str]: + """Membership view only. Not geometric identity.""" + + return frozenset(self.declared_ids) + + +@dataclass(frozen=True, slots=True) +class DimensionalSpace: + """Ambient axes plus only those couplings that were declared.""" + + ambient_dimensions: tuple[Dimension, ...] + couplings: tuple[Coupling, ...] + + 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 coupling in self.couplings: + missing = [item for item in coupling.declared_ids if item not in ambient] + if missing: + raise DimensionalArityError( + f"coupling {coupling.declared_ids} uses undeclared dimensions {tuple(missing)}" + ) + + +def dimension(id: str) -> Dimension: + return Dimension(id) + + +def coupling(dimension_ids: Sequence[str]) -> Coupling: + if not isinstance(dimension_ids, Sequence) or isinstance(dimension_ids, (str, bytes)): + raise DimensionalArityError("coupling dimensions must be an ordered declaration sequence") + return Coupling(tuple(Dimension(item) for item in dimension_ids)) + + +def space( + ambient_ids: Sequence[str], + coupling_declarations: Sequence[Sequence[str]] = (), +) -> DimensionalSpace: + if not isinstance(ambient_ids, Sequence) or isinstance(ambient_ids, (str, bytes)): + raise DimensionalArityError("ambient dimensions must be a declared sequence") + declared = tuple(coupling(item) for item in coupling_declarations) + return DimensionalSpace( + ambient_dimensions=tuple(Dimension(item) for item in ambient_ids), + couplings=declared, + ) + + +def observed_shared_ids(left: Coupling, right: Coupling) -> frozenset[str]: + """Shared members of two declarations. This is not a new coupling.""" + + return left.member_ids & right.member_ids + + +def geometry_from_declared_couplings(declared: DimensionalSpace) -> Mapping[str, object]: + """Geometry is the declared couplings, not the power set of ambient axes.""" + + return { + "ambient_ids": tuple(item.id for item in declared.ambient_dimensions), + "ambient_count": len(declared.ambient_dimensions), + "couplings": tuple( + { + "declared_ids": coupling.declared_ids, + "arity": coupling.arity, + } + for coupling in declared.couplings + ), + "arity_counts": _arity_counts(declared.couplings), + "observed_shares": tuple(_share_records(declared.couplings)), + "inferred_from_ambient": False, + "inferred_higher_arity_from_overlap": False, + "order_identity": "hmmm", + } + + +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 _share_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_shared_ids(left, right) + if shared: + yield { + "left": left.declared_ids, + "right": right.declared_ids, + "shared_ids": tuple(sorted(shared)), + "creates_higher_arity_coupling": False, + } + + +def has_declared_coupling(declared: DimensionalSpace, dimension_ids: Sequence[str]) -> bool: + """True only if this exact declaration sequence is present. + + Permuted declarations are not treated as the same or as different geometry. + """ + + target = tuple(dimension_ids) + return any(item.declared_ids == target for item in declared.couplings) + + +__all__ = [ + "Coupling", + "Dimension", + "DimensionalArityError", + "DimensionalSpace", + "coupling", + "dimension", + "geometry_from_declared_couplings", + "has_declared_coupling", + "observed_shared_ids", + "space", +] diff --git a/research/epac/epac_molecular.py b/research/epac/epac_molecular.py index 062ee6d..33155f0 100644 --- a/research/epac/epac_molecular.py +++ b/research/epac/epac_molecular.py @@ -14,7 +14,8 @@ from edcm.gonol import ClosedGonol, GonolReceipt, construct_gonol, replay_gonol from ucns.direct_mobius import native_mobius_state -from epac_atomic import AtomicRecord, atomic_record +from epac_atomic import AtomicRecord +from epac_dimensional_arity import geometry_from_declared_couplings, space from epac_periodic import atomic_of, carried, construct_element_gonol, symbol_of @@ -80,6 +81,24 @@ def _attachment_set(record: AtomicRecord, needed: int) -> tuple[tuple[int, int], ) +def _atom_dimension_id(gonol: ClosedGonol) -> str: + return f"{symbol_of(gonol)}#{gonol.occurrence}" + + +def _declared_dimensional_space( + participants: tuple[ClosedGonol, ...], + center: ClosedGonol | None, + ligands: tuple[ClosedGonol, ...], +): + ambient = [_atom_dimension_id(item) 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) + + def _mobius_coupling() -> Mapping[str, Any]: origin = native_mobius_state(0) one = origin.advance(1) @@ -133,6 +152,8 @@ def construct_molecule(formula: str) -> MolecularConstruction: geometry_authority=__import__("ucns.public_gonol", fromlist=["public_gonol"]), ) mobius = _mobius_coupling() + dimensional = _declared_dimensional_space(participants, center, ligands) + geometry = geometry_from_declared_couplings(dimensional) 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 = { @@ -163,6 +184,8 @@ def construct_molecule(formula: str) -> MolecularConstruction: tuple(mobius["frame"]), mobius["complete_restored"], ), + "dimensional_geometry": geometry, + "declared_coupling_arities": [item["arity"] for item in geometry["couplings"]], } return MolecularConstruction(formula=formula, receipt=receipt, invariants=invariants) diff --git a/research/epac/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py new file mode 100644 index 0000000..e9a7a2f --- /dev/null +++ b/research/epac/tests/test_epac_arity.py @@ -0,0 +1,97 @@ +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 ( + DimensionalArityError, + coupling, + geometry_from_declared_couplings, + has_declared_coupling, + observed_shared_ids, + space, +) + + +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"], ({"declared_ids": ("x",), "arity": 1},)) + + def test_binary_in_two_ambient_dimensions(self) -> None: + declared = space(["x", "y"], [["x", "y"]]) + self.assertEqual(declared.couplings[0].arity, 2) + + def test_ternary_only_when_declared(self) -> None: + declared = space(["x", "y", "z"], [["x", "y", "z"]]) + self.assertEqual(declared.couplings[0].arity, 3) + + def test_two_binaries_in_three_dimensions_do_not_create_xy_or_xyz(self) -> None: + declared = space(["x", "y", "z"], [["z", "x"], ["z", "y"]]) + geometry = geometry_from_declared_couplings(declared) + self.assertEqual(len(declared.ambient_dimensions), 3) + self.assertEqual(tuple(item.arity for item in declared.couplings), (2, 2)) + self.assertTrue(has_declared_coupling(declared, ["z", "x"])) + self.assertTrue(has_declared_coupling(declared, ["z", "y"])) + self.assertFalse(has_declared_coupling(declared, ["x", "y"])) + self.assertFalse(has_declared_coupling(declared, ["x", "y", "z"])) + self.assertFalse(has_declared_coupling(declared, ["z", "x", "y"])) + shares = geometry["observed_shares"] + self.assertEqual(len(shares), 1) + self.assertEqual(shares[0]["shared_ids"], ("z",)) + self.assertFalse(shares[0]["creates_higher_arity_coupling"]) + self.assertFalse(geometry["inferred_from_ambient"]) + self.assertFalse(geometry["inferred_higher_arity_from_overlap"]) + + 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["ambient_count"], 5) + self.assertEqual(geometry["couplings"], ()) + + 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) + self.assertEqual(len(declared.ambient_dimensions), 7) + + 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)) + + 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_declaration_sequence_is_preserved_without_order_semantics(self) -> None: + declared = space(["x", "z"], [["z", "x"], ["x", "z"]]) + self.assertEqual(declared.couplings[0].declared_ids, ("z", "x")) + self.assertEqual(declared.couplings[1].declared_ids, ("x", "z")) + self.assertNotEqual(declared.couplings[0].declared_ids, declared.couplings[1].declared_ids) + self.assertEqual(declared.couplings[0].member_ids, declared.couplings[1].member_ids) + geometry = geometry_from_declared_couplings(declared) + self.assertEqual(geometry["order_identity"], "hmmm") + + def test_overlap_observation_is_not_a_coupling(self) -> None: + zx = coupling(["z", "x"]) + zy = coupling(["z", "y"]) + self.assertEqual(observed_shared_ids(zx, zy), frozenset({"z"})) + self.assertEqual(zx.arity, 2) + self.assertEqual(zy.arity, 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index 2978612..8c697aa 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -36,6 +36,21 @@ def test_unpaired_valence_and_shells_are_used(self) -> None: self.assertTrue(carbon_dioxide["ligand_has_p"]) self.assertEqual(carbon_dioxide["center_unpaired_lm"], ["1:1", "1:0"]) + 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"]) + def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: molecules = construct_declared_molecules() signatures = {formula: item.invariants["ucns_coupling_signature"] for formula, item in molecules.items()} From d8e7039dd20f6a27e129c0cdb361525cbb4db760 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 18:06:46 +0000 Subject: [PATCH 07/21] Harden stack root lifecycle and manifest gates --- .github/workflows/stack-ci.yml | 27 ++++++ .gitignore | 1 + AGENTS.md | 32 +++++++ README.md | 47 ++++++---- STACK_MANIFEST.md | 54 ++++++++---- stack-manifest.json | 42 ++++++--- tools/check_stack_manifest.py | 152 +++++++++++++++++++++++++++++++++ 7 files changed, 311 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/stack-ci.yml create mode 100644 AGENTS.md create mode 100644 tools/check_stack_manifest.py diff --git a/.github/workflows/stack-ci.yml b/.github/workflows/stack-ci.yml new file mode 100644 index 0000000..1c92fec --- /dev/null +++ b/.github/workflows/stack-ci.yml @@ -0,0 +1,27 @@ +name: Stack CI + +on: + pull_request: + push: + branches: + - main + - "agent/**" + +jobs: + stack-root: + name: Stack root verification + runs-on: ubuntu-latest + steps: + - name: Check out stack + uses: actions/checkout@v7.0.1 + + - name: Set up Python + uses: actions/setup-python@v7.0.0 + with: + python-version: "3.12" + + - name: Verify stack manifest + run: python3 tools/check_stack_manifest.py + + - name: Check whitespace + run: git diff --check diff --git a/.gitignore b/.gitignore index c18dd8d..22cb37f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__/ +.skill-lib/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..07489ad --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# AGENTS.md + +Guidance for agents working in `The-Interdependency/stack`. + +## Stack role + +`stack` is the active research stack. Named repositories are retained as archive +provenance: keep their identities, pinned commits, authority boundaries, and license +status visible even when active work moves into this repository. + +Repo-level Public Gonol admission is downstream of research. Do not require a +participant's Public Gonol before construction; record closure and promotion only after +the research boundary is admissible. + +## Editing boundaries + +- Do not delete archived repository provenance. +- Do not edit participant research directories unless the task is explicitly scoped to + that participant. +- Keep root stack lifecycle, manifest, CI, and verification changes outside participant + research directories when possible. +- Before committing, run the stack manifest gate: + +```bash +python3 tools/check_stack_manifest.py +``` + +## hmmm + +- `research/epac/` is active provisional research with no external source archive yet. +- Root stack licensing is not fully settled because archived participants have mixed + license states. diff --git a/README.md b/README.md index b485119..e4e77dc 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ "This is how it comes together." -`The-Interdependency/stack` is the consolidated, provenance-bearing aggregation of the -organization's core repositories. It is an integration surface, not a source of truth: -each `research/` snapshot is pinned to an exact source commit, and canonical edits still -happen in the individual repositories. +`The-Interdependency/stack` is the consolidated, provenance-bearing research stack for the +organization's core work. The named repositories are retained as archive provenance: their +identities, source commits, boundaries, and licenses explain where each stack participant +came from. After a participant is archived into `stack`, stack-level research may continue +here without deleting that provenance. + +Repository-level Public Gonol admission is a promotion artifact. It happens after a body +of research is ready to close, not before construction starts. ## Layout @@ -16,7 +20,7 @@ stack/ │ ├── metapat/ # full snapshot — semantic authority (Meta Energy Theory) │ ├── ucns/ # full snapshot — geometry and mathematical representation │ ├── edcm/ # full snapshot — measurement and text-gonol construction -│ ├── epac/ # placeholder — energy particle affixiation coupling (no source yet) +│ ├── epac/ # stack-owned provisional energy particle affixiation coupling research │ ├── pcea/ # full snapshot — prime circle encryption algorithm │ └── ptcna/ # full snapshot — prime tensor circled neural architecture ├── libs/ # empty scaffolds reserved for consolidated library surfaces @@ -36,18 +40,20 @@ stack/ ## Usage guidance -- **Start at [`STACK_MANIFEST.md`](STACK_MANIFEST.md).** It pins the exact source commit, - branch, authority, snapshot path, and license status of every participant. -- **Read research sources in place.** `research//` contains that repository's full - tracked tree at the pinned commit (README, AGENTS/CLAUDE, source, tests, and docs). -- **Do not edit `research/` snapshots as doctrine.** Edit the canonical repository first, - then refresh this aggregation with the procedure in `STACK_MANIFEST.md`. +- **Start at [`STACK_MANIFEST.md`](STACK_MANIFEST.md).** It records archived source + identities, stack ownership, authority boundaries, snapshot paths, and license status. +- **Read and continue research in place.** `research//` contains archived source + material plus any stack-owned research deltas accepted after archival. +- **Preserve provenance.** Do not delete named repository identities just because their + active research has moved into `stack`; those names are the replay trail. +- **Do not require Public Gonols up front.** Construct candidate research first. Close and + promote a participant's Public Gonol only after the research has an admissible boundary. - **`libs/` is reserved, not implemented.** Its scaffolds mark where consolidated library surfaces will live; nothing in this repo depends on them yet. - **`backend/` and `frontend/cli/` are empty scaffolds** reserved for stack-level application work. -## Replaying a snapshot +## Archiving A Participant From a clean checkout of the source repository at the desired commit: @@ -56,12 +62,23 @@ git -C archive | tar -x -C research// ``` Then update `STACK_MANIFEST.md` and `stack-manifest.json`, recompute the work-graph digest, -and commit with the new source commit SHAs in the message. +and commit with the archived source commit SHAs in the message. After archival, stack-owned +research changes belong in this repository unless a participant is deliberately split back +out into its own active repo. + +## Verification + +```bash +python3 tools/check_stack_manifest.py +``` + +Stack CI runs this same root manifest gate plus `git diff --check`. ## hmmm +- Repo-level Public Gonol closure and promotion receipts are downstream of research. - Whether `libs/` should eventually contain package-only sources, built artifacts, or full vendored copies remains an open stack-integration decision. - The exact shape of `backend/` and `frontend/cli/` is not yet declared. -- `research/epac/` is a placeholder until an energy particle affixiation coupling source - repository exists. +- `research/epac/` is stack-owned provisional research until it is closed, promoted, or + deliberately split into a separate archived participant. diff --git a/STACK_MANIFEST.md b/STACK_MANIFEST.md index 1dbe3ae..10dbf7d 100644 --- a/STACK_MANIFEST.md +++ b/STACK_MANIFEST.md @@ -1,16 +1,17 @@ # STACK_MANIFEST.md -Provenance record for the consolidated snapshots in `The-Interdependency/stack`. +Provenance and lifecycle record for the consolidated research stack in +`The-Interdependency/stack`. -- Snapshot UTC: `2026-08-22T10:19:43Z` +- Snapshot UTC: `2026-08-22T12:20:36Z` - Stack-manifest schema: `the-interdependency.stack-manifest` version `1.0.0` - Work-graph digest (SHA-256 over canonical `repositories` + `boundaries` JSON): - `f7a56ead99174598fd37e044f25c88b5dd47f53f9e4836c41620781bc3e6494a` + `5cb76f4cea491b2c50ac009477c6df13482ede47f826e29e75f8f56a102cfabf` - Machine-readable copy: [`stack-manifest.json`](stack-manifest.json) ## Participants -| Repository | Exact source commit | Source branch | Authority | Snapshot path | +| Repository | Archived source commit | Source branch | Authority | Stack path | |---|---|---|---|---| | `The-Interdependency/skill-lib` | `fb3b53a7629f7f03ecf255167d52c13abef1a979` | main | organization-wide build and evidence doctrine | `skill-lib/` | | `The-Interdependency/metapat` | `34d954aa1e2092e615b03a180500f6b6977f501e` | main | semantic authority (Meta Energy Theory) | `research/metapat/` | @@ -18,11 +19,13 @@ Provenance record for the consolidated snapshots in `The-Interdependency/stack`. | `The-Interdependency/edcm` | `7951ca32ba0f2494dc68ff9b7f6a80151918a56d` | main | measurement and text-gonol construction | `research/edcm/` | | `The-Interdependency/pcea` | `4d2c581448b97bfb71da92b35487e74e6e3bcedc` | main | prime circle encryption algorithm | `research/pcea/` | | `The-Interdependency/ptcna` | `97abdd1bbda61a68e0aac8595a32a3cb0ce73487` | main | prime tensor circled neural architecture | `research/ptcna/` | -| `The-Interdependency/epac` | `hmmm` | — | placeholder scaffold for energy particle affixiation coupling; no source repository exists yet | `research/epac/` | +| `The-Interdependency/epac` | `hmmm` | stack-owned | provisional energy particle affixiation coupling research; no external source archive yet | `research/epac/` | -Each `research/` snapshot is the complete tracked working tree of its source repository at -the pinned commit, produced with `git archive HEAD`. VCS data, virtualenvs, caches, and -untracked files are excluded by construction. +For participants with an archived source commit, the stack path began as the complete tracked +working tree of its source repository at that commit, produced with `git archive HEAD`. VCS +data, virtualenvs, caches, and untracked files are excluded by construction. Stack-owned +research deltas may then continue in this repository while the archived source identity is +retained for replay and provenance. ## License status at snapshot commits @@ -34,19 +37,21 @@ untracked files are excluded by construction. | edcm | MPL-2.0 (`LICENSE`) | | pcea | present (`LICENSE`) | | ptcna | present (`LICENSE`) | -| epac | placeholder — no license yet | +| epac | stack-owned provisional research — root stack licensing unresolved | ## Non-transfer boundaries -- `authority_transfer: false` — snapshots do not gain authority over the source repositories. +- `authority_transfer: false` — archived repositories do not transfer theorem, measurement, + or promotion status merely by being archived into stack. - `proof_status_transfer: false` — no proof/theorem status transfers into this aggregation. - `measurement_status_transfer: false` — no measurement/empirical validity transfers. -- `semantic_mapping: external-provenance` — canonical meaning remains owned by the source - repositories; this repo only pins identities. -- `research/` snapshots are **not the source of truth**. Edit the canonical repositories - first, then refresh this snapshot. +- `semantic_mapping: stack-research-with-archive-provenance` — stack is the working research + surface while archived repository identities remain visible. +- `archive_provenance_required: true` — named repositories are kept because provenance matters. +- `public_gonol_timing: after-research-closure` — each participant's Public Gonol is closed + after research reaches an admissible boundary, not before construction. -## Refresh procedure +## Archive procedure For each pinned repository, from a clean checkout at the desired commit: @@ -55,12 +60,27 @@ git -C archive | tar -x -C research// ``` Then update this file and `stack-manifest.json`, recompute the work-graph digest, and -commit with a message citing the new source commit SHAs. +commit with a message citing the archived source commit SHAs. After archival, stack-owned +research may continue here unless the participant is deliberately split back out. + +## Verification + +Run the root verifier after manifest or lifecycle edits: + +```bash +python3 tools/check_stack_manifest.py +``` + +The verifier checks the machine manifest schema, declared paths, archive-status fields, +non-transfer boundaries, and digest agreement with this file. ## hmmm - ucns has no LICENSE file at snapshot commit `1975fe7`; its licensing terms need to be resolved at the source repository. -- epac has no source repository yet; its placeholder carries no doctrine until one exists. +- epac has no external source archive yet; its current material is stack-owned provisional + research and remains candidate status until closure. +- Repo-level Public Gonol closure receipts are not present yet; they occur after research, + before promotion. - `libs/`, `backend/`, and `frontend/cli/` are intentional empty scaffolds reserved for later stack integration work. diff --git a/stack-manifest.json b/stack-manifest.json index e357602..bbfc640 100644 --- a/stack-manifest.json +++ b/stack-manifest.json @@ -1,61 +1,79 @@ { "schema": "the-interdependency.stack-manifest", "version": "1.0.0", - "work_graph_sha256": "f7a56ead99174598fd37e044f25c88b5dd47f53f9e4836c41620781bc3e6494a", + "work_graph_sha256": "5cb76f4cea491b2c50ac009477c6df13482ede47f826e29e75f8f56a102cfabf", "repositories": [ { "repository": "The-Interdependency/skill-lib", "commit": "fb3b53a7629f7f03ecf255167d52c13abef1a979", "authority": "organization-wide build and evidence doctrine", - "relation": "full snapshot at stack root skill-lib/" + "path": "skill-lib", + "relation": "archived source tree at stack root skill-lib/", + "archive_status": "archived-source-tree" }, { "repository": "The-Interdependency/metapat", "commit": "34d954aa1e2092e615b03a180500f6b6977f501e", "authority": "semantic authority (Meta Energy Theory)", - "relation": "full snapshot at research/metapat/" + "path": "research/metapat", + "relation": "archived source tree at research/metapat/", + "archive_status": "archived-source-tree" }, { "repository": "The-Interdependency/ucns", "commit": "1975fe70cf4e0826a8020c2da3047569e277af64", "authority": "geometry and mathematical representation", - "relation": "full snapshot at research/ucns/" + "path": "research/ucns", + "relation": "archived source tree at research/ucns/", + "archive_status": "archived-source-tree" }, { "repository": "The-Interdependency/edcm", "commit": "7951ca32ba0f2494dc68ff9b7f6a80151918a56d", "authority": "measurement and text-gonol construction", - "relation": "full snapshot at research/edcm/" + "path": "research/edcm", + "relation": "archived source tree at research/edcm/", + "archive_status": "archived-source-tree" }, { "repository": "The-Interdependency/pcea", "commit": "4d2c581448b97bfb71da92b35487e74e6e3bcedc", "authority": "prime circle encryption algorithm", - "relation": "full snapshot at research/pcea/" + "path": "research/pcea", + "relation": "archived source tree at research/pcea/", + "archive_status": "archived-source-tree" }, { "repository": "The-Interdependency/ptcna", "commit": "97abdd1bbda61a68e0aac8595a32a3cb0ce73487", "authority": "prime tensor circled neural architecture", - "relation": "full snapshot at research/ptcna/" + "path": "research/ptcna", + "relation": "archived source tree at research/ptcna/", + "archive_status": "archived-source-tree" }, { "repository": "The-Interdependency/epac", "commit": "hmmm", - "authority": "placeholder scaffold for energy particle affixiation coupling; no source repository exists yet", - "relation": "placeholder at research/epac/" + "authority": "provisional energy particle affixiation coupling research; no external source archive yet", + "path": "research/epac", + "relation": "stack-owned provisional research at research/epac/", + "archive_status": "stack-owned-provisional-research" } ], "boundaries": { "authority_transfer": false, "proof_status_transfer": false, "measurement_status_transfer": false, - "semantic_mapping": "external-provenance", + "semantic_mapping": "stack-research-with-archive-provenance", "agent_scope": "cross-repository-work-graph", + "archive_provenance_required": true, + "source_repositories_after_archive": "archived-provenance-records", + "public_gonol_timing": "after-research-closure", + "promotion_requires_public_gonol": true, "hmmm": [ "ucns has no LICENSE file at snapshot commit 1975fe7", - "epac is a placeholder with no source repository", - "research/ snapshots are not source of truth; canonical sources remain The-Interdependency/" + "epac has no external source archive yet", + "repo-level Public Gonol closure receipts are downstream of research" ] } } diff --git a/tools/check_stack_manifest.py b/tools/check_stack_manifest.py new file mode 100644 index 0000000..fc31c3d --- /dev/null +++ b/tools/check_stack_manifest.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Validate the stack manifest and root lifecycle contract. + +This verifier is intentionally offline. It does not fetch archived repositories or +interpret participant research. It checks that the stack's root provenance record is +internally consistent and that declared stack paths exist. +""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +import re +import sys +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +MANIFEST_PATH = ROOT / "stack-manifest.json" +HUMAN_MANIFEST_PATH = ROOT / "STACK_MANIFEST.md" + +SCHEMA = "the-interdependency.stack-manifest" +VERSION = "1.0.0" +HEX40 = re.compile(r"^[0-9a-f]{40}$") + + +def _fail(message: str) -> None: + raise SystemExit(f"stack manifest check failed: {message}") + + +def _load_manifest() -> dict[str, Any]: + try: + data = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) + except json.JSONDecodeError as error: + _fail(f"{MANIFEST_PATH.name} is not valid JSON: {error}") + if not isinstance(data, dict): + _fail("manifest root must be an object") + return data + + +def _canonical_digest(manifest: dict[str, Any]) -> str: + payload = { + "repositories": manifest.get("repositories"), + "boundaries": manifest.get("boundaries"), + } + encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def _require_text(mapping: dict[str, Any], key: str, *, context: str) -> str: + value = mapping.get(key) + if not isinstance(value, str) or not value.strip(): + _fail(f"{context}.{key} must be non-empty text") + return value + + +def _validate_repository(entry: Any, seen: set[str]) -> None: + if not isinstance(entry, dict): + _fail("every repositories[] entry must be an object") + + repository = _require_text(entry, "repository", context="repository") + if repository in seen: + _fail(f"duplicate repository entry: {repository}") + seen.add(repository) + if not repository.startswith("The-Interdependency/"): + _fail(f"repository is outside The-Interdependency: {repository}") + + commit = _require_text(entry, "commit", context=repository) + authority = _require_text(entry, "authority", context=repository) + relation = _require_text(entry, "relation", context=repository) + archive_status = _require_text(entry, "archive_status", context=repository) + path_text = _require_text(entry, "path", context=repository) + + if "\n" in authority or "\n" in relation: + _fail(f"{repository} authority/relation must be single-line text") + + if archive_status == "archived-source-tree": + if not HEX40.fullmatch(commit): + _fail(f"{repository} archived source commit must be a 40-hex SHA") + if "archived source tree" not in relation: + _fail(f"{repository} relation must describe an archived source tree") + elif archive_status == "stack-owned-provisional-research": + if commit != "hmmm": + _fail(f"{repository} provisional stack-owned research must use commit hmmm") + else: + _fail(f"{repository} has unknown archive_status {archive_status!r}") + + path = ROOT / path_text + if path_text.startswith("/") or ".." in Path(path_text).parts: + _fail(f"{repository} path must be repository-relative") + if not path.exists(): + _fail(f"{repository} path does not exist: {path_text}") + if (path / ".git").exists(): + _fail(f"{repository} path must not contain nested VCS metadata: {path_text}") + + +def _validate_boundaries(boundaries: Any) -> None: + if not isinstance(boundaries, dict): + _fail("boundaries must be an object") + expected_false = ( + "authority_transfer", + "proof_status_transfer", + "measurement_status_transfer", + ) + for key in expected_false: + if boundaries.get(key) is not False: + _fail(f"boundaries.{key} must be false") + if boundaries.get("archive_provenance_required") is not True: + _fail("boundaries.archive_provenance_required must be true") + if boundaries.get("promotion_requires_public_gonol") is not True: + _fail("boundaries.promotion_requires_public_gonol must be true") + if boundaries.get("public_gonol_timing") != "after-research-closure": + _fail("boundaries.public_gonol_timing must be after-research-closure") + if boundaries.get("semantic_mapping") != "stack-research-with-archive-provenance": + _fail("boundaries.semantic_mapping has unexpected value") + hmmm = boundaries.get("hmmm") + if not isinstance(hmmm, list) or not all(isinstance(item, str) and item for item in hmmm): + _fail("boundaries.hmmm must be a non-empty text list") + + +def main() -> int: + manifest = _load_manifest() + if manifest.get("schema") != SCHEMA: + _fail(f"schema must be {SCHEMA}") + if manifest.get("version") != VERSION: + _fail(f"version must be {VERSION}") + + repositories = manifest.get("repositories") + if not isinstance(repositories, list) or not repositories: + _fail("repositories must be a non-empty list") + seen: set[str] = set() + for entry in repositories: + _validate_repository(entry, seen) + + _validate_boundaries(manifest.get("boundaries")) + + computed = _canonical_digest(manifest) + declared = manifest.get("work_graph_sha256") + if declared != computed: + _fail(f"work_graph_sha256 mismatch: declared {declared!r}, computed {computed}") + + human = HUMAN_MANIFEST_PATH.read_text(encoding="utf-8") + if computed not in human: + _fail("STACK_MANIFEST.md does not contain the computed digest") + + print(f"stack manifest ok: {computed}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From c6e02154db1da225be8835207e2bb1f260b3b1ef Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 18:11:11 +0000 Subject: [PATCH 08/21] Encode oriented couplings and degree relations (z, x) is not (x, z). (x, z) and (y, z) do not yield (x, y, z) without an explicit non-overlap proof. Degree records incidence and slot of each dimension on declared couplings so shared-axis binaries remain binaries. --- research/epac/docs/arity.md | 28 +-- research/epac/epac_dimensional_arity.py | 235 +++++++++++++++++------- research/epac/tests/test_epac_arity.py | 112 +++++++---- 3 files changed, 256 insertions(+), 119 deletions(-) diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md index 67c60f1..2d0968f 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -2,24 +2,26 @@ Status: **CROSS-DOMAIN-HYPOTHESIS / provisional**. Not org canon. -Dimension tells where. Arity tells what intersects at once. +Dimension tells where. Arity tells what intersects at once. Degree tells how a +dimension is incident on declared couplings. ```text -ambient_dimensions: declared independent axes -couplings: only explicitly declared intersections -Coupling.arity = number of dimensions in that one coupling +(z, x) ≠ (x, z) +(x, z) and (y, z) ↛ (x, y, z) without an explicit proof ``` -Ambient size and arity are independent. Three ambient dimensions do not create -a ternary coupling. `{z,x}` and `{z,y}` remain two arity-2 declarations that -share `z`. They do not declare `{x,y}` or `{x,y,z}`. +Degree is required. For ambient `{x,y,z}` with couplings `(z,x)` and `(z,y)`: -Geometry is generated from declared couplings, not from the power set of -available axes. The same ambient space may hold mixed arities. There is no -x/y/z special case; ids may be `d1…dk`. +```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)`. -`hmmm`: whether `{x,z}` and `{z,x}` are the same coupling or oriented -declarations is undeclared. The implementation stores declaration sequence -only. +Overlap of members is not a proof. Forbidden inference rules include +`overlap-closure`, `permutation-identity`, and `ambient-power-set`. See `epac_dimensional_arity.py`. diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index 1cb2933..0a81102 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -1,39 +1,20 @@ -"""Declared dimensional arity. - -Dimension tells where. Arity tells what intersects at once. - -Ambient dimension count and coupling arity are independent. Geometry is -generated only from explicitly declared couplings. Overlapping lower-arity -couplings do not create a higher-arity coupling. - -Domain claims (provisional, this candidate): - -- surface_form: dimension - term_id: epac.dimensional.dimension - claiming_domain: epac candidate - claimed_sense: an independent coordinate axis in a declared ambient space - excluded: coupling arity; participant count; x/y/z as the general model - -- surface_form: arity - term_id: epac.dimensional.arity - claiming_domain: epac candidate - claimed_sense: number of dimensions participating in one declared coupling - excluded: ambient dimension count; gonol participant-count policies - (edcm.gonol arity_policy is a different sense) - -- surface_form: coupling - term_id: epac.dimensional.coupling - claiming_domain: epac candidate - claimed_sense: one explicit intersection of exactly k declared dimensions - excluded: the power set of ambient dimensions; inferred closures - -Collision: edcm.gonol ScaleOptionSet.arity_policy counts closed gonol -participants, not dimensional intersections. Resolution: different term ids. - -hmmm: whether a coupling is intrinsically unordered ({x,z} = {z,x}) or may -carry orientation (zx ≠ xz) is undeclared. This module stores the declared -sequence as declaration identity only. It does not quotient by permutation -and does not treat order as geometric orientation. +"""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. + +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 + +Collision: edcm.gonol arity_policy counts gonol participants, not dimensional +intersections. """ from __future__ import annotations @@ -42,6 +23,16 @@ from typing import Iterable, Mapping, Sequence +FORBIDDEN_INFERENCE_RULES = frozenset( + { + "ambient-power-set", + "overlap-closure", + "permutation-identity", + "shared-dimension-join", + } +) + + class DimensionalArityError(ValueError): """Fail-closed dimensional arity error.""" @@ -59,7 +50,7 @@ def __post_init__(self) -> None: @dataclass(frozen=True, slots=True) class Coupling: - """One explicitly declared intersection of dimensions.""" + """One explicitly declared ordered intersection of dimensions.""" dimensions: tuple[Dimension, ...] @@ -78,31 +69,75 @@ def arity(self) -> int: def declared_ids(self) -> tuple[str, ...]: return tuple(dimension.id for dimension in self.dimensions) + +@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 member_ids(self) -> frozenset[str]: - """Membership view only. Not geometric identity.""" + 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.""" - return frozenset(self.declared_ids) + 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 plus only those couplings that were declared.""" + """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 coupling in self.couplings: - missing = [item for item in coupling.declared_ids if item not in ambient] + for item in self.couplings: + missing = [name for name in item.declared_ids if name not in ambient] if missing: raise DimensionalArityError( - f"coupling {coupling.declared_ids} uses undeclared dimensions {tuple(missing)}" + f"coupling {item.declared_ids} uses undeclared dimensions {tuple(missing)}" ) + declared = {item.declared_ids for item in self.couplings} + for proof in self.proofs: + 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 dimension(id: str) -> Dimension: @@ -118,6 +153,7 @@ def coupling(dimension_ids: Sequence[str]) -> Coupling: def space( ambient_ids: Sequence[str], coupling_declarations: Sequence[Sequence[str]] = (), + proofs: Sequence[CouplingProof] = (), ) -> DimensionalSpace: if not isinstance(ambient_ids, Sequence) or isinstance(ambient_ids, (str, bytes)): raise DimensionalArityError("ambient dimensions must be a declared sequence") @@ -125,33 +161,99 @@ def space( return DimensionalSpace( ambient_dimensions=tuple(Dimension(item) for item in ambient_ids), couplings=declared, + proofs=tuple(proofs), ) -def observed_shared_ids(left: Coupling, right: Coupling) -> frozenset[str]: - """Shared members of two declarations. This is not a new coupling.""" +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 + ) + - return left.member_ids & right.member_ids +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 geometry_from_declared_couplings(declared: DimensionalSpace) -> Mapping[str, object]: - """Geometry is the declared couplings, not the power set of ambient axes.""" +def has_declared_coupling(declared: DimensionalSpace, dimension_ids: Sequence[str]) -> bool: + target = tuple(dimension_ids) + return any(item.declared_ids == target for item in declared.couplings) + + +def install_proven_coupling(declared: DimensionalSpace, proof: CouplingProof) -> DimensionalSpace: + """Add a coupling only with an explicit non-forbidden proof.""" + + if proof.conclusion.declared_ids in {item.declared_ids for item in declared.couplings}: + return DimensionalSpace( + ambient_dimensions=declared.ambient_dimensions, + couplings=declared.couplings, + proofs=declared.proofs + (proof,), + ) + missing = [ + name + for name in proof.conclusion.declared_ids + if name not in {axis.id for axis in declared.ambient_dimensions} + ] + if missing: + raise DimensionalArityError( + f"proven coupling {proof.conclusion.declared_ids} uses undeclared dimensions {tuple(missing)}" + ) + declared_ids = {item.declared_ids for item in declared.couplings} + for premise in proof.premises: + if premise.declared_ids not in declared_ids: + raise DimensionalArityError( + f"proof {proof.rule_id!r} cites missing premise {premise.declared_ids}" + ) + return DimensionalSpace( + ambient_dimensions=declared.ambient_dimensions, + couplings=declared.couplings + (proof.conclusion,), + proofs=declared.proofs + (proof,), + ) + + +def geometry_from_declared_couplings(declared: DimensionalSpace) -> Mapping[str, object]: + degrees = degree_relations(declared) return { "ambient_ids": tuple(item.id for item in declared.ambient_dimensions), "ambient_count": len(declared.ambient_dimensions), "couplings": tuple( { - "declared_ids": coupling.declared_ids, - "arity": coupling.arity, + "declared_ids": item.declared_ids, + "arity": item.arity, } - for coupling in declared.couplings + for item in declared.couplings ), "arity_counts": _arity_counts(declared.couplings), - "observed_shares": tuple(_share_records(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, - "order_identity": "hmmm", + "zx_equals_xz": False, } @@ -162,40 +264,35 @@ def _arity_counts(couplings: tuple[Coupling, ...]) -> tuple[tuple[int, int], ... return tuple(sorted(counts.items())) -def _share_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, object]]: +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_shared_ids(left, right) + shared = observed_common_ids(left, right) if shared: yield { "left": left.declared_ids, "right": right.declared_ids, - "shared_ids": tuple(sorted(shared)), - "creates_higher_arity_coupling": False, + "common_ids": tuple(sorted(shared)), + "proof_of_higher_arity": False, } -def has_declared_coupling(declared: DimensionalSpace, dimension_ids: Sequence[str]) -> bool: - """True only if this exact declaration sequence is present. - - Permuted declarations are not treated as the same or as different geometry. - """ - - target = tuple(dimension_ids) - return any(item.declared_ids == target for item in declared.couplings) - - __all__ = [ "Coupling", + "CouplingProof", + "DegreeRelation", "Dimension", "DimensionalArityError", "DimensionalSpace", + "FORBIDDEN_INFERENCE_RULES", "coupling", + "degree_relations", "dimension", "geometry_from_declared_couplings", "has_declared_coupling", - "observed_shared_ids", + "install_proven_coupling", + "observed_common_ids", "space", ] diff --git a/research/epac/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py index e9a7a2f..5dc29d9 100644 --- a/research/epac/tests/test_epac_arity.py +++ b/research/epac/tests/test_epac_arity.py @@ -8,11 +8,14 @@ sys.path.insert(0, str(EPAC_ROOT)) from epac_dimensional_arity import ( + CouplingProof, DimensionalArityError, coupling, + degree_relations, geometry_from_declared_couplings, has_declared_coupling, - observed_shared_ids, + install_proven_coupling, + observed_common_ids, space, ) @@ -23,43 +26,85 @@ def test_unary_in_one_ambient_dimension(self) -> None: geometry = geometry_from_declared_couplings(declared) self.assertEqual(geometry["ambient_count"], 1) self.assertEqual(geometry["couplings"], ({"declared_ids": ("x",), "arity": 1},)) + self.assertEqual(geometry["degree_relations"][0]["degree"], 1) - def test_binary_in_two_ambient_dimensions(self) -> None: - declared = space(["x", "y"], [["x", "y"]]) - self.assertEqual(declared.couplings[0].arity, 2) + def test_zx_is_not_xz(self) -> None: + declared = space(["x", "z"], [["z", "x"]]) + self.assertTrue(has_declared_coupling(declared, ["z", "x"])) + self.assertFalse(has_declared_coupling(declared, ["x", "z"])) + self.assertNotEqual(coupling(["z", "x"]), coupling(["x", "z"])) + geometry = geometry_from_declared_couplings(declared) + self.assertFalse(geometry["zx_equals_xz"]) + 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"]]) + 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"]) + 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),)) + + 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", + ) + self.assertFalse(has_declared_coupling(declared, ["x", "y", "z"])) - def test_ternary_only_when_declared(self) -> None: - declared = space(["x", "y", "z"], [["x", "y", "z"]]) - self.assertEqual(declared.couplings[0].arity, 3) + 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_two_binaries_in_three_dimensions_do_not_create_xy_or_xyz(self) -> None: + def test_zx_and_zy_degree_has_z_in_slot_zero_twice(self) -> None: declared = space(["x", "y", "z"], [["z", "x"], ["z", "y"]]) - geometry = geometry_from_declared_couplings(declared) - self.assertEqual(len(declared.ambient_dimensions), 3) - self.assertEqual(tuple(item.arity for item in declared.couplings), (2, 2)) - self.assertTrue(has_declared_coupling(declared, ["z", "x"])) - self.assertTrue(has_declared_coupling(declared, ["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"])) - self.assertFalse(has_declared_coupling(declared, ["z", "x", "y"])) - shares = geometry["observed_shares"] - self.assertEqual(len(shares), 1) - self.assertEqual(shares[0]["shared_ids"], ("z",)) - self.assertFalse(shares[0]["creates_higher_arity_coupling"]) - self.assertFalse(geometry["inferred_from_ambient"]) - self.assertFalse(geometry["inferred_higher_arity_from_overlap"]) 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["ambient_count"], 5) 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) - self.assertEqual(len(declared.ambient_dimensions), 7) + 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( @@ -67,6 +112,9 @@ def test_mixed_arities_in_one_ambient_space(self) -> None: [["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"): @@ -76,21 +124,11 @@ def test_coupling_cannot_repeat_a_dimension(self) -> None: with self.assertRaisesRegex(DimensionalArityError, "cannot repeat"): coupling(["d1", "d1"]) - def test_declaration_sequence_is_preserved_without_order_semantics(self) -> None: - declared = space(["x", "z"], [["z", "x"], ["x", "z"]]) - self.assertEqual(declared.couplings[0].declared_ids, ("z", "x")) - self.assertEqual(declared.couplings[1].declared_ids, ("x", "z")) - self.assertNotEqual(declared.couplings[0].declared_ids, declared.couplings[1].declared_ids) - self.assertEqual(declared.couplings[0].member_ids, declared.couplings[1].member_ids) - geometry = geometry_from_declared_couplings(declared) - self.assertEqual(geometry["order_identity"], "hmmm") - - def test_overlap_observation_is_not_a_coupling(self) -> None: - zx = coupling(["z", "x"]) - zy = coupling(["z", "y"]) - self.assertEqual(observed_shared_ids(zx, zy), frozenset({"z"})) - self.assertEqual(zx.arity, 2) - self.assertEqual(zy.arity, 2) + 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) if __name__ == "__main__": From a41bbd3ce688d0148962193c1f0e1b977c750599 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 18:12:45 +0000 Subject: [PATCH 09/21] Revert "Harden stack root lifecycle and manifest gates" This reverts commit d8e7039dd20f6a27e129c0cdb361525cbb4db760. --- .github/workflows/stack-ci.yml | 27 ------ .gitignore | 1 - AGENTS.md | 32 ------- README.md | 47 ++++------ STACK_MANIFEST.md | 54 ++++-------- stack-manifest.json | 42 +++------ tools/check_stack_manifest.py | 152 --------------------------------- 7 files changed, 44 insertions(+), 311 deletions(-) delete mode 100644 .github/workflows/stack-ci.yml delete mode 100644 AGENTS.md delete mode 100644 tools/check_stack_manifest.py diff --git a/.github/workflows/stack-ci.yml b/.github/workflows/stack-ci.yml deleted file mode 100644 index 1c92fec..0000000 --- a/.github/workflows/stack-ci.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Stack CI - -on: - pull_request: - push: - branches: - - main - - "agent/**" - -jobs: - stack-root: - name: Stack root verification - runs-on: ubuntu-latest - steps: - - name: Check out stack - uses: actions/checkout@v7.0.1 - - - name: Set up Python - uses: actions/setup-python@v7.0.0 - with: - python-version: "3.12" - - - name: Verify stack manifest - run: python3 tools/check_stack_manifest.py - - - name: Check whitespace - run: git diff --check diff --git a/.gitignore b/.gitignore index 22cb37f..c18dd8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ __pycache__/ -.skill-lib/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 07489ad..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,32 +0,0 @@ -# AGENTS.md - -Guidance for agents working in `The-Interdependency/stack`. - -## Stack role - -`stack` is the active research stack. Named repositories are retained as archive -provenance: keep their identities, pinned commits, authority boundaries, and license -status visible even when active work moves into this repository. - -Repo-level Public Gonol admission is downstream of research. Do not require a -participant's Public Gonol before construction; record closure and promotion only after -the research boundary is admissible. - -## Editing boundaries - -- Do not delete archived repository provenance. -- Do not edit participant research directories unless the task is explicitly scoped to - that participant. -- Keep root stack lifecycle, manifest, CI, and verification changes outside participant - research directories when possible. -- Before committing, run the stack manifest gate: - -```bash -python3 tools/check_stack_manifest.py -``` - -## hmmm - -- `research/epac/` is active provisional research with no external source archive yet. -- Root stack licensing is not fully settled because archived participants have mixed - license states. diff --git a/README.md b/README.md index e4e77dc..b485119 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,10 @@ "This is how it comes together." -`The-Interdependency/stack` is the consolidated, provenance-bearing research stack for the -organization's core work. The named repositories are retained as archive provenance: their -identities, source commits, boundaries, and licenses explain where each stack participant -came from. After a participant is archived into `stack`, stack-level research may continue -here without deleting that provenance. - -Repository-level Public Gonol admission is a promotion artifact. It happens after a body -of research is ready to close, not before construction starts. +`The-Interdependency/stack` is the consolidated, provenance-bearing aggregation of the +organization's core repositories. It is an integration surface, not a source of truth: +each `research/` snapshot is pinned to an exact source commit, and canonical edits still +happen in the individual repositories. ## Layout @@ -20,7 +16,7 @@ stack/ │ ├── metapat/ # full snapshot — semantic authority (Meta Energy Theory) │ ├── ucns/ # full snapshot — geometry and mathematical representation │ ├── edcm/ # full snapshot — measurement and text-gonol construction -│ ├── epac/ # stack-owned provisional energy particle affixiation coupling research +│ ├── epac/ # placeholder — energy particle affixiation coupling (no source yet) │ ├── pcea/ # full snapshot — prime circle encryption algorithm │ └── ptcna/ # full snapshot — prime tensor circled neural architecture ├── libs/ # empty scaffolds reserved for consolidated library surfaces @@ -40,20 +36,18 @@ stack/ ## Usage guidance -- **Start at [`STACK_MANIFEST.md`](STACK_MANIFEST.md).** It records archived source - identities, stack ownership, authority boundaries, snapshot paths, and license status. -- **Read and continue research in place.** `research//` contains archived source - material plus any stack-owned research deltas accepted after archival. -- **Preserve provenance.** Do not delete named repository identities just because their - active research has moved into `stack`; those names are the replay trail. -- **Do not require Public Gonols up front.** Construct candidate research first. Close and - promote a participant's Public Gonol only after the research has an admissible boundary. +- **Start at [`STACK_MANIFEST.md`](STACK_MANIFEST.md).** It pins the exact source commit, + branch, authority, snapshot path, and license status of every participant. +- **Read research sources in place.** `research//` contains that repository's full + tracked tree at the pinned commit (README, AGENTS/CLAUDE, source, tests, and docs). +- **Do not edit `research/` snapshots as doctrine.** Edit the canonical repository first, + then refresh this aggregation with the procedure in `STACK_MANIFEST.md`. - **`libs/` is reserved, not implemented.** Its scaffolds mark where consolidated library surfaces will live; nothing in this repo depends on them yet. - **`backend/` and `frontend/cli/` are empty scaffolds** reserved for stack-level application work. -## Archiving A Participant +## Replaying a snapshot From a clean checkout of the source repository at the desired commit: @@ -62,23 +56,12 @@ git -C archive | tar -x -C research// ``` Then update `STACK_MANIFEST.md` and `stack-manifest.json`, recompute the work-graph digest, -and commit with the archived source commit SHAs in the message. After archival, stack-owned -research changes belong in this repository unless a participant is deliberately split back -out into its own active repo. - -## Verification - -```bash -python3 tools/check_stack_manifest.py -``` - -Stack CI runs this same root manifest gate plus `git diff --check`. +and commit with the new source commit SHAs in the message. ## hmmm -- Repo-level Public Gonol closure and promotion receipts are downstream of research. - Whether `libs/` should eventually contain package-only sources, built artifacts, or full vendored copies remains an open stack-integration decision. - The exact shape of `backend/` and `frontend/cli/` is not yet declared. -- `research/epac/` is stack-owned provisional research until it is closed, promoted, or - deliberately split into a separate archived participant. +- `research/epac/` is a placeholder until an energy particle affixiation coupling source + repository exists. diff --git a/STACK_MANIFEST.md b/STACK_MANIFEST.md index 10dbf7d..1dbe3ae 100644 --- a/STACK_MANIFEST.md +++ b/STACK_MANIFEST.md @@ -1,17 +1,16 @@ # STACK_MANIFEST.md -Provenance and lifecycle record for the consolidated research stack in -`The-Interdependency/stack`. +Provenance record for the consolidated snapshots in `The-Interdependency/stack`. -- Snapshot UTC: `2026-08-22T12:20:36Z` +- Snapshot UTC: `2026-08-22T10:19:43Z` - Stack-manifest schema: `the-interdependency.stack-manifest` version `1.0.0` - Work-graph digest (SHA-256 over canonical `repositories` + `boundaries` JSON): - `5cb76f4cea491b2c50ac009477c6df13482ede47f826e29e75f8f56a102cfabf` + `f7a56ead99174598fd37e044f25c88b5dd47f53f9e4836c41620781bc3e6494a` - Machine-readable copy: [`stack-manifest.json`](stack-manifest.json) ## Participants -| Repository | Archived source commit | Source branch | Authority | Stack path | +| Repository | Exact source commit | Source branch | Authority | Snapshot path | |---|---|---|---|---| | `The-Interdependency/skill-lib` | `fb3b53a7629f7f03ecf255167d52c13abef1a979` | main | organization-wide build and evidence doctrine | `skill-lib/` | | `The-Interdependency/metapat` | `34d954aa1e2092e615b03a180500f6b6977f501e` | main | semantic authority (Meta Energy Theory) | `research/metapat/` | @@ -19,13 +18,11 @@ Provenance and lifecycle record for the consolidated research stack in | `The-Interdependency/edcm` | `7951ca32ba0f2494dc68ff9b7f6a80151918a56d` | main | measurement and text-gonol construction | `research/edcm/` | | `The-Interdependency/pcea` | `4d2c581448b97bfb71da92b35487e74e6e3bcedc` | main | prime circle encryption algorithm | `research/pcea/` | | `The-Interdependency/ptcna` | `97abdd1bbda61a68e0aac8595a32a3cb0ce73487` | main | prime tensor circled neural architecture | `research/ptcna/` | -| `The-Interdependency/epac` | `hmmm` | stack-owned | provisional energy particle affixiation coupling research; no external source archive yet | `research/epac/` | +| `The-Interdependency/epac` | `hmmm` | — | placeholder scaffold for energy particle affixiation coupling; no source repository exists yet | `research/epac/` | -For participants with an archived source commit, the stack path began as the complete tracked -working tree of its source repository at that commit, produced with `git archive HEAD`. VCS -data, virtualenvs, caches, and untracked files are excluded by construction. Stack-owned -research deltas may then continue in this repository while the archived source identity is -retained for replay and provenance. +Each `research/` snapshot is the complete tracked working tree of its source repository at +the pinned commit, produced with `git archive HEAD`. VCS data, virtualenvs, caches, and +untracked files are excluded by construction. ## License status at snapshot commits @@ -37,21 +34,19 @@ retained for replay and provenance. | edcm | MPL-2.0 (`LICENSE`) | | pcea | present (`LICENSE`) | | ptcna | present (`LICENSE`) | -| epac | stack-owned provisional research — root stack licensing unresolved | +| epac | placeholder — no license yet | ## Non-transfer boundaries -- `authority_transfer: false` — archived repositories do not transfer theorem, measurement, - or promotion status merely by being archived into stack. +- `authority_transfer: false` — snapshots do not gain authority over the source repositories. - `proof_status_transfer: false` — no proof/theorem status transfers into this aggregation. - `measurement_status_transfer: false` — no measurement/empirical validity transfers. -- `semantic_mapping: stack-research-with-archive-provenance` — stack is the working research - surface while archived repository identities remain visible. -- `archive_provenance_required: true` — named repositories are kept because provenance matters. -- `public_gonol_timing: after-research-closure` — each participant's Public Gonol is closed - after research reaches an admissible boundary, not before construction. +- `semantic_mapping: external-provenance` — canonical meaning remains owned by the source + repositories; this repo only pins identities. +- `research/` snapshots are **not the source of truth**. Edit the canonical repositories + first, then refresh this snapshot. -## Archive procedure +## Refresh procedure For each pinned repository, from a clean checkout at the desired commit: @@ -60,27 +55,12 @@ git -C archive | tar -x -C research// ``` Then update this file and `stack-manifest.json`, recompute the work-graph digest, and -commit with a message citing the archived source commit SHAs. After archival, stack-owned -research may continue here unless the participant is deliberately split back out. - -## Verification - -Run the root verifier after manifest or lifecycle edits: - -```bash -python3 tools/check_stack_manifest.py -``` - -The verifier checks the machine manifest schema, declared paths, archive-status fields, -non-transfer boundaries, and digest agreement with this file. +commit with a message citing the new source commit SHAs. ## hmmm - ucns has no LICENSE file at snapshot commit `1975fe7`; its licensing terms need to be resolved at the source repository. -- epac has no external source archive yet; its current material is stack-owned provisional - research and remains candidate status until closure. -- Repo-level Public Gonol closure receipts are not present yet; they occur after research, - before promotion. +- epac has no source repository yet; its placeholder carries no doctrine until one exists. - `libs/`, `backend/`, and `frontend/cli/` are intentional empty scaffolds reserved for later stack integration work. diff --git a/stack-manifest.json b/stack-manifest.json index bbfc640..e357602 100644 --- a/stack-manifest.json +++ b/stack-manifest.json @@ -1,79 +1,61 @@ { "schema": "the-interdependency.stack-manifest", "version": "1.0.0", - "work_graph_sha256": "5cb76f4cea491b2c50ac009477c6df13482ede47f826e29e75f8f56a102cfabf", + "work_graph_sha256": "f7a56ead99174598fd37e044f25c88b5dd47f53f9e4836c41620781bc3e6494a", "repositories": [ { "repository": "The-Interdependency/skill-lib", "commit": "fb3b53a7629f7f03ecf255167d52c13abef1a979", "authority": "organization-wide build and evidence doctrine", - "path": "skill-lib", - "relation": "archived source tree at stack root skill-lib/", - "archive_status": "archived-source-tree" + "relation": "full snapshot at stack root skill-lib/" }, { "repository": "The-Interdependency/metapat", "commit": "34d954aa1e2092e615b03a180500f6b6977f501e", "authority": "semantic authority (Meta Energy Theory)", - "path": "research/metapat", - "relation": "archived source tree at research/metapat/", - "archive_status": "archived-source-tree" + "relation": "full snapshot at research/metapat/" }, { "repository": "The-Interdependency/ucns", "commit": "1975fe70cf4e0826a8020c2da3047569e277af64", "authority": "geometry and mathematical representation", - "path": "research/ucns", - "relation": "archived source tree at research/ucns/", - "archive_status": "archived-source-tree" + "relation": "full snapshot at research/ucns/" }, { "repository": "The-Interdependency/edcm", "commit": "7951ca32ba0f2494dc68ff9b7f6a80151918a56d", "authority": "measurement and text-gonol construction", - "path": "research/edcm", - "relation": "archived source tree at research/edcm/", - "archive_status": "archived-source-tree" + "relation": "full snapshot at research/edcm/" }, { "repository": "The-Interdependency/pcea", "commit": "4d2c581448b97bfb71da92b35487e74e6e3bcedc", "authority": "prime circle encryption algorithm", - "path": "research/pcea", - "relation": "archived source tree at research/pcea/", - "archive_status": "archived-source-tree" + "relation": "full snapshot at research/pcea/" }, { "repository": "The-Interdependency/ptcna", "commit": "97abdd1bbda61a68e0aac8595a32a3cb0ce73487", "authority": "prime tensor circled neural architecture", - "path": "research/ptcna", - "relation": "archived source tree at research/ptcna/", - "archive_status": "archived-source-tree" + "relation": "full snapshot at research/ptcna/" }, { "repository": "The-Interdependency/epac", "commit": "hmmm", - "authority": "provisional energy particle affixiation coupling research; no external source archive yet", - "path": "research/epac", - "relation": "stack-owned provisional research at research/epac/", - "archive_status": "stack-owned-provisional-research" + "authority": "placeholder scaffold for energy particle affixiation coupling; no source repository exists yet", + "relation": "placeholder at research/epac/" } ], "boundaries": { "authority_transfer": false, "proof_status_transfer": false, "measurement_status_transfer": false, - "semantic_mapping": "stack-research-with-archive-provenance", + "semantic_mapping": "external-provenance", "agent_scope": "cross-repository-work-graph", - "archive_provenance_required": true, - "source_repositories_after_archive": "archived-provenance-records", - "public_gonol_timing": "after-research-closure", - "promotion_requires_public_gonol": true, "hmmm": [ "ucns has no LICENSE file at snapshot commit 1975fe7", - "epac has no external source archive yet", - "repo-level Public Gonol closure receipts are downstream of research" + "epac is a placeholder with no source repository", + "research/ snapshots are not source of truth; canonical sources remain The-Interdependency/" ] } } diff --git a/tools/check_stack_manifest.py b/tools/check_stack_manifest.py deleted file mode 100644 index fc31c3d..0000000 --- a/tools/check_stack_manifest.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python3 -"""Validate the stack manifest and root lifecycle contract. - -This verifier is intentionally offline. It does not fetch archived repositories or -interpret participant research. It checks that the stack's root provenance record is -internally consistent and that declared stack paths exist. -""" - -from __future__ import annotations - -import hashlib -import json -from pathlib import Path -import re -import sys -from typing import Any - - -ROOT = Path(__file__).resolve().parents[1] -MANIFEST_PATH = ROOT / "stack-manifest.json" -HUMAN_MANIFEST_PATH = ROOT / "STACK_MANIFEST.md" - -SCHEMA = "the-interdependency.stack-manifest" -VERSION = "1.0.0" -HEX40 = re.compile(r"^[0-9a-f]{40}$") - - -def _fail(message: str) -> None: - raise SystemExit(f"stack manifest check failed: {message}") - - -def _load_manifest() -> dict[str, Any]: - try: - data = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) - except json.JSONDecodeError as error: - _fail(f"{MANIFEST_PATH.name} is not valid JSON: {error}") - if not isinstance(data, dict): - _fail("manifest root must be an object") - return data - - -def _canonical_digest(manifest: dict[str, Any]) -> str: - payload = { - "repositories": manifest.get("repositories"), - "boundaries": manifest.get("boundaries"), - } - encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") - return hashlib.sha256(encoded).hexdigest() - - -def _require_text(mapping: dict[str, Any], key: str, *, context: str) -> str: - value = mapping.get(key) - if not isinstance(value, str) or not value.strip(): - _fail(f"{context}.{key} must be non-empty text") - return value - - -def _validate_repository(entry: Any, seen: set[str]) -> None: - if not isinstance(entry, dict): - _fail("every repositories[] entry must be an object") - - repository = _require_text(entry, "repository", context="repository") - if repository in seen: - _fail(f"duplicate repository entry: {repository}") - seen.add(repository) - if not repository.startswith("The-Interdependency/"): - _fail(f"repository is outside The-Interdependency: {repository}") - - commit = _require_text(entry, "commit", context=repository) - authority = _require_text(entry, "authority", context=repository) - relation = _require_text(entry, "relation", context=repository) - archive_status = _require_text(entry, "archive_status", context=repository) - path_text = _require_text(entry, "path", context=repository) - - if "\n" in authority or "\n" in relation: - _fail(f"{repository} authority/relation must be single-line text") - - if archive_status == "archived-source-tree": - if not HEX40.fullmatch(commit): - _fail(f"{repository} archived source commit must be a 40-hex SHA") - if "archived source tree" not in relation: - _fail(f"{repository} relation must describe an archived source tree") - elif archive_status == "stack-owned-provisional-research": - if commit != "hmmm": - _fail(f"{repository} provisional stack-owned research must use commit hmmm") - else: - _fail(f"{repository} has unknown archive_status {archive_status!r}") - - path = ROOT / path_text - if path_text.startswith("/") or ".." in Path(path_text).parts: - _fail(f"{repository} path must be repository-relative") - if not path.exists(): - _fail(f"{repository} path does not exist: {path_text}") - if (path / ".git").exists(): - _fail(f"{repository} path must not contain nested VCS metadata: {path_text}") - - -def _validate_boundaries(boundaries: Any) -> None: - if not isinstance(boundaries, dict): - _fail("boundaries must be an object") - expected_false = ( - "authority_transfer", - "proof_status_transfer", - "measurement_status_transfer", - ) - for key in expected_false: - if boundaries.get(key) is not False: - _fail(f"boundaries.{key} must be false") - if boundaries.get("archive_provenance_required") is not True: - _fail("boundaries.archive_provenance_required must be true") - if boundaries.get("promotion_requires_public_gonol") is not True: - _fail("boundaries.promotion_requires_public_gonol must be true") - if boundaries.get("public_gonol_timing") != "after-research-closure": - _fail("boundaries.public_gonol_timing must be after-research-closure") - if boundaries.get("semantic_mapping") != "stack-research-with-archive-provenance": - _fail("boundaries.semantic_mapping has unexpected value") - hmmm = boundaries.get("hmmm") - if not isinstance(hmmm, list) or not all(isinstance(item, str) and item for item in hmmm): - _fail("boundaries.hmmm must be a non-empty text list") - - -def main() -> int: - manifest = _load_manifest() - if manifest.get("schema") != SCHEMA: - _fail(f"schema must be {SCHEMA}") - if manifest.get("version") != VERSION: - _fail(f"version must be {VERSION}") - - repositories = manifest.get("repositories") - if not isinstance(repositories, list) or not repositories: - _fail("repositories must be a non-empty list") - seen: set[str] = set() - for entry in repositories: - _validate_repository(entry, seen) - - _validate_boundaries(manifest.get("boundaries")) - - computed = _canonical_digest(manifest) - declared = manifest.get("work_graph_sha256") - if declared != computed: - _fail(f"work_graph_sha256 mismatch: declared {declared!r}, computed {computed}") - - human = HUMAN_MANIFEST_PATH.read_text(encoding="utf-8") - if computed not in human: - _fail("STACK_MANIFEST.md does not contain the computed digest") - - print(f"stack manifest ok: {computed}") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) From a1d7ae7253849230650c2c08258489454a79b758 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 18:32:05 +0000 Subject: [PATCH 10/21] Construct EPAC Public Gonols instead of EDCM text gonols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EPAC construction now closes on the UCNS Public Gonol carrier. Oriented couplings plus each arity's charge state (nuclear Z and Möbius ε at t=0) and degree are the three-dimensional structure already in the math. --- research/epac/README.md | 7 +- research/epac/docs/arity.md | 11 + ...-molecular-geometry-from-element-gonols.md | 13 +- research/epac/epac_dimensional_arity.py | 106 ++++- research/epac/epac_molecular.py | 51 +-- research/epac/epac_periodic.py | 104 ++--- research/epac/epac_public_gonol.py | 389 ++++++++++++++++++ .../subatomic-affixiation-baseline.md | 7 +- research/epac/subatomic/subatomic_gonol.py | 91 ++-- .../epac/subatomic/test_subatomic_gonol.py | 9 +- research/epac/tests/test_epac_arity.py | 21 +- research/epac/tests/test_epac_public_gonol.py | 92 +++++ ..._geometry_comparison_after_construction.py | 1 - .../epac/tests/test_molecular_affixiation.py | 9 +- .../tests/test_periodic_element_gonols.py | 4 +- 15 files changed, 758 insertions(+), 157 deletions(-) create mode 100644 research/epac/epac_public_gonol.py create mode 100644 research/epac/tests/test_epac_public_gonol.py diff --git a/research/epac/README.md b/research/epac/README.md index 7546903..a669337 100644 --- a/research/epac/README.md +++ b/research/epac/README.md @@ -12,8 +12,11 @@ until a canonical `The-Interdependency/epac` source is created and pinned in 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. + 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, @@ -25,7 +28,7 @@ until a canonical `The-Interdependency/epac` source is created and pinned in From this directory: ```bash -PYTHONPATH=".:../edcm:../ucns/src" python3 -m unittest discover -s tests -q +PYTHONPATH=".:../ucns/src" python3 -m unittest discover -s tests -q ``` Do not open `data/sealed_known_molecular_geometry.json` during construction. diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md index 2d0968f..54159be 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -21,6 +21,17 @@ 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)`. + +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`. diff --git a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md index 7dd6982..3a660b9 100644 --- a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -2,7 +2,8 @@ - Status: **CROSS-DOMAIN-HYPOTHESIS / provisional research candidate** - Owner of record: `The-Interdependency/stack` → `research/epac/` -- Constructor: `edcm.gonol` from the pinned EDCM snapshot +- 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. @@ -24,11 +25,11 @@ scopes) until comparison. ## Frozen pipeline ```text -element gonols - -> valence arity - -> affixiation - -> UCNS coupling geometry (native Möbius only) - -> molecular gonol +element Public Gonols (epac.public_gonol) + -> unpaired-valence attachment sites + -> declared oriented couplings with per-slot Z and Möbius ε + -> three-dimensional structure = those couplings + charge states + degree + -> molecular Public Gonol -> construction invariants -> (only then) compare to sealed known chemistry ``` diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index 0a81102..822af16 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -6,12 +6,17 @@ ``(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. +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. + 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 Collision: edcm.gonol arity_policy counts gonol participants, not dimensional intersections. @@ -23,6 +28,9 @@ from typing import Iterable, Mapping, Sequence +# Established UCNS Möbius frame sign at t=0: ε in (t, ε) ~ (t+n, (-1)^n ε). +MOBIUS_EPSILON_T0 = 1 + FORBIDDEN_INFERENCE_RULES = frozenset( { "ambient-power-set", @@ -39,13 +47,16 @@ class DimensionalArityError(ValueError): @dataclass(frozen=True, slots=True) class Dimension: - """One independent coordinate axis.""" + """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) @@ -69,6 +80,16 @@ def arity(self) -> int: 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: @@ -140,27 +161,36 @@ def __post_init__(self) -> None: ) -def dimension(id: str) -> Dimension: - return Dimension(id) +def dimension(id: str, charge: int | None = None) -> Dimension: + return Dimension(id, charge) -def coupling(dimension_ids: Sequence[str]) -> Coupling: +def coupling(dimension_ids: Sequence[str], charges: Mapping[str, int] | None = None) -> Coupling: if not isinstance(dimension_ids, Sequence) or isinstance(dimension_ids, (str, bytes)): raise DimensionalArityError("coupling dimensions must be an ordered declaration sequence") - return Coupling(tuple(Dimension(item) for item in dimension_ids)) + charge_map = dict(charges or {}) + return Coupling(tuple(Dimension(item, charge_map.get(item)) for item in dimension_ids)) def space( ambient_ids: Sequence[str], coupling_declarations: Sequence[Sequence[str]] = (), proofs: Sequence[CouplingProof] = (), + charges: Mapping[str, int] | None = None, ) -> DimensionalSpace: if not isinstance(ambient_ids, Sequence) or isinstance(ambient_ids, (str, bytes)): raise DimensionalArityError("ambient dimensions must be a declared sequence") - declared = tuple(coupling(item) for item in coupling_declarations) + 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: + if not isinstance(item, Sequence) or isinstance(item, (str, bytes)): + raise DimensionalArityError("each coupling declaration must be an ordered sequence") + declared.append(Coupling(tuple(by_id[name] if name in by_id else Dimension(name) for name in item))) return DimensionalSpace( - ambient_dimensions=tuple(Dimension(item) for item in ambient_ids), - couplings=declared, + ambient_dimensions=ambient, + couplings=tuple(declared), proofs=tuple(proofs), ) @@ -220,17 +250,62 @@ def install_proven_coupling(declared: DimensionalSpace, proof: CouplingProof) -> ) +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. 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, + } + 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, + } + + 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": tuple( - { - "declared_ids": item.declared_ids, - "arity": item.arity, - } - for item in declared.couplings + "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( @@ -254,6 +329,7 @@ def geometry_from_declared_couplings(declared: DimensionalSpace) -> Mapping[str, "inferred_from_ambient": False, "inferred_higher_arity_from_overlap": False, "zx_equals_xz": False, + "structure": structure_from_charged_couplings(declared), } @@ -287,6 +363,7 @@ def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, ob "DimensionalArityError", "DimensionalSpace", "FORBIDDEN_INFERENCE_RULES", + "MOBIUS_EPSILON_T0", "coupling", "degree_relations", "dimension", @@ -295,4 +372,5 @@ def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, ob "install_proven_coupling", "observed_common_ids", "space", + "structure_from_charged_couplings", ] diff --git a/research/epac/epac_molecular.py b/research/epac/epac_molecular.py index 33155f0..510a1b3 100644 --- a/research/epac/epac_molecular.py +++ b/research/epac/epac_molecular.py @@ -1,9 +1,14 @@ -"""Molecular gonols from atomic electron-shell gonols plus UCNS Möbius coupling. +"""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. No sealed molecular-shape file is opened here. +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. It is not an inferred cartesian embedding. """ from __future__ import annotations @@ -11,12 +16,12 @@ from dataclasses import dataclass from typing import Any, Mapping -from edcm.gonol import ClosedGonol, GonolReceipt, construct_gonol, replay_gonol from ucns.direct_mobius import native_mobius_state from epac_atomic import AtomicRecord from epac_dimensional_arity import geometry_from_declared_couplings, space 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], ...]] = { @@ -28,18 +33,17 @@ } RELATION = "epac.affixiation.unpaired-valence" -SCALE = "recursive" @dataclass(frozen=True, slots=True) class MolecularConstruction: formula: str - receipt: GonolReceipt + receipt: PublicGonolReceipt invariants: Mapping[str, Any] -def _instantiate(composition: tuple[tuple[str, int], ...]) -> tuple[ClosedGonol, ...]: - instances: list[ClosedGonol] = [] +def _instantiate(composition: tuple[tuple[str, int], ...]) -> tuple[ClosedPublicGonol, ...]: + instances: list[ClosedPublicGonol] = [] occurrence = 0 for symbol, count in composition: for _ in range(count): @@ -48,11 +52,11 @@ def _instantiate(composition: tuple[tuple[str, int], ...]) -> tuple[ClosedGonol, return tuple(instances) -def _record_for(gonol: ClosedGonol) -> AtomicRecord: +def _record_for(gonol: ClosedPublicGonol) -> AtomicRecord: return atomic_of(symbol_of(gonol)) -def _choose_center(participants: tuple[ClosedGonol, ...]) -> ClosedGonol | None: +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. @@ -81,22 +85,23 @@ def _attachment_set(record: AtomicRecord, needed: int) -> tuple[tuple[int, int], ) -def _atom_dimension_id(gonol: ClosedGonol) -> str: +def _atom_dimension_id(gonol: ClosedPublicGonol) -> str: return f"{symbol_of(gonol)}#{gonol.occurrence}" def _declared_dimensional_space( - participants: tuple[ClosedGonol, ...], - center: ClosedGonol | None, - ligands: tuple[ClosedGonol, ...], + 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) + return space(ambient, declarations, charges=charges) def _mobius_coupling() -> Mapping[str, Any]: @@ -144,16 +149,16 @@ def construct_molecule(formula: str) -> MolecularConstruction: ligand_sites = tuple( tuple((e.l, e.m_l) for e in _record_for(item).unpaired_valence) for item in ligands ) - receipt = construct_gonol( - scale=SCALE, - source_id=f"epac.molecule:{formula}", - participants=participants, - relation=RELATION, - geometry_authority=__import__("ucns.public_gonol", fromlist=["public_gonol"]), - ) mobius = _mobius_coupling() dimensional = _declared_dimensional_space(participants, center, 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 = { @@ -190,8 +195,8 @@ def construct_molecule(formula: str) -> MolecularConstruction: return MolecularConstruction(formula=formula, receipt=receipt, invariants=invariants) -def replay_molecule(construction: MolecularConstruction) -> GonolReceipt: - return replay_gonol(receipt=construction.receipt) +def replay_molecule(construction: MolecularConstruction) -> PublicGonolReceipt: + return replay_public_gonol(construction.receipt) def construct_declared_molecules() -> dict[str, MolecularConstruction]: diff --git a/research/epac/epac_periodic.py b/research/epac/epac_periodic.py index 9895086..3c62e55 100644 --- a/research/epac/epac_periodic.py +++ b/research/epac/epac_periodic.py @@ -1,30 +1,34 @@ -"""Element gonols closed from full atomic electron-shell structure. +"""Element gonols closed as EPAC Public Gonols from atomic electron-shell structure. Usage guidance -------------- -Each electron is a closed gonol with (n, l, m_l, m_s), shell, subshell, -hydrogenic angular id, radial nodes, Slater Z_eff, and Rydberg energy. -Electrons of one n affixiate as a shell. Shells plus a nucleus gonol -affixiate as the element. Nothing molecular is encoded. +Each electron, shell, nucleus, and element is an EPAC Public Gonol on the +UCNS carrier. This module does not use ``edcm.gonol``. Nothing molecular is +encoded here. from epac_periodic import construct_element_gonol, construct_periodic_table oxygen = construct_element_gonol("O") + assert oxygen.constructor_id == "epac.public_gonol" """ from __future__ import annotations -from typing import Any, Iterable +from typing import Iterable -from edcm.gonol import ClosedGonol, GonolReceipt, construct_gonol, replay_gonol +from epac_atomic import AtomicRecord, ElectronState, iter_table +from epac_public_gonol import ( + ClosedPublicGonol, + PublicGonolReceipt, + construct_public_gonol, + replay_public_gonol, +) -from epac_atomic import AtomicRecord, ElectronState, atomic_record, iter_table - -def _geometry_authority() -> Any: - from ucns import public_gonol - - return public_gonol +def _carrier_glyph(text: str) -> str | None: + if len(text) == 1: + return text + return None def _electron_options(electron: ElectronState) -> tuple[tuple[str, str], ...]: @@ -44,16 +48,16 @@ def _electron_options(electron: ElectronState) -> tuple[tuple[str, str], ...]: ) -def _construct_electron(electron: ElectronState, *, symbol: str, atom_occurrence: int) -> ClosedGonol: - receipt = construct_gonol( - scale="word", - source="e", +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), - geometry_authority=_geometry_authority(), occurrence=electron.index, - ) - return receipt.gonol + ).gonol def _construct_shell( @@ -62,37 +66,37 @@ def _construct_shell( *, symbol: str, atom_occurrence: int, -) -> ClosedGonol: - members = tuple(_construct_electron(e, symbol=symbol, atom_occurrence=atom_occurrence) for e in electrons) - return construct_gonol( - scale="word", - source=f"n{n}", +) -> 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}", - participants=members, relation="epac.atomic.shell", - geometry_authority=_geometry_authority(), + identity_glyph=_carrier_glyph(str(n)), + participants=members, occurrence=n, + carried_options=(("n", str(n)),), ).gonol -def _construct_nucleus(record: AtomicRecord, *, atom_occurrence: int) -> ClosedGonol: - return construct_gonol( - scale="word", - source="nuc", +def _construct_nucleus(record: AtomicRecord, *, atom_occurrence: int) -> ClosedPublicGonol: + return construct_public_gonol( source_id=f"epac.nucleus:{record.symbol}#{atom_occurrence}", + relation="epac.atomic.nucleus", carried_options=( ("Z", str(record.Z)), ("A", str(record.A)), ("protons", str(record.proton_count)), ("neutrons", str(record.neutron_count)), + ("symbol", record.symbol), ), - geometry_authority=_geometry_authority(), occurrence=0, ).gonol -def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt: - """Close one element gonol whose participants are nucleus + electron shells.""" +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(): @@ -101,18 +105,17 @@ def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt break if record is None: raise ValueError(f"no atomic record for symbol {symbol!r}") - shells: list[ClosedGonol] = [] + 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) - ) + 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)), @@ -126,24 +129,22 @@ def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt ("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)), ) - return construct_gonol( - scale="word", - source=symbol, + return construct_public_gonol( source_id=f"epac.periodic:{symbol}#{occurrence}", - participants=(nucleus, *shells), relation="epac.atomic.element", + identity_glyph=_carrier_glyph(symbol), + participants=(nucleus, *shells), carried_options=carried, - geometry_authority=_geometry_authority(), occurrence=occurrence, ) -def construct_periodic_table() -> dict[str, GonolReceipt]: +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: GonolReceipt) -> GonolReceipt: - return replay_gonol(receipt=receipt) +def replay_element_gonol(receipt: PublicGonolReceipt) -> PublicGonolReceipt: + return replay_public_gonol(receipt) def atomic_of(symbol: str) -> AtomicRecord: @@ -153,11 +154,16 @@ def atomic_of(symbol: str) -> AtomicRecord: raise ValueError(symbol) -def symbol_of(gonol: ClosedGonol) -> str: - return "".join(gonol.source_units) +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: ClosedGonol, key: str) -> str: +def carried(gonol: ClosedPublicGonol, key: str) -> str: for item_key, value in gonol.carried_options: if item_key == key: return value diff --git a/research/epac/epac_public_gonol.py b/research/epac/epac_public_gonol.py new file mode 100644 index 0000000..64cb3e1 --- /dev/null +++ b/research/epac/epac_public_gonol.py @@ -0,0 +1,389 @@ +"""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. No cartesian embedding +and no ternary coupling are 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 dataclasses import dataclass +from hashlib import sha256 +import json +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 molecular shape or VSEPR", +) + +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, Mapping): + return {str(key): _freeze_json(item) for key, item in value.items()} + if isinstance(value, Sequence) and not isinstance(value, (str, bytes)): + return [_freeze_json(item) for item in value] + raise PublicGonolConstructionError(f"value is not JSON-stable: {type(value)!r}") + + +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(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) + 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/subatomic-affixiation-baseline.md b/research/epac/subatomic/subatomic-affixiation-baseline.md index dea5a33..7a66220 100644 --- a/research/epac/subatomic/subatomic-affixiation-baseline.md +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -237,8 +237,8 @@ numeric nuclear data is web-pinned 2026-08-22; alpha-cluster and isospin citatio ## 11. Subatomic gonol (2026-08-22) The subatomic gonol closes one element gonol per symbol from three separately addressable -layers, using the EDCM gonol candidate constructor (`edcm.gonol/v1`) with -`ucns.public_gonol` supplied as the explicit geometry authority: +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; @@ -251,7 +251,8 @@ layers, using the EDCM gonol candidate constructor (`edcm.gonol/v1`) with - `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` — sealed gonol receipts; `replay_gonol` byte-identical for all. +- `receipts/gonol_*.json` — historical EDCM-constructor receipts, superseded as + constructor identity. Replay of the current constructor is `replay_public_gonol`. | Element | Gonol receipt digest | |---|---| diff --git a/research/epac/subatomic/subatomic_gonol.py b/research/epac/subatomic/subatomic_gonol.py index 0ab10c3..3ed9072 100644 --- a/research/epac/subatomic/subatomic_gonol.py +++ b/research/epac/subatomic/subatomic_gonol.py @@ -12,16 +12,15 @@ (n, l, m_l, m_s, shell, subshell, angular id, radial nodes, Slater Z_eff, Rydberg energy). -Construction uses the EDCM gonol candidate constructor -(``edcm.gonol.construct_gonol``) with ``ucns.public_gonol`` supplied as the -explicit geometry authority. No Public Gonol position operation and no Möbius -coupling law is invented. +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="::/src" python3 - <<'PY' + PYTHONPATH=":/src" python3 - <<'PY' from subatomic_gonol import construct_subatomic_gonol, replay_subatomic_gonol receipt = construct_subatomic_gonol("He") @@ -37,13 +36,13 @@ if _PARENT not in sys.path: sys.path.insert(0, _PARENT) -from edcm.gonol import ( # noqa: E402 - ClosedGonol, - GonolReceipt, - construct_gonol, - replay_gonol, -) from epac_atomic import AtomicRecord, atomic_record # noqa: E402 +from epac_public_gonol import ( # noqa: E402 + ClosedPublicGonol, + PublicGonolReceipt, + construct_public_gonol, + replay_public_gonol, +) import element_affixiation_candidate as identity # noqa: E402 import nuclear_harmonic_candidates as harmonics # noqa: E402 @@ -52,10 +51,10 @@ # 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 EDCM gonol candidate constructor +# 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: _geometry_authority, _nucleus_participant, _shell_participants, _electron_options, _harmonic_rows +# internal_surface: _carrier_glyph, _nucleus_participant, _shell_participants, _electron_options, _harmonic_rows # auth_boundary: none # storage_boundary: none # network_boundary: none @@ -64,9 +63,9 @@ # tests: subatomic.test_subatomic_gonol # rollout: local candidate module under stack/research/epac/subatomic/ # rollback: remove module, tests, and generated receipts -# requires: edcm_gonol, epac_atomic, epac_subatomic_element_affixiation_candidate, epac_subatomic_nuclear_harmonic_candidates +# 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; EDCM gonol candidate is not selected canon +# unresolved: UCNS position operations; UCNS harmonic notation; EPAC Public Gonol candidate is not selected canon # === END MODULE_BUILD === # === CONTRACTS === @@ -77,7 +76,7 @@ # # id: subatomic_gonol_replays_byte_identical # given: a subatomic gonol receipt -# then: replay_gonol reproduces the same receipt_digest +# then: replay_public_gonol reproduces the same receipt_digest # class: correctness # # id: subatomic_gonol_keeps_layers_distinct @@ -87,7 +86,7 @@ # # id: subatomic_gonol_invents_no_geometry # given: construction -# then: UCNS geometry is consumed only as the explicit public_gonol authority; no position operation or Möbius coupling law is defined or inferred +# 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 @@ -99,12 +98,6 @@ SUPPORTED_SYMBOLS: tuple[str, ...] = ("H", "He", "Li", "C") -def _geometry_authority(): - from ucns import public_gonol - - return public_gonol - - def _harmonic_rows(symbol: str) -> tuple[harmonics.HarmonicCandidate, ...]: return tuple( candidate @@ -130,7 +123,13 @@ def _electron_options(record: AtomicRecord, electron) -> tuple[tuple[str, str], ) -def _nucleus_participant(symbol: str, occurrence: int) -> ClosedGonol: +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)), @@ -157,50 +156,43 @@ def _nucleus_participant(symbol: str, occurrence: int) -> ClosedGonol: ), ) ) - receipt = construct_gonol( - scale="word", - source="nuc", + return construct_public_gonol( source_id=f"epac.subatomic.nucleus:{symbol}#{occurrence}", relation="epac.subatomic.nucleus", carried_options=carried, - geometry_authority=_geometry_authority(), occurrence=occurrence, - ) - return receipt.gonol + ).gonol -def _shell_participants(record: AtomicRecord, occurrence: int) -> tuple[ClosedGonol, ...]: +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[ClosedGonol] = [] + shells: list[ClosedPublicGonol] = [] for n in sorted(by_n): - members: list[ClosedGonol] = [] + members: list[ClosedPublicGonol] = [] for electron in by_n[n]: - electron_receipt = construct_gonol( - scale="word", - source="e", + 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), - geometry_authority=_geometry_authority(), occurrence=electron.index, ) members.append(electron_receipt.gonol) - shell_receipt = construct_gonol( - scale="word", - source=f"n{n}", + 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, - geometry_authority=_geometry_authority(), occurrence=n, + carried_options=(("n", str(n)),), ) shells.append(shell_receipt.gonol) return tuple(shells) -def construct_subatomic_gonol(symbol: str, *, occurrence: int = 0) -> GonolReceipt: +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( @@ -215,6 +207,7 @@ def construct_subatomic_gonol(symbol: str, *, occurrence: int = 0) -> GonolRecei if any(harmonics.recurrence_test(candidate).values()) ) carried = [ + ("symbol", symbol), ("Z", str(record.Z)), ("period", str(record.period)), ("group", str(record.group)), @@ -224,24 +217,22 @@ def construct_subatomic_gonol(symbol: str, *, occurrence: int = 0) -> GonolRecei ("harmonic-surviving", harmonic_surviving or "none"), ("status", "CROSS-DOMAIN-HYPOTHESIS"), ] - return construct_gonol( - scale="word", - source=symbol, + 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, - geometry_authority=_geometry_authority(), occurrence=occurrence, ) -def replay_subatomic_gonol(receipt: GonolReceipt) -> str: +def replay_subatomic_gonol(receipt: PublicGonolReceipt) -> str: """Replay a completed subatomic gonol receipt; returns its digest.""" - return replay_gonol(receipt=receipt).receipt_digest + return replay_public_gonol(receipt).receipt_digest -def subatomic_receipt_record(receipt: GonolReceipt) -> dict: +def subatomic_receipt_record(receipt: PublicGonolReceipt) -> dict: """JSON-safe summary of one subatomic gonol receipt.""" gonol = receipt.gonol return { @@ -252,7 +243,7 @@ def subatomic_receipt_record(receipt: GonolReceipt) -> dict: "source_id": receipt.source_id, "receipt_digest": receipt.receipt_digest, "atomic_id": gonol.atomic_id, - "scale": gonol.scale, + "identity_glyph": gonol.identity_glyph, "relation": gonol.relation, "participant_kinds": [ ("nucleus" if "nucleus" in p.source_id else "shell") for p in gonol.participants diff --git a/research/epac/subatomic/test_subatomic_gonol.py b/research/epac/subatomic/test_subatomic_gonol.py index cb3e76e..f7383b3 100644 --- a/research/epac/subatomic/test_subatomic_gonol.py +++ b/research/epac/subatomic/test_subatomic_gonol.py @@ -81,13 +81,16 @@ def test_keeps_layers_distinct(): def test_invents_no_geometry(): source = open(m.__file__, encoding="utf-8").read() - # The module consumes EDCM gonol construction and supplies the explicit - # UCNS public_gonol authority; it must not define position operations. + # 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.gonol.geometry_digest # geometry observed from the supplied authority + assert receipt.constructor_id == "epac.public_gonol" + assert receipt.gonol.geometry_digest def test_stays_cross_domain_hypothesis(): diff --git a/research/epac/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py index 5dc29d9..eb188fc 100644 --- a/research/epac/tests/test_epac_arity.py +++ b/research/epac/tests/test_epac_arity.py @@ -25,28 +25,43 @@ 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"], ({"declared_ids": ("x",), "arity": 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"]]) + 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"])) 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"]]) + 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",)) 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..cfc697a --- /dev/null +++ b/research/epac/tests/test_epac_public_gonol.py @@ -0,0 +1,92 @@ +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_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 index ef0836d..b5736d5 100644 --- a/research/epac/tests/test_geometry_comparison_after_construction.py +++ b/research/epac/tests/test_geometry_comparison_after_construction.py @@ -8,7 +8,6 @@ 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" / "edcm")) sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) from epac_molecular import construct_declared_molecules, matched_information_control diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index 8c697aa..8059da3 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -7,7 +7,6 @@ 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" / "edcm")) sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) from epac_molecular import construct_declared_molecules, replay_molecule @@ -50,6 +49,14 @@ def test_declared_couplings_are_binary_and_do_not_fill_ambient(self) -> None: 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) def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: molecules = construct_declared_molecules() diff --git a/research/epac/tests/test_periodic_element_gonols.py b/research/epac/tests/test_periodic_element_gonols.py index 8b4ac02..8295628 100644 --- a/research/epac/tests/test_periodic_element_gonols.py +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -7,7 +7,6 @@ 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" / "edcm")) sys.path.insert(0, str(STACK_ROOT / "research" / "ucns" / "src")) from epac_periodic import construct_element_gonol, construct_periodic_table, replay_element_gonol @@ -28,8 +27,9 @@ def test_constructs_z1_to_z18(self) -> None: 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 "".join(item.source_units).startswith("n")] + 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} From ef631fe9294066fd9a25de0ff9059d366db76a1f Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:31:42 +0000 Subject: [PATCH 11/21] Compare sealed shapes against the charged 3-structure Construction still has no chemistry shape names. After closure, the readout is oriented couplings plus each arity's charge state and degree. Topology without charge collapses H2O with CO2; charge distinguishes them; neither recovers the sealed linear class. --- research/epac/README.md | 8 + research/epac/docs/arity.md | 4 +- ...-molecular-geometry-from-element-gonols.md | 23 +-- research/epac/epac_comparison.py | 158 ++++++++++++++++++ research/epac/epac_dimensional_arity.py | 54 ++++++ research/epac/epac_molecular.py | 9 +- research/epac/epac_public_gonol.py | 2 +- research/epac/tests/test_epac_arity.py | 16 ++ ..._geometry_comparison_after_construction.py | 65 ++++--- .../epac/tests/test_molecular_affixiation.py | 2 +- 10 files changed, 307 insertions(+), 34 deletions(-) create mode 100644 research/epac/epac_comparison.py diff --git a/research/epac/README.md b/research/epac/README.md index a669337..6f4b3e5 100644 --- a/research/epac/README.md +++ b/research/epac/README.md @@ -32,3 +32,11 @@ 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/docs/arity.md b/research/epac/docs/arity.md index 54159be..25c0ffd 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -35,4 +35,6 @@ Construction is `epac.public_gonol` on the UCNS Public Gonol carrier, not Overlap of members is not a proof. Forbidden inference rules include `overlap-closure`, `permutation-identity`, and `ambient-power-set`. -See `epac_dimensional_arity.py`. +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 index 3a660b9..8c10009 100644 --- a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -74,22 +74,25 @@ 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 UCNS coupling invariants distinguish formulas that chemistry - distinguishes by shape. -5. Compare three signatures: UCNS Möbius coupling; atomic unpaired (l, m_l) - plus ligand shell content; and a matched-information control of formula - symbols only. +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 UCNS coupling invariants distinguish - sealed shape classes after subtracting the matched-information control. -- `FALSIFIED` as prediction — if those invariants are identical across sealed - shape classes, or if distinguishing power is already present in - valence+stoichiometry. +- `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. 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 index 822af16..cb120ba 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -288,6 +288,58 @@ def structure_from_charged_couplings(declared: DimensionalSpace) -> Mapping[str, } +def _tuple_tree(value: object) -> object: + if isinstance(value, Sequence) and not isinstance(value, (str, bytes)): + return tuple(_tuple_tree(item) for item in value) + return value + + +def charged_structure_readout(structure: Mapping[str, object]) -> tuple[object, ...]: + """Order-invariant 3-structure: couplings + charge states + degree. + + Occurrence labels are dropped. Slot order inside each coupling is kept, so + ``(8, 1)`` is not ``(1, 8)``. + """ + + parts = tuple( + sorted( + ( + int(part["arity"]), + _tuple_tree(part["charge_state"]), + ) + for part in structure["parts"] + ) + ) + degree = tuple( + sorted( + ( + int(item["degree"]), + _tuple_tree(item["slot_degrees"]), + item["charge"], + ) + for item in structure["degree"] + ) + ) + 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(arity for arity, _charge in parts), + tuple((deg, slots) for deg, slots, _charge in degree), + participating, + ternary, + ) + + def geometry_from_declared_couplings(declared: DimensionalSpace) -> Mapping[str, object]: degrees = degree_relations(declared) couplings = tuple( @@ -364,6 +416,7 @@ def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, ob "DimensionalSpace", "FORBIDDEN_INFERENCE_RULES", "MOBIUS_EPSILON_T0", + "charged_structure_readout", "coupling", "degree_relations", "dimension", @@ -373,4 +426,5 @@ def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, ob "observed_common_ids", "space", "structure_from_charged_couplings", + "topology_structure_readout", ] diff --git a/research/epac/epac_molecular.py b/research/epac/epac_molecular.py index 510a1b3..07507fb 100644 --- a/research/epac/epac_molecular.py +++ b/research/epac/epac_molecular.py @@ -19,7 +19,12 @@ from ucns.direct_mobius import native_mobius_state from epac_atomic import AtomicRecord -from epac_dimensional_arity import geometry_from_declared_couplings, space +from epac_dimensional_arity import ( + charged_structure_readout, + geometry_from_declared_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 @@ -191,6 +196,8 @@ def construct_molecule(formula: str) -> MolecularConstruction: ), "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"]), } return MolecularConstruction(formula=formula, receipt=receipt, invariants=invariants) diff --git a/research/epac/epac_public_gonol.py b/research/epac/epac_public_gonol.py index 64cb3e1..a159b0b 100644 --- a/research/epac/epac_public_gonol.py +++ b/research/epac/epac_public_gonol.py @@ -97,7 +97,7 @@ "not a UCNS geometric function operation", "not a UCNS Möbius coupling law", "not METAPAT canon promotion", - "not molecular shape or VSEPR", + "not imported chemistry shape names", ) HMMM: tuple[str, ...] = ( diff --git a/research/epac/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py index eb188fc..88522f6 100644 --- a/research/epac/tests/test_epac_arity.py +++ b/research/epac/tests/test_epac_arity.py @@ -10,6 +10,7 @@ from epac_dimensional_arity import ( CouplingProof, DimensionalArityError, + charged_structure_readout, coupling, degree_relations, geometry_from_declared_couplings, @@ -17,6 +18,7 @@ install_proven_coupling, observed_common_ids, space, + topology_structure_readout, ) @@ -72,6 +74,20 @@ def test_xz_and_yz_do_not_give_xyz_without_proof(self) -> None: 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_overlap_is_not_an_installable_proof(self) -> None: declared = space(["x", "y", "z"], [["x", "z"], ["y", "z"]]) diff --git a/research/epac/tests/test_geometry_comparison_after_construction.py b/research/epac/tests/test_geometry_comparison_after_construction.py index b5736d5..2721819 100644 --- a/research/epac/tests/test_geometry_comparison_after_construction.py +++ b/research/epac/tests/test_geometry_comparison_after_construction.py @@ -10,6 +10,8 @@ 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 @@ -17,32 +19,55 @@ class GeometryComparisonAfterConstructionTest(unittest.TestCase): - def test_what_atomic_shells_add_versus_ucns_coupling(self) -> None: + 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)), (2, ((8, 1), 1)))) + self.assertEqual(co2_charged[0], ((2, ((6, 8), 1)), (2, ((6, 8), 1)))) + + 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} - ucns = {f: c.invariants["ucns_coupling_signature"] for f, c in constructions.items()} - atomic = {f: c.invariants["atomic_coupling_signature"] for f, c in constructions.items()} - control = {f: matched_information_control(c.invariants) for f, c in constructions.items()} - + 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(len(set(ucns.values())), 1) - self.assertNotEqual(known_shapes["H2O"], known_shapes["CO2"]) + 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.assertNotEqual(atomic["H2O"], atomic["CO2"]) - self.assertTrue(constructions["CO2"].invariants["ligand_has_p"]) - self.assertFalse(constructions["H2O"].invariants["ligand_has_p"]) - - # UCNS Möbius is identical across sealed shape classes. - ucns_predicts_shape = len(set(ucns.values())) == len(set(known_shapes.values())) - self.assertFalse(ucns_predicts_shape) - - # Atomic shell/unpaired-(l,m) signatures distinguish the formulas, but - # they are functions of the atoms already named in the formula. - atomic_equals_control = set(atomic.values()) == set(control.values()) - self.assertFalse(atomic_equals_control) - self.assertEqual(len(set(atomic.values())), len(constructions)) self.assertEqual(len(set(control.values())), len(constructions)) diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index 8059da3..76a4cbe 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -65,7 +65,7 @@ def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: 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"): + for term in ("bent", "tetrahedral", "trigonal-pyramidal", "vsepr", "linear"): self.assertNotIn(term, source) From 6213bc8c1e4bc1a4b4b534021c995bb08bc42dc8 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:38:02 +0000 Subject: [PATCH 12/21] Require (z, instance) for every x and y occurrence Each instance has its own oriented hub coupling. (z, x0) does not cover x1, and (x, z) does not satisfy (z, x). --- research/epac/docs/arity.md | 6 ++ research/epac/epac_dimensional_arity.py | 71 +++++++++++++++++++ research/epac/epac_molecular.py | 9 +++ research/epac/tests/test_epac_arity.py | 26 +++++++ .../epac/tests/test_molecular_affixiation.py | 9 +++ 5 files changed, 121 insertions(+) diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md index 25c0ffd..abb099e 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -8,8 +8,14 @@ dimension is incident on declared couplings. ```text (z, x) ≠ (x, z) (x, z) and (y, z) ↛ (x, y, z) without an explicit proof + +every instance of x has its own (z, x_i) +every instance of y has its own (z, y_j) ``` +A second occurrence is a second instance. `(z, x_0)` does not cover `x_1`. +`(x_i, z)` does not satisfy `(z, x_i)`. + Degree is required. For ambient `{x,y,z}` with couplings `(z,x)` and `(z,y)`: ```text diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index cb120ba..6e0379f 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -6,6 +6,11 @@ ``(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 instance of ``x`` has its own declared ``(z, x_i)``. Every 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)``. + 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. @@ -17,6 +22,7 @@ - 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 dimension; each x_i / y_j is distinct Collision: edcm.gonol arity_policy counts gonol participants, not dimensional intersections. @@ -219,6 +225,68 @@ def has_declared_coupling(declared: DimensionalSpace, dimension_ids: Sequence[st 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 install_proven_coupling(declared: DimensionalSpace, proof: CouplingProof) -> DimensionalSpace: """Add a coupling only with an explicit non-forbidden proof.""" @@ -423,7 +491,10 @@ def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, ob "geometry_from_declared_couplings", "has_declared_coupling", "install_proven_coupling", + "instances_missing_oriented_hub_coupling", "observed_common_ids", + "oriented_instance_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 index 07507fb..a9097d0 100644 --- a/research/epac/epac_molecular.py +++ b/research/epac/epac_molecular.py @@ -22,6 +22,7 @@ from epac_dimensional_arity import ( charged_structure_readout, geometry_from_declared_couplings, + oriented_instance_couplings, space, topology_structure_readout, ) @@ -156,6 +157,13 @@ def construct_molecule(formula: str) -> MolecularConstruction: ) mobius = _mobius_coupling() 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}", @@ -198,6 +206,7 @@ def construct_molecule(formula: str) -> MolecularConstruction: "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) diff --git a/research/epac/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py index 88522f6..da852e2 100644 --- a/research/epac/tests/test_epac_arity.py +++ b/research/epac/tests/test_epac_arity.py @@ -16,7 +16,10 @@ geometry_from_declared_couplings, has_declared_coupling, install_proven_coupling, + instances_missing_oriented_hub_coupling, observed_common_ids, + oriented_instance_couplings, + require_every_instance_has_oriented_hub_coupling, space, topology_structure_readout, ) @@ -89,6 +92,29 @@ def test_xz_and_yz_do_not_give_xyz_without_proof(self) -> None: 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"): diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index 76a4cbe..c1103e2 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -57,6 +57,15 @@ def test_declared_couplings_are_binary_and_do_not_fill_ambient(self) -> None: 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"], ()) def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: molecules = construct_declared_molecules() From e438dde8f25aecdcb4310e9bda1723a01e2dae70 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:38:13 +0000 Subject: [PATCH 13/21] Note per-instance hub couplings in the molecular constructor --- research/epac/epac_molecular.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/research/epac/epac_molecular.py b/research/epac/epac_molecular.py index a9097d0..a07f0d7 100644 --- a/research/epac/epac_molecular.py +++ b/research/epac/epac_molecular.py @@ -8,7 +8,8 @@ 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. It is not an inferred cartesian embedding. +with degree. Every ligand instance has its own (center, instance) coupling. +It is not an inferred cartesian embedding. """ from __future__ import annotations From 61002c8dd41ae773334a0d6679b928d79a0d34de Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:39:03 +0000 Subject: [PATCH 14/21] Extend subatomic gonol through iron with symbol-abbreviation coupling - extended_atomic: Z=1..26 quantum layer (Z<=18 delegates to epac_atomic; declared ground-state configs K..Fe incl Cr 4s1.3d5) - subatomic_gonol: 26 supported symbols via epac_public_gonol - symbol_coupling: one/two-letter abbreviation gonol coupled to element gonol at declared arity 2 (epac.symbol-coupling) - 26/26 subatomic tests pass; 29 sibling tests OK; audit closed 26/26 Signed-off-by: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> --- .../element_affixiation_candidate.py | 12 +- research/epac/subatomic/extended_atomic.py | 243 ++++++++++++++++++ .../subatomic-affixiation-baseline.md | 17 ++ research/epac/subatomic/subatomic_gonol.py | 11 +- research/epac/subatomic/symbol_coupling.py | 141 ++++++++++ .../epac/subatomic/test_extended_atomic.py | 58 +++++ .../epac/subatomic/test_subatomic_gonol.py | 8 +- .../epac/subatomic/test_symbol_coupling.py | 70 +++++ 8 files changed, 550 insertions(+), 10 deletions(-) create mode 100644 research/epac/subatomic/extended_atomic.py create mode 100644 research/epac/subatomic/symbol_coupling.py create mode 100644 research/epac/subatomic/test_extended_atomic.py create mode 100644 research/epac/subatomic/test_symbol_coupling.py diff --git a/research/epac/subatomic/element_affixiation_candidate.py b/research/epac/subatomic/element_affixiation_candidate.py index 43e5d24..8789fb6 100644 --- a/research/epac/subatomic/element_affixiation_candidate.py +++ b/research/epac/subatomic/element_affixiation_candidate.py @@ -96,11 +96,15 @@ } # 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), - "C": (6, 12), + "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), } diff --git a/research/epac/subatomic/extended_atomic.py b/research/epac/subatomic/extended_atomic.py new file mode 100644 index 0000000..d922474 --- /dev/null +++ b/research/epac/subatomic/extended_atomic.py @@ -0,0 +1,243 @@ +"""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 + +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_atomic import ( # noqa: E402 + 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/subatomic-affixiation-baseline.md b/research/epac/subatomic/subatomic-affixiation-baseline.md index 7a66220..1a3a490 100644 --- a/research/epac/subatomic/subatomic-affixiation-baseline.md +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -265,3 +265,20 @@ Layers stay distinct inside the gonol: nucleus and electron shells remain indivi 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`). +- **Symbol-abbreviation coupling** (`symbol_coupling.py`): each element's one- or two-letter + abbreviation closes as a symbol gonol (one character gonol for H; two ordered character + gonols for He, Fe, ...) and couples to the closed element gonol at declared arity 2 via + `epac.symbol-coupling`. This adds the two-letter abbreviation without inventing a single + Public Gonol glyph for it. +- 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 index 3ed9072..f8c4119 100644 --- a/research/epac/subatomic/subatomic_gonol.py +++ b/research/epac/subatomic/subatomic_gonol.py @@ -36,7 +36,12 @@ if _PARENT not in sys.path: sys.path.insert(0, _PARENT) -from epac_atomic import AtomicRecord, atomic_record # noqa: E402 +from extended_atomic import ( # noqa: E402 + EXTENDED_SYMBOLS, + SYMBOL_TO_Z, + AtomicRecord, + atomic_record, +) from epac_public_gonol import ( # noqa: E402 ClosedPublicGonol, PublicGonolReceipt, @@ -95,7 +100,7 @@ # class: doctrine # === END CONTRACTS === -SUPPORTED_SYMBOLS: tuple[str, ...] = ("H", "He", "Li", "C") +SUPPORTED_SYMBOLS: tuple[str, ...] = EXTENDED_SYMBOLS def _harmonic_rows(symbol: str) -> tuple[harmonics.HarmonicCandidate, ...]: @@ -198,7 +203,7 @@ def construct_subatomic_gonol(symbol: str, *, occurrence: int = 0) -> PublicGono raise ValueError( f"subatomic gonol supports {SUPPORTED_SYMBOLS}; got {symbol!r}" ) - record = atomic_record(identity.ISOTOPE_DEFAULTS[symbol][0]) + record = atomic_record(SYMBOL_TO_Z[symbol]) nucleus = _nucleus_participant(symbol, occurrence) shells = _shell_participants(record, occurrence) harmonic_surviving = ",".join( diff --git a/research/epac/subatomic/symbol_coupling.py b/research/epac/subatomic/symbol_coupling.py new file mode 100644 index 0000000..c7154f3 --- /dev/null +++ b/research/epac/subatomic/symbol_coupling.py @@ -0,0 +1,141 @@ +"""Symbol-abbreviation coupling for subatomic element gonols. + +Adds each element's one- or two-letter abbreviation as a closed symbol gonol +and couples it to the closed subatomic element gonol through the EPAC Public +Gonol constructor. + +- one-letter symbols (H, B, C, ...) close from a single identity glyph; +- two-letter symbols (He, Li, Be, Fe, ...) close from two ordered character + gonols affixiated at arity 2; +- the coupled gonol declares exactly two participants: element gonol and + symbol gonol. Geometry follows the declared coupling only. + +Status: CROSS-DOMAIN-HYPOTHESIS / implemented candidate. Not selected canon. + +Usage guidance: + + from symbol_coupling import couple_symbol + + receipt = couple_symbol("Fe") + print(receipt.receipt_digest) +""" + +# === MODULE_BUILD === +# id: epac_subatomic_symbol_coupling +# module_name: symbol_coupling +# module_kind: experiment +# summary: couples each subatomic element gonol with its closed one/two-letter symbol-abbreviation gonol through the EPAC Public Gonol constructor +# owner: The Interdependency +# public_surface: SUPPORTED_SYMBOLS, construct_symbol_gonol, couple_symbol, replay_symbol_coupling +# internal_surface: none +# auth_boundary: none +# 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: two-letter symbols have no single Public Gonol glyph; coupling is declared, not geometry-inferred +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: symbol_gonol_preserves_exact_abbreviation +# given: a symbol gonol for element symbol S +# then: participants are the exact ordered characters of S with one identity glyph each, and carried abbreviation-length equals len(S) +# class: correctness +# +# id: symbol_coupling_arity_two +# given: a symbol-coupled gonol +# then: exactly two participants (element gonol, symbol gonol) are declared and the coupling declares arity 2 +# 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 + +from epac_public_gonol import ( + ClosedPublicGonol, + PublicGonolReceipt, + construct_public_gonol, + replay_public_gonol, +) + +import subatomic_gonol + +SUPPORTED_SYMBOLS: tuple[str, ...] = subatomic_gonol.SUPPORTED_SYMBOLS + +RELATION_SYMBOL = "epac.symbol.abbreviation" +RELATION_COUPLING = "epac.symbol-coupling" + + +def construct_symbol_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: + """Close one symbol-abbreviation gonol from its exact ordered characters.""" + 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.symbol.character:{symbol}#{occurrence}:{index}:{character}", + relation="epac.symbol.character", + identity_glyph=character, + occurrence=index, + ).gonol + ) + return construct_public_gonol( + source_id=f"epac.symbol:{symbol}#{occurrence}", + relation=RELATION_SYMBOL, + participants=tuple(glyphs), + occurrence=occurrence, + carried_options=( + ("symbol", symbol), + ("abbreviation-length", str(len(symbol))), + ), + ) + + +def couple_symbol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: + """Couple a closed element gonol with its closed symbol-abbreviation gonol.""" + 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.symbol-coupled:{symbol}#{occurrence}", + relation=RELATION_COUPLING, + participants=(element, symbol_gonol), + couplings=( + { + "relation": RELATION_COUPLING, + "arity": 2, + "dimensions": [element.source_id, symbol_gonol.source_id], + }, + ), + occurrence=occurrence, + carried_options=(("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_extended_atomic.py b/research/epac/subatomic/test_extended_atomic.py new file mode 100644 index 0000000..2d93be0 --- /dev/null +++ b/research/epac/subatomic/test_extended_atomic.py @@ -0,0 +1,58 @@ +"""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 diff --git a/research/epac/subatomic/test_subatomic_gonol.py b/research/epac/subatomic/test_subatomic_gonol.py index f7383b3..48b1245 100644 --- a/research/epac/subatomic/test_subatomic_gonol.py +++ b/research/epac/subatomic/test_subatomic_gonol.py @@ -33,7 +33,7 @@ # === END CHECKS === import subatomic_gonol as m -from epac_atomic import atomic_record +from extended_atomic import atomic_record def _receipts(): @@ -49,8 +49,10 @@ def test_combines_three_sources(): 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. - assert any(key.startswith("harmonic:") for key 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 diff --git a/research/epac/subatomic/test_symbol_coupling.py b/research/epac/subatomic/test_symbol_coupling.py new file mode 100644 index 0000000..74b7a93 --- /dev/null +++ b/research/epac/subatomic/test_symbol_coupling.py @@ -0,0 +1,70 @@ +"""Executable witnesses for the symbol-abbreviation coupling.""" + +# === CHECKS === +# 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_arity_two +# proves: symbol_coupling_arity_two +# call: self::test_symbol_coupling_arity_two +# 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_symbol_gonol_preserves_exact_abbreviation(): + h = m.construct_symbol_gonol("H").gonol + assert h.identity_glyph is None # two participants? no — H closes from one char + 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_arity_two(): + 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 + coupling = receipt.gonol.couplings[0] + assert coupling["relation"] == "epac.symbol-coupling" + assert coupling["arity"] == 2 + assert len(coupling["dimensions"]) == 2 + + +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" From fa4e3c1c4c880180f76aec9d487e52046a866058 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:48:41 +0000 Subject: [PATCH 15/21] Repair tracking: ignore local skill usage state, drop superseded files - .gitignore now covers .skill-lib/ (local usage state, never committed) - remove superseded coupling_arity duplicates; canonical implementation is epac_dimensional_arity.py Signed-off-by: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c18dd8d..22cb37f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__/ +.skill-lib/ From e9962c8fb4aa499e9d9d91a5240fc91b723843f2 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:50:00 +0000 Subject: [PATCH 16/21] Give every letter instance its own (z, instance) coupling Two-letter abbreviations are (z,x) and (z,y) on three participating dimensions. The charged 3-structure readout keeps those instance ids. --- research/epac/epac_dimensional_arity.py | 7 ++- .../subatomic-affixiation-baseline.md | 8 ++- research/epac/subatomic/symbol_coupling.py | 55 ++++++++++++++++--- .../epac/subatomic/test_symbol_coupling.py | 28 ++++++++++ ..._geometry_comparison_after_construction.py | 16 +++++- 5 files changed, 99 insertions(+), 15 deletions(-) diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index 6e0379f..45e9ecb 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -365,8 +365,8 @@ def _tuple_tree(value: object) -> object: def charged_structure_readout(structure: Mapping[str, object]) -> tuple[object, ...]: """Order-invariant 3-structure: couplings + charge states + degree. - Occurrence labels are dropped. Slot order inside each coupling is kept, so - ``(8, 1)`` is not ``(1, 8)``. + 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( @@ -374,6 +374,7 @@ def charged_structure_readout(structure: Mapping[str, object]) -> tuple[object, ( int(part["arity"]), _tuple_tree(part["charge_state"]), + _tuple_tree(part["coupling"]), ) for part in structure["parts"] ) @@ -401,7 +402,7 @@ def topology_structure_readout(structure: Mapping[str, object]) -> tuple[object, parts, degree, participating, ternary = charged_structure_readout(structure) return ( - tuple(arity for arity, _charge in parts), + tuple(item[0] for item in parts), tuple((deg, slots) for deg, slots, _charge in degree), participating, ternary, diff --git a/research/epac/subatomic/subatomic-affixiation-baseline.md b/research/epac/subatomic/subatomic-affixiation-baseline.md index 1a3a490..76c9456 100644 --- a/research/epac/subatomic/subatomic-affixiation-baseline.md +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -275,9 +275,11 @@ law, and no scale interchange is introduced. Standing is `implemented-candidate` `extended_atomic` and the EPAC Public Gonol constructor (`epac_public_gonol`). - **Symbol-abbreviation coupling** (`symbol_coupling.py`): each element's one- or two-letter abbreviation closes as a symbol gonol (one character gonol for H; two ordered character - gonols for He, Fe, ...) and couples to the closed element gonol at declared arity 2 via - `epac.symbol-coupling`. This adds the two-letter abbreviation without inventing a single - Public Gonol glyph for it. + gonols for He, Fe, ...). Every letter instance has its own oriented hub coupling + `(z, instance)`: two-letter symbols are `(z, x)` and `(z, y)` on three participating + dimensions, not a ternary coupling and not a single invented Public Gonol glyph. The + closed symbol gonol then couples to the closed element gonol at declared arity 2 via + `epac.symbol-coupling`. - 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` diff --git a/research/epac/subatomic/symbol_coupling.py b/research/epac/subatomic/symbol_coupling.py index c7154f3..2941adc 100644 --- a/research/epac/subatomic/symbol_coupling.py +++ b/research/epac/subatomic/symbol_coupling.py @@ -6,9 +6,11 @@ - one-letter symbols (H, B, C, ...) close from a single identity glyph; - two-letter symbols (He, Li, Be, Fe, ...) close from two ordered character - gonols affixiated at arity 2; -- the coupled gonol declares exactly two participants: element gonol and - symbol gonol. Geometry follows the declared coupling only. + gonols; each letter instance has its own oriented hub coupling ``(z, x_i)`` + / ``(z, y_j)``. That is the three-dimensional structure for a two-letter + abbreviation. +- the element/symbol coupling declares exactly two participants: element gonol + and symbol gonol. Geometry follows declared couplings only. Status: CROSS-DOMAIN-HYPOTHESIS / implemented candidate. Not selected canon. @@ -47,6 +49,11 @@ # then: participants are the exact ordered characters of S with one identity glyph each, and carried abbreviation-length equals len(S) # class: correctness # +# id: symbol_every_letter_instance_has_oriented_hub_coupling +# given: a symbol gonol with one or two letter instances +# then: every letter instance has its own declared (z, instance) coupling; two-letter symbols occupy three participating dimensions without declaring a ternary coupling +# class: construction +# # id: symbol_coupling_arity_two # given: a symbol-coupled gonol # then: exactly two participants (element gonol, symbol gonol) are declared and the coupling declares arity 2 @@ -65,14 +72,27 @@ from __future__ import annotations -from epac_public_gonol import ( +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_dimensional_arity import ( # noqa: E402 + geometry_from_declared_couplings, + oriented_instance_couplings, + space, +) +from epac_public_gonol import ( # noqa: E402 ClosedPublicGonol, PublicGonolReceipt, construct_public_gonol, replay_public_gonol, ) +from extended_atomic import SYMBOL_TO_Z # noqa: E402 -import subatomic_gonol +import subatomic_gonol # noqa: E402 SUPPORTED_SYMBOLS: tuple[str, ...] = subatomic_gonol.SUPPORTED_SYMBOLS @@ -81,7 +101,11 @@ def construct_symbol_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: - """Close one symbol-abbreviation gonol from its exact ordered characters.""" + """Close one symbol-abbreviation gonol from its exact ordered characters. + + Every letter instance has declared ``(z, instance)``. Two letters are two + instances, so the structure is ``(z, x)`` and ``(z, y)``. + """ if symbol not in SUPPORTED_SYMBOLS: raise ValueError(f"symbol {symbol!r} is outside the supported element table") characters = tuple(symbol) @@ -95,14 +119,31 @@ def construct_symbol_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolRe occurrence=index, ).gonol ) + hub_id = f"epac.symbol:{symbol}#{occurrence}" + instance_ids = tuple(item.source_id for item in glyphs) + declared = space( + (hub_id, *instance_ids), + [[hub_id, instance_id] for instance_id in instance_ids], + charges={hub_id: int(SYMBOL_TO_Z[symbol])}, + ) + instance_couplings = oriented_instance_couplings( + declared, hub_id=hub_id, instance_ids=instance_ids + ) + geometry = geometry_from_declared_couplings(declared) return construct_public_gonol( - source_id=f"epac.symbol:{symbol}#{occurrence}", + source_id=hub_id, relation=RELATION_SYMBOL, participants=tuple(glyphs), occurrence=occurrence, + couplings=geometry["couplings"], + structure=geometry["structure"], carried_options=( ("symbol", symbol), ("abbreviation-length", str(len(symbol))), + ( + "oriented-instance-couplings", + ";".join(f"({hub},{inst})" for hub, inst in instance_couplings), + ), ), ) diff --git a/research/epac/subatomic/test_symbol_coupling.py b/research/epac/subatomic/test_symbol_coupling.py index 74b7a93..ccd2d68 100644 --- a/research/epac/subatomic/test_symbol_coupling.py +++ b/research/epac/subatomic/test_symbol_coupling.py @@ -1,6 +1,12 @@ """Executable witnesses for the symbol-abbreviation coupling.""" # === CHECKS === +# id: check_symbol_every_letter_instance_has_oriented_hub_coupling +# proves: symbol_every_letter_instance_has_oriented_hub_coupling +# call: self::test_every_letter_instance_has_zx_and_zy +# 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 @@ -29,6 +35,28 @@ import symbol_coupling as m +def test_every_letter_instance_has_zx_and_zy(): + hydrogen = m.construct_symbol_gonol("H") + helium = m.construct_symbol_gonol("He") + iron = m.construct_symbol_gonol("Fe") + h_ids = [item["declared_ids"] for item in hydrogen.gonol.couplings] + assert len(h_ids) == 1 + assert h_ids[0][0] == hydrogen.gonol.source_id + assert h_ids[0][1] == hydrogen.gonol.participants[0].source_id + assert helium.gonol.structure["participating_dimension_count"] == 3 + assert helium.gonol.structure["ternary_coupling_declared"] is False + assert [item["declared_ids"][0] for item in helium.gonol.couplings] == [ + helium.gonol.source_id, + helium.gonol.source_id, + ] + assert [item["declared_ids"][1] for item in helium.gonol.couplings] == [ + helium.gonol.participants[0].source_id, + helium.gonol.participants[1].source_id, + ] + assert [item["slot_charges"] for item in helium.gonol.couplings] == [[2, None], [2, None]] + assert [item["slot_charges"] for item in iron.gonol.couplings] == [[26, None], [26, None]] + + def test_symbol_gonol_preserves_exact_abbreviation(): h = m.construct_symbol_gonol("H").gonol assert h.identity_glyph is None # two participants? no — H closes from one char diff --git a/research/epac/tests/test_geometry_comparison_after_construction.py b/research/epac/tests/test_geometry_comparison_after_construction.py index 2721819..051e206 100644 --- a/research/epac/tests/test_geometry_comparison_after_construction.py +++ b/research/epac/tests/test_geometry_comparison_after_construction.py @@ -38,8 +38,20 @@ def test_charged_couplings_are_the_three_dimensional_structure(self) -> None: 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)), (2, ((8, 1), 1)))) - self.assertEqual(co2_charged[0], ((2, ((6, 8), 1)), (2, ((6, 8), 1)))) + 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() From a0d0fb0dc7d971162c4fb37a3c17943ef2c977b0 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:56:09 +0000 Subject: [PATCH 17/21] Keep letters out of physics 3-structure Abbreviations are nomenclature. Physical (z, x_i)/(z, y_j) couplings stay on atom instances; letter characters do not take nuclear Z or dimensional arity. --- research/epac/docs/arity.md | 9 +- research/epac/epac_dimensional_arity.py | 13 ++- .../subatomic-affixiation-baseline.md | 12 +- research/epac/subatomic/symbol_coupling.py | 107 +++++++----------- .../epac/subatomic/test_symbol_coupling.py | 57 +++++----- 5 files changed, 86 insertions(+), 112 deletions(-) diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md index abb099e..b7b2531 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -9,12 +9,13 @@ dimension is incident on declared couplings. (z, x) ≠ (x, z) (x, z) and (y, z) ↛ (x, y, z) without an explicit proof -every instance of x has its own (z, x_i) -every instance of y has its own (z, y_j) +every physical instance of x has its own (z, x_i) +every physical instance of y has its own (z, y_j) ``` -A second occurrence is a second instance. `(z, x_0)` does not cover `x_1`. -`(x_i, z)` does not satisfy `(z, x_i)`. +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. Degree is required. For ambient `{x,y,z}` with couplings `(z,x)` and `(z,y)`: diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index 45e9ecb..3960efa 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -6,10 +6,11 @@ ``(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 instance of ``x`` has its own declared ``(z, x_i)``. Every 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)``. +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. The three-dimensional structure is the combination of declared oriented couplings, their arity charge states, and degree. That span can involve three @@ -22,10 +23,10 @@ - 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 dimension; each x_i / y_j is distinct +- instance: occurrence-addressed physical axis or atom; each x_i / y_j is distinct Collision: edcm.gonol arity_policy counts gonol participants, not dimensional -intersections. +intersections. Letters/abbreviations are nomenclature, not physics instances. """ from __future__ import annotations diff --git a/research/epac/subatomic/subatomic-affixiation-baseline.md b/research/epac/subatomic/subatomic-affixiation-baseline.md index 76c9456..f344e9d 100644 --- a/research/epac/subatomic/subatomic-affixiation-baseline.md +++ b/research/epac/subatomic/subatomic-affixiation-baseline.md @@ -273,13 +273,11 @@ law, and no scale interchange is introduced. Standing is `implemented-candidate` 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`). -- **Symbol-abbreviation coupling** (`symbol_coupling.py`): each element's one- or two-letter - abbreviation closes as a symbol gonol (one character gonol for H; two ordered character - gonols for He, Fe, ...). Every letter instance has its own oriented hub coupling - `(z, instance)`: two-letter symbols are `(z, x)` and `(z, y)` on three participating - dimensions, not a ternary coupling and not a single invented Public Gonol glyph. The - closed symbol gonol then couples to the closed element gonol at declared arity 2 via - `epac.symbol-coupling`. +- **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` diff --git a/research/epac/subatomic/symbol_coupling.py b/research/epac/subatomic/symbol_coupling.py index 2941adc..4baae93 100644 --- a/research/epac/subatomic/symbol_coupling.py +++ b/research/epac/subatomic/symbol_coupling.py @@ -1,16 +1,14 @@ -"""Symbol-abbreviation coupling for subatomic element gonols. +"""Nomenclature coupling: element gonol + abbreviation. -Adds each element's one- or two-letter abbreviation as a closed symbol gonol -and couples it to the closed subatomic element gonol through the EPAC Public -Gonol constructor. +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. -- one-letter symbols (H, B, C, ...) close from a single identity glyph; -- two-letter symbols (He, Li, Be, Fe, ...) close from two ordered character - gonols; each letter instance has its own oriented hub coupling ``(z, x_i)`` - / ``(z, y_j)``. That is the three-dimensional structure for a two-letter - abbreviation. -- the element/symbol coupling declares exactly two participants: element gonol - and symbol gonol. Geometry follows declared couplings only. +- 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. @@ -19,6 +17,7 @@ from symbol_coupling import couple_symbol receipt = couple_symbol("Fe") + assert receipt.gonol.structure is None print(receipt.receipt_digest) """ @@ -26,11 +25,11 @@ # id: epac_subatomic_symbol_coupling # module_name: symbol_coupling # module_kind: experiment -# summary: couples each subatomic element gonol with its closed one/two-letter symbol-abbreviation gonol through the EPAC Public Gonol constructor +# 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: none +# auth_boundary: letters/nomenclature are excluded from epac physics couplings # storage_boundary: none # network_boundary: none # user_data_boundary: none @@ -40,23 +39,23 @@ # rollback: remove module, tests, and generated receipts # requires: epac_public_gonol, epac_subatomic_gonol # since: 2026-08-22 -# unresolved: two-letter symbols have no single Public Gonol glyph; coupling is declared, not geometry-inferred +# 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 characters of S with one identity glyph each, and carried abbreviation-length equals len(S) +# then: participants are the exact ordered name-characters of S; no physics coupling, charge, or 3-structure is attached # class: correctness # -# id: symbol_every_letter_instance_has_oriented_hub_coupling -# given: a symbol gonol with one or two letter instances -# then: every letter instance has its own declared (z, instance) coupling; two-letter symbols occupy three participating dimensions without declaring a ternary coupling -# class: construction +# 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_arity_two -# given: a symbol-coupled gonol -# then: exactly two participants (element gonol, symbol gonol) are declared and the coupling declares arity 2 +# 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 @@ -79,33 +78,24 @@ if _PARENT not in sys.path: sys.path.insert(0, _PARENT) -from epac_dimensional_arity import ( # noqa: E402 - geometry_from_declared_couplings, - oriented_instance_couplings, - space, -) from epac_public_gonol import ( # noqa: E402 ClosedPublicGonol, PublicGonolReceipt, construct_public_gonol, replay_public_gonol, ) -from extended_atomic import SYMBOL_TO_Z # noqa: E402 import subatomic_gonol # noqa: E402 SUPPORTED_SYMBOLS: tuple[str, ...] = subatomic_gonol.SUPPORTED_SYMBOLS -RELATION_SYMBOL = "epac.symbol.abbreviation" -RELATION_COUPLING = "epac.symbol-coupling" +RELATION_SYMBOL = "epac.nomenclature.abbreviation" +RELATION_COUPLING = "epac.nomenclature.element-abbreviation" def construct_symbol_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: - """Close one symbol-abbreviation gonol from its exact ordered characters. + """Close one abbreviation as nomenclature. Not a physics gonol.""" - Every letter instance has declared ``(z, instance)``. Two letters are two - instances, so the structure is ``(z, x)`` and ``(z, y)``. - """ if symbol not in SUPPORTED_SYMBOLS: raise ValueError(f"symbol {symbol!r} is outside the supported element table") characters = tuple(symbol) @@ -113,58 +103,47 @@ def construct_symbol_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolRe for index, character in enumerate(characters): glyphs.append( construct_public_gonol( - source_id=f"epac.symbol.character:{symbol}#{occurrence}:{index}:{character}", - relation="epac.symbol.character", + 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 ) - hub_id = f"epac.symbol:{symbol}#{occurrence}" - instance_ids = tuple(item.source_id for item in glyphs) - declared = space( - (hub_id, *instance_ids), - [[hub_id, instance_id] for instance_id in instance_ids], - charges={hub_id: int(SYMBOL_TO_Z[symbol])}, - ) - instance_couplings = oriented_instance_couplings( - declared, hub_id=hub_id, instance_ids=instance_ids - ) - geometry = geometry_from_declared_couplings(declared) return construct_public_gonol( - source_id=hub_id, + source_id=f"epac.nomenclature.abbreviation:{symbol}#{occurrence}", relation=RELATION_SYMBOL, participants=tuple(glyphs), occurrence=occurrence, - couplings=geometry["couplings"], - structure=geometry["structure"], carried_options=( + ("domain", "nomenclature"), ("symbol", symbol), ("abbreviation-length", str(len(symbol))), - ( - "oriented-instance-couplings", - ";".join(f"({hub},{inst})" for hub, inst in instance_couplings), - ), ), ) def couple_symbol(symbol: str, *, occurrence: int = 0) -> PublicGonolReceipt: - """Couple a closed element gonol with its closed symbol-abbreviation gonol.""" + """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.symbol-coupled:{symbol}#{occurrence}", + source_id=f"epac.nomenclature.element-abbreviation:{symbol}#{occurrence}", relation=RELATION_COUPLING, participants=(element, symbol_gonol), - couplings=( - { - "relation": RELATION_COUPLING, - "arity": 2, - "dimensions": [element.source_id, symbol_gonol.source_id], - }, - ), occurrence=occurrence, - carried_options=(("symbol", symbol),), + carried_options=( + ("domain", "nomenclature"), + ("symbol", symbol), + ), ) diff --git a/research/epac/subatomic/test_symbol_coupling.py b/research/epac/subatomic/test_symbol_coupling.py index ccd2d68..73911cd 100644 --- a/research/epac/subatomic/test_symbol_coupling.py +++ b/research/epac/subatomic/test_symbol_coupling.py @@ -1,9 +1,9 @@ -"""Executable witnesses for the symbol-abbreviation coupling.""" +"""Executable witnesses for nomenclature abbreviation coupling.""" # === CHECKS === -# id: check_symbol_every_letter_instance_has_oriented_hub_coupling -# proves: symbol_every_letter_instance_has_oriented_hub_coupling -# call: self::test_every_letter_instance_has_zx_and_zy +# 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 # @@ -13,9 +13,9 @@ # mutates: none # cleanup: none # -# id: check_symbol_coupling_arity_two -# proves: symbol_coupling_arity_two -# call: self::test_symbol_coupling_arity_two +# id: check_symbol_coupling_two_participants +# proves: symbol_coupling_two_participants +# call: self::test_symbol_coupling_two_participants # mutates: none # cleanup: none # @@ -35,31 +35,25 @@ import symbol_coupling as m -def test_every_letter_instance_has_zx_and_zy(): - hydrogen = m.construct_symbol_gonol("H") +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") - h_ids = [item["declared_ids"] for item in hydrogen.gonol.couplings] - assert len(h_ids) == 1 - assert h_ids[0][0] == hydrogen.gonol.source_id - assert h_ids[0][1] == hydrogen.gonol.participants[0].source_id - assert helium.gonol.structure["participating_dimension_count"] == 3 - assert helium.gonol.structure["ternary_coupling_declared"] is False - assert [item["declared_ids"][0] for item in helium.gonol.couplings] == [ - helium.gonol.source_id, - helium.gonol.source_id, - ] - assert [item["declared_ids"][1] for item in helium.gonol.couplings] == [ - helium.gonol.participants[0].source_id, - helium.gonol.participants[1].source_id, - ] - assert [item["slot_charges"] for item in helium.gonol.couplings] == [[2, None], [2, None]] - assert [item["slot_charges"] for item in iron.gonol.couplings] == [[26, None], [26, None]] + 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 h.identity_glyph is None # two participants? no — H closes from one char assert len(h.participants) == 1 assert dict(h.carried_options)["abbreviation-length"] == "1" @@ -72,15 +66,16 @@ def test_symbol_gonol_preserves_exact_abbreviation(): assert [p.identity_glyph for p in fe.participants] == ["F", "e"] -def test_symbol_coupling_arity_two(): +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 - coupling = receipt.gonol.couplings[0] - assert coupling["relation"] == "epac.symbol-coupling" - assert coupling["arity"] == 2 - assert len(coupling["dimensions"]) == 2 + 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(): From e4f474e95d4b395dfa4b19e537e8cfde9792f5c6 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:40:38 +0000 Subject: [PATCH 18/21] Couple every electron instance to the nucleus Atomic 3-structure is one (nucleus, electron_i) coupling per electron, with slot charges (Z, -1). That closure stays inside the element gonol. Molecular water remains (O#2, H#0) and (O#2, H#1). Letters stay names. --- research/epac/docs/arity.md | 5 ++ ...-molecular-geometry-from-element-gonols.md | 8 ++-- research/epac/epac_dimensional_arity.py | 3 +- research/epac/epac_periodic.py | 48 +++++++++++++++++++ .../epac/tests/test_molecular_affixiation.py | 12 +++++ .../tests/test_periodic_element_gonols.py | 32 +++++++++++++ 6 files changed, 104 insertions(+), 4 deletions(-) diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md index b7b2531..9506f3e 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -17,6 +17,11 @@ 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. +At atomic scale the hub is the nucleus and every electron instance has its own +`(nucleus, electron_i)` with slot charges `(Z, -1)`. That structure closes +inside the element gonol. Molecular scale does not reopen it: 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 diff --git a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md index 8c10009..4a1025f 100644 --- a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -26,10 +26,12 @@ scopes) until comparison. ```text element Public Gonols (epac.public_gonol) + -> 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 couplings with per-slot Z and Möbius ε - -> three-dimensional structure = those couplings + charge states + degree - -> molecular Public Gonol + -> declared oriented (center, ligand_i) couplings with per-slot Z and Möbius ε + -> molecular three-dimensional structure = those atom-instance couplings + charge states + degree + -> molecular Public Gonol (closed atoms remain atomic participants) -> construction invariants -> (only then) compare to sealed known chemistry ``` diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index 3960efa..0cbdefe 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -10,7 +10,8 @@ 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. +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 diff --git a/research/epac/epac_periodic.py b/research/epac/epac_periodic.py index 3c62e55..20d5115 100644 --- a/research/epac/epac_periodic.py +++ b/research/epac/epac_periodic.py @@ -1,5 +1,10 @@ """Element gonols closed as EPAC Public Gonols from atomic electron-shell structure. +Every electron instance has its own ``(nucleus, electron_i)`` coupling. +Nuclear ``Z`` and electron charge ``-1`` are the slot charges. That atomic +3-structure closes with the element gonol. Molecular construction must not +reopen it. Letters and abbreviations are not these axes. + Usage guidance -------------- Each electron, shell, nucleus, and element is an EPAC Public Gonol on the @@ -10,6 +15,7 @@ oxygen = construct_element_gonol("O") assert oxygen.constructor_id == "epac.public_gonol" + assert len(oxygen.structure["parts"]) == 8 """ from __future__ import annotations @@ -17,6 +23,11 @@ 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, @@ -24,6 +35,9 @@ replay_public_gonol, ) +# Elementary charge in units of e. Nuclear Z is proton count in the same units. +ELECTRON_CHARGE = -1 + def _carrier_glyph(text: str) -> str | None: if len(text) == 1: @@ -95,6 +109,35 @@ def _construct_nucleus(record: AtomicRecord, *, atom_occurrence: int) -> ClosedP ).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.""" @@ -129,6 +172,9 @@ def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolR ("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", @@ -136,6 +182,8 @@ def construct_element_gonol(symbol: str, *, occurrence: int = 0) -> PublicGonolR participants=(nucleus, *shells), carried_options=carried, occurrence=occurrence, + couplings=geometry["couplings"], + structure=geometry["structure"], ) diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index c1103e2..2d60364 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -66,6 +66,18 @@ def test_declared_couplings_are_binary_and_do_not_fill_ambient(self) -> None: 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"]) + ) def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: molecules = construct_declared_molecules() diff --git a/research/epac/tests/test_periodic_element_gonols.py b/research/epac/tests/test_periodic_element_gonols.py index 8295628..90f3edb 100644 --- a/research/epac/tests/test_periodic_element_gonols.py +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -9,6 +9,7 @@ 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 from epac_periodic import construct_element_gonol, construct_periodic_table, replay_element_gonol @@ -55,6 +56,37 @@ def test_hund_unpaired_and_shells(self) -> None: 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"]) + def test_construction_does_not_carry_shape_labels(self) -> None: receipt = construct_element_gonol("N") blob = str(receipt.gonol.carried_options) + receipt.gonol.relation From 4b262735fb57c91c0bff57b417b38b1515fc6d61 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:47:08 +0000 Subject: [PATCH 19/21] Represent each local 3-structure as a quaternion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four components hold one 3: Möbius ε plus the three axis charges. The extra coordinate is the scalar, not a fourth ambient axis. Hamilton product is not a coupling proof and does not install (x,y,z). --- research/epac/docs/arity.md | 8 ++ ...-molecular-geometry-from-element-gonols.md | 1 + research/epac/epac_dimensional_arity.py | 98 ++++++++++++++++++- research/epac/epac_public_gonol.py | 5 +- research/epac/tests/test_epac_arity.py | 43 ++++++++ .../epac/tests/test_molecular_affixiation.py | 13 +++ .../tests/test_periodic_element_gonols.py | 17 +++- 7 files changed, 180 insertions(+), 5 deletions(-) diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md index 9506f3e..f5c8ef4 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -41,6 +41,14 @@ 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`. diff --git a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md index 4a1025f..32bc8ae 100644 --- a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -31,6 +31,7 @@ element Public Gonols (epac.public_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 diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index 0cbdefe..33df26c 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -17,6 +17,10 @@ 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 @@ -25,9 +29,11 @@ - 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 @@ -38,6 +44,9 @@ # 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( { @@ -45,6 +54,7 @@ "overlap-closure", "permutation-identity", "shared-dimension-join", + "hamilton-product-closure", } ) @@ -320,12 +330,71 @@ def install_proven_coupling(declared: DimensionalSpace, proof: CouplingProof) -> ) +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. This is not an - inferred cartesian embedding and not a ternary coupling. + 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) @@ -355,6 +424,10 @@ def structure_from_charged_couplings(declared: DimensionalSpace) -> Mapping[str, ), "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), } @@ -411,6 +484,20 @@ def topology_structure_readout(structure: Mapping[str, object]) -> tuple[object, ) +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( @@ -487,6 +574,9 @@ def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, ob "DimensionalSpace", "FORBIDDEN_INFERENCE_RULES", "MOBIUS_EPSILON_T0", + "QUATERNION_REPRESENTATION_DIMENSION", + "QUATERNION_SCALAR_AXIS", + "REPRESENTED_STRUCTURE_DIMENSION", "charged_structure_readout", "coupling", "degree_relations", @@ -495,8 +585,12 @@ def _common_records(couplings: tuple[Coupling, ...]) -> Iterable[Mapping[str, ob "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", diff --git a/research/epac/epac_public_gonol.py b/research/epac/epac_public_gonol.py index a159b0b..74ec3cd 100644 --- a/research/epac/epac_public_gonol.py +++ b/research/epac/epac_public_gonol.py @@ -6,8 +6,9 @@ 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. No cartesian embedding -and no ternary coupling are inferred. +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 -------------- diff --git a/research/epac/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py index da852e2..9420822 100644 --- a/research/epac/tests/test_epac_arity.py +++ b/research/epac/tests/test_epac_arity.py @@ -10,6 +10,9 @@ from epac_dimensional_arity import ( CouplingProof, DimensionalArityError, + QUATERNION_REPRESENTATION_DIMENSION, + QUATERNION_SCALAR_AXIS, + REPRESENTED_STRUCTURE_DIMENSION, charged_structure_readout, coupling, degree_relations, @@ -17,8 +20,10 @@ 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, @@ -123,6 +128,12 @@ def test_overlap_is_not_an_installable_proof(self) -> None: 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: @@ -187,6 +198,38 @@ def test_common_ids_are_not_a_coupling(self) -> None: 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"], ()) + if __name__ == "__main__": unittest.main() diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index 2d60364..772a039 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -9,6 +9,7 @@ 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 @@ -78,6 +79,18 @@ def test_declared_couplings_are_binary_and_do_not_fill_ambient(self) -> None: self.assertTrue( all(part["coupling"][0] == "epac.nucleus:O#2" for part in oxygen.structure["parts"]) ) + 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_is_the_same_mobius_loop(self) -> None: molecules = construct_declared_molecules() diff --git a/research/epac/tests/test_periodic_element_gonols.py b/research/epac/tests/test_periodic_element_gonols.py index 90f3edb..d28b04a 100644 --- a/research/epac/tests/test_periodic_element_gonols.py +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -9,7 +9,7 @@ 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 +from epac_dimensional_arity import charged_structure_readout, quaternion_structure_readout from epac_periodic import construct_element_gonol, construct_periodic_table, replay_element_gonol @@ -86,6 +86,21 @@ def test_every_electron_instance_has_nucleus_coupling(self) -> None: 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_construction_does_not_carry_shape_labels(self) -> None: receipt = construct_element_gonol("N") From 44877be6d757ea5a7b5a1f56fb36ca57bb2c4613 Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sun, 23 Aug 2026 15:49:32 +0000 Subject: [PATCH 20/21] Repair EPAC review findings --- research/epac/epac_atomic.py | 36 +++-- research/epac/epac_dimensional_arity.py | 131 ++++++++++++------ research/epac/epac_molecular.py | 67 ++++++++- research/epac/epac_public_gonol.py | 74 +++++++++- research/epac/subatomic/extended_atomic.py | 9 +- research/epac/subatomic/subatomic_gonol.py | 32 +++-- .../epac/subatomic/test_extended_atomic.py | 5 + .../epac/subatomic/test_subatomic_gonol.py | 18 +++ research/epac/tests/test_epac_arity.py | 24 ++++ research/epac/tests/test_epac_public_gonol.py | 54 +++++++- .../epac/tests/test_molecular_affixiation.py | 11 +- .../tests/test_periodic_element_gonols.py | 4 + 12 files changed, 370 insertions(+), 95 deletions(-) diff --git a/research/epac/epac_atomic.py b/research/epac/epac_atomic.py index da265be..82729ce 100644 --- a/research/epac/epac_atomic.py +++ b/research/epac/epac_atomic.py @@ -207,17 +207,23 @@ def _promoted_unpaired(electrons: tuple[ElectronState, ...]) -> tuple[ElectronSt 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 = [e for e in valence if e.l == 0 and e.paired and e.m_s == 1] - if not s_pairs or not empty_p_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, unpairing s and adding p. - promoted_from_s = s_pairs[0] + # 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=1, + 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]), @@ -228,17 +234,17 @@ def _promoted_unpaired(electrons: tuple[ElectronState, ...]) -> tuple[ElectronSt paired=False, ) unpaired_s = ElectronState( - index=promoted_from_s.index, - n=promoted_from_s.n, + index=remaining_s.index, + n=remaining_s.n, l=0, - m_l=0, - m_s=1, - shell=promoted_from_s.shell, - subshell=promoted_from_s.subshell, - angular_id=promoted_from_s.angular_id, - radial_nodes=promoted_from_s.radial_nodes, - z_eff=promoted_from_s.z_eff, - e_rydberg=promoted_from_s.e_rydberg, + 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, ) diff --git a/research/epac/epac_dimensional_arity.py b/research/epac/epac_dimensional_arity.py index 33df26c..feaf55b 100644 --- a/research/epac/epac_dimensional_arity.py +++ b/research/epac/epac_dimensional_arity.py @@ -38,8 +38,8 @@ from __future__ import annotations +from collections.abc import Iterable, Mapping, Sequence from dataclasses import dataclass -from typing import Iterable, Mapping, Sequence # Established UCNS Möbius frame sign at t=0: ε in (t, ε) ~ (t+n, (-1)^n ε). @@ -172,6 +172,17 @@ def __post_init__(self) -> None: ) 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( @@ -179,15 +190,20 @@ def __post_init__(self) -> None: ) +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: - if not isinstance(dimension_ids, Sequence) or isinstance(dimension_ids, (str, bytes)): - raise DimensionalArityError("coupling dimensions must be an ordered declaration sequence") + 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 dimension_ids)) + return Coupling(tuple(Dimension(item, charge_map.get(item)) for item in ids)) def space( @@ -196,16 +212,14 @@ def space( proofs: Sequence[CouplingProof] = (), charges: Mapping[str, int] | None = None, ) -> DimensionalSpace: - if not isinstance(ambient_ids, Sequence) or isinstance(ambient_ids, (str, bytes)): - raise DimensionalArityError("ambient dimensions must be a declared sequence") + 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: - if not isinstance(item, Sequence) or isinstance(item, (str, bytes)): - raise DimensionalArityError("each coupling declaration must be an ordered sequence") - declared.append(Coupling(tuple(by_id[name] if name in by_id else Dimension(name) for name in item))) + 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), @@ -233,7 +247,7 @@ def observed_common_ids(left: Coupling, right: Coupling) -> frozenset[str]: def has_declared_coupling(declared: DimensionalSpace, dimension_ids: Sequence[str]) -> bool: - target = tuple(dimension_ids) + target = _require_dimension_id_sequence(dimension_ids, field="coupling lookup dimensions") return any(item.declared_ids == target for item in declared.couplings) @@ -299,34 +313,50 @@ def oriented_instance_couplings( 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.""" - if proof.conclusion.declared_ids in {item.declared_ids for item in declared.couplings}: - return DimensionalSpace( - ambient_dimensions=declared.ambient_dimensions, - couplings=declared.couplings, - proofs=declared.proofs + (proof,), - ) - missing = [ - name - for name in proof.conclusion.declared_ids - if name not in {axis.id for axis in declared.ambient_dimensions} - ] - if missing: - raise DimensionalArityError( - f"proven coupling {proof.conclusion.declared_ids} uses undeclared dimensions {tuple(missing)}" - ) + 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 proof.premises: + 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 + (proof.conclusion,), - proofs=declared.proofs + (proof,), + couplings=declared.couplings + (bound_conclusion,), + proofs=declared.proofs + (bound_proof,), ) @@ -415,6 +445,7 @@ def structure_from_charged_couplings(declared: DimensionalSpace) -> Mapping[str, "charge": item.dimension.charge, "degree": item.degree, "slot_degrees": item.slot_degrees, + "incidences": item.incidences, } for item in degrees if item.degree @@ -432,11 +463,29 @@ def structure_from_charged_couplings(declared: DimensionalSpace) -> Mapping[str, 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. @@ -445,25 +494,23 @@ def charged_structure_readout(structure: Mapping[str, object]) -> tuple[object, """ parts = tuple( - sorted( - ( - int(part["arity"]), - _tuple_tree(part["charge_state"]), - _tuple_tree(part["coupling"]), - ) - for part in structure["parts"] + ( + int(part["arity"]), + _tuple_tree(part["charge_state"]), + _tuple_tree(part["coupling"]), ) + for part in structure["parts"] ) degree = tuple( - sorted( - ( - int(item["degree"]), - _tuple_tree(item["slot_degrees"]), - item["charge"], - ) - for item in structure["degree"] + ( + 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, diff --git a/research/epac/epac_molecular.py b/research/epac/epac_molecular.py index a07f0d7..e3f8f11 100644 --- a/research/epac/epac_molecular.py +++ b/research/epac/epac_molecular.py @@ -111,13 +111,55 @@ def _declared_dimensional_space( return space(ambient, declarations, charges=charges) -def _mobius_coupling() -> Mapping[str, Any]: +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", - "parameter": "turn-index", + "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]), @@ -148,15 +190,21 @@ def construct_molecule(formula: str) -> MolecularConstruction: used_promotion = False else: ligands = tuple(item for item in participants if item is not center) - needed = len(ligands) center_record = _record_for(center) ground = tuple((e.l, e.m_l) for e in center_record.unpaired_valence) - used_promotion = needed > len(ground) - center_sites = _attachment_set(center_record, needed) ligand_sites = tuple( tuple((e.l, e.m_l) for e in _record_for(item).unpaired_valence) for item in ligands ) - mobius = _mobius_coupling() + 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: @@ -183,6 +231,8 @@ def construct_molecule(formula: str) -> MolecularConstruction: "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], @@ -199,6 +249,11 @@ def construct_molecule(formula: str) -> MolecularConstruction: "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"], diff --git a/research/epac/epac_public_gonol.py b/research/epac/epac_public_gonol.py index 74ec3cd..1eb8176 100644 --- a/research/epac/epac_public_gonol.py +++ b/research/epac/epac_public_gonol.py @@ -73,9 +73,12 @@ 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 ( @@ -191,13 +194,66 @@ def _geometry(identity_glyph: str | None, carrier_index: int | None) -> dict[str def _freeze_json(value: Any) -> Any: if value is None or isinstance(value, (str, int, float, bool)): return value - if isinstance(value, Mapping): - return {str(key): _freeze_json(item) for key, item in value.items()} - if isinstance(value, Sequence) and not isinstance(value, (str, bytes)): - return [_freeze_json(item) for item in 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, @@ -269,9 +325,12 @@ def _receipt_payload( def canonical_receipt_bytes(payload: Mapping[str, Any]) -> bytes: - return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode( - "utf-8" - ) + return json.dumps( + _json_ready(payload), + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") def _digest(payload: Mapping[str, Any]) -> str: @@ -308,6 +367,7 @@ def construct_public_gonol( ) 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( diff --git a/research/epac/subatomic/extended_atomic.py b/research/epac/subatomic/extended_atomic.py index d922474..c5db19e 100644 --- a/research/epac/subatomic/extended_atomic.py +++ b/research/epac/subatomic/extended_atomic.py @@ -62,14 +62,7 @@ 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_atomic import ( # noqa: E402 +from epac_atomic import ( AtomicRecord, ElectronState, atomic_record as base_atomic_record, diff --git a/research/epac/subatomic/subatomic_gonol.py b/research/epac/subatomic/subatomic_gonol.py index f8c4119..4fee545 100644 --- a/research/epac/subatomic/subatomic_gonol.py +++ b/research/epac/subatomic/subatomic_gonol.py @@ -20,7 +20,7 @@ Usage guidance: - PYTHONPATH=":/src" python3 - <<'PY' + PYTHONPATH=":/subatomic:/src" python3 - <<'PY' from subatomic_gonol import construct_subatomic_gonol, replay_subatomic_gonol receipt = construct_subatomic_gonol("He") @@ -29,28 +29,21 @@ PY """ -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 extended_atomic import ( # noqa: E402 +from extended_atomic import ( EXTENDED_SYMBOLS, SYMBOL_TO_Z, AtomicRecord, atomic_record, ) -from epac_public_gonol import ( # noqa: E402 +from epac_public_gonol import ( ClosedPublicGonol, PublicGonolReceipt, construct_public_gonol, replay_public_gonol, ) -import element_affixiation_candidate as identity # noqa: E402 -import nuclear_harmonic_candidates as harmonics # noqa: E402 +import element_affixiation_candidate as identity +import nuclear_harmonic_candidates as harmonics # === MODULE_BUILD === # id: epac_subatomic_gonol @@ -111,6 +104,19 @@ def _harmonic_rows(symbol: str) -> tuple[harmonics.HarmonicCandidate, ...]: ) +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)), @@ -209,7 +215,7 @@ def construct_subatomic_gonol(symbol: str, *, occurrence: int = 0) -> PublicGono harmonic_surviving = ",".join( candidate.candidate_id for candidate in _harmonic_rows(symbol) - if any(harmonics.recurrence_test(candidate).values()) + if _harmonic_survives_symbol(candidate, symbol) ) carried = [ ("symbol", symbol), diff --git a/research/epac/subatomic/test_extended_atomic.py b/research/epac/subatomic/test_extended_atomic.py index 2d93be0..4797ceb 100644 --- a/research/epac/subatomic/test_extended_atomic.py +++ b/research/epac/subatomic/test_extended_atomic.py @@ -56,3 +56,8 @@ def test_extended_atomic_stays_candidate(): 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_subatomic_gonol.py b/research/epac/subatomic/test_subatomic_gonol.py index 48b1245..2895dbf 100644 --- a/research/epac/subatomic/test_subatomic_gonol.py +++ b/research/epac/subatomic/test_subatomic_gonol.py @@ -102,3 +102,21 @@ def test_stays_cross_domain_hypothesis(): 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/tests/test_epac_arity.py b/research/epac/tests/test_epac_arity.py index 9420822..b10cac6 100644 --- a/research/epac/tests/test_epac_arity.py +++ b/research/epac/tests/test_epac_arity.py @@ -43,6 +43,8 @@ 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) @@ -148,6 +150,15 @@ def test_explicit_proof_can_install_higher_arity(self) -> None: 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)} @@ -230,6 +241,19 @@ def test_four_dimensions_represent_each_local_three(self) -> None: 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 index cfc697a..c0fc6cb 100644 --- a/research/epac/tests/test_epac_public_gonol.py +++ b/research/epac/tests/test_epac_public_gonol.py @@ -75,10 +75,62 @@ def test_charged_couplings_are_the_structure(self) -> None: self.assertFalse(receipt.structure["inferred_cartesian_embedding"]) self.assertEqual( [part["charge_state"] for part in receipt.structure["parts"]], - [[[8, 1], 1], [[8, 1], 1]], + [((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( diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index 772a039..75af59f 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -34,7 +34,9 @@ def test_unpaired_valence_and_shells_are_used(self) -> None: 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"], ["1:1", "1:0"]) + 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() @@ -92,10 +94,13 @@ def test_declared_couplings_are_binary_and_do_not_fill_ambient(self) -> None: self.assertEqual(quaternion_structure_readout(molecules["H2"].receipt.structure), ()) self.assertEqual(len(quaternion_structure_readout(molecules["CH4"].receipt.structure)), 6) - def test_ucns_coupling_is_the_same_mobius_loop(self) -> None: + 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())), 1) + 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() diff --git a/research/epac/tests/test_periodic_element_gonols.py b/research/epac/tests/test_periodic_element_gonols.py index d28b04a..d975b3e 100644 --- a/research/epac/tests/test_periodic_element_gonols.py +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -52,6 +52,10 @@ def test_hund_unpaired_and_shells(self) -> None: 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}) From a7ec865c78c7a8b5b0e806bf13d10fdbdb739c7c Mon Sep 17 00:00:00 2001 From: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:10:26 +0000 Subject: [PATCH 21/21] Build nuclei from proton and neutron gonols Element construction starts with nucleon precursors. Each proton and neutron is a closed gonol; the nucleus is their affixiation. Neutrons couple to protons as (proton, neutron) with charges (+1, 0). Electrons still couple to the closed nucleus. Molecular couplings do not reopen nucleons. --- research/epac/docs/arity.md | 13 +- ...-molecular-geometry-from-element-gonols.md | 6 +- research/epac/epac_periodic.py | 122 ++++++++++++++++-- .../epac/tests/test_molecular_affixiation.py | 10 ++ .../tests/test_periodic_element_gonols.py | 73 ++++++++++- 5 files changed, 204 insertions(+), 20 deletions(-) diff --git a/research/epac/docs/arity.md b/research/epac/docs/arity.md index f5c8ef4..4f96a5a 100644 --- a/research/epac/docs/arity.md +++ b/research/epac/docs/arity.md @@ -17,10 +17,15 @@ 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. -At atomic scale the hub is the nucleus and every electron instance has its own -`(nucleus, electron_i)` with slot charges `(Z, -1)`. That structure closes -inside the element gonol. Molecular scale does not reopen it: water remains -`(O#2, H#0)` and `(O#2, H#1)`. +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)`: diff --git a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md index 32bc8ae..8a83573 100644 --- a/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md +++ b/research/epac/docs/preregistration-molecular-geometry-from-element-gonols.md @@ -25,7 +25,8 @@ scopes) until comparison. ## Frozen pipeline ```text -element Public Gonols (epac.public_gonol) +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 @@ -39,7 +40,8 @@ element Public Gonols (epac.public_gonol) ## Inputs allowed in construction -- atomic number Z, default isotope A, proton and neutron counts +- 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² diff --git a/research/epac/epac_periodic.py b/research/epac/epac_periodic.py index 20d5115..3e1c401 100644 --- a/research/epac/epac_periodic.py +++ b/research/epac/epac_periodic.py @@ -1,21 +1,22 @@ -"""Element gonols closed as EPAC Public Gonols from atomic electron-shell structure. +"""Element gonols closed as EPAC Public Gonols from nucleon then electron structure. -Every electron instance has its own ``(nucleus, electron_i)`` coupling. -Nuclear ``Z`` and electron charge ``-1`` are the slot charges. That atomic -3-structure closes with the element gonol. Molecular construction must not -reopen it. Letters and abbreviations are not these axes. +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 electron, shell, nucleus, and element is an EPAC Public Gonol on the -UCNS carrier. This module does not use ``edcm.gonol``. Nothing molecular is -encoded here. +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 - oxygen = construct_element_gonol("O") - assert oxygen.constructor_id == "epac.public_gonol" - assert len(oxygen.structure["parts"]) == 8 + 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 @@ -35,8 +36,13 @@ replay_public_gonol, ) -# Elementary charge in units of e. Nuclear Z is proton count in the same units. +# 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: @@ -94,10 +100,98 @@ def _construct_shell( ).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="epac.atomic.nucleus", + relation=NUCLEUS_RELATION, + participants=(*protons, *neutrons), carried_options=( ("Z", str(record.Z)), ("A", str(record.A)), @@ -106,6 +200,8 @@ def _construct_nucleus(record: AtomicRecord, *, atom_occurrence: int) -> ClosedP ("symbol", record.symbol), ), occurrence=0, + couplings=couplings, + structure=structure, ).gonol diff --git a/research/epac/tests/test_molecular_affixiation.py b/research/epac/tests/test_molecular_affixiation.py index 75af59f..fa601d6 100644 --- a/research/epac/tests/test_molecular_affixiation.py +++ b/research/epac/tests/test_molecular_affixiation.py @@ -81,6 +81,16 @@ def test_declared_couplings_are_binary_and_do_not_fill_ambient(self) -> None: 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( diff --git a/research/epac/tests/test_periodic_element_gonols.py b/research/epac/tests/test_periodic_element_gonols.py index d975b3e..a2c80ca 100644 --- a/research/epac/tests/test_periodic_element_gonols.py +++ b/research/epac/tests/test_periodic_element_gonols.py @@ -9,7 +9,12 @@ 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, quaternion_structure_readout +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 @@ -106,6 +111,72 @@ def test_every_electron_instance_has_nucleus_coupling(self) -> None: 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