feat(third-door): Slice-2 wave subsumption (surprise/Procrustes/GoldTether/biography)
Collapse parallel residual/projection paths into WaveManifold: - surprise 32-vec residual → compute_spectral_leakage - single non-null Procrustes pair → wave_analogical_polar - coherence_residual / GoldTether drift → measure_unitary_residual - biography integrate → unitary lock-in + holonomy_encode Null-point Kabsch retained. Discovery wiring unchanged (no teaching import).
This commit is contained in:
parent
231b4c664c
commit
541257f81d
8 changed files with 167 additions and 45 deletions
|
|
@ -19,6 +19,7 @@ import numpy as np
|
|||
from algebra.cl41 import N_COMPONENTS
|
||||
from algebra.holonomy import holonomy_encode, holonomy_similarity
|
||||
from algebra.versor import unitize_versor, versor_condition
|
||||
from core.physics.wave_manifold import WaveManifold
|
||||
|
||||
_CLOSURE_TOL = 1e-6
|
||||
_TELEMETRY_SCHEMA = "biography_holonomy_v1"
|
||||
|
|
@ -69,16 +70,32 @@ def integrate_biography(
|
|||
"""Integrate ordered identity/session versors into a biography holonomy blade.
|
||||
|
||||
Order is load-bearing. Empty trajectory is refused (no confabulated self).
|
||||
|
||||
ADR-0241 Slice 2: each trajectory versor and the integrated blade must pass
|
||||
the wave unitary residual (standing-wave / unitary-propagator lock-in). The
|
||||
holonomy blade itself remains reconstruction-over-storage via
|
||||
:func:`holonomy_encode` (no raw experience dump).
|
||||
"""
|
||||
if not trajectory:
|
||||
raise ValueError("biography trajectory must be non-empty")
|
||||
closed = [_as_versor(v, f"trajectory[{i}]") for i, v in enumerate(trajectory)]
|
||||
wave = WaveManifold()
|
||||
for i, v in enumerate(closed):
|
||||
r = wave.measure_unitary_residual(v)
|
||||
if r >= _CLOSURE_TOL:
|
||||
raise ValueError(
|
||||
f"trajectory[{i}] failed wave unitary residual: {r:.3e}"
|
||||
)
|
||||
blade = holonomy_encode(closed, alpha=alpha)
|
||||
cond = versor_condition(blade)
|
||||
if cond >= _CLOSURE_TOL:
|
||||
raise ValueError(f"biography blade not closed: {cond:.3e}")
|
||||
blade_arr = np.asarray(blade, dtype=np.float64)
|
||||
r_blade = wave.measure_unitary_residual(blade_arr)
|
||||
if r_blade >= _CLOSURE_TOL:
|
||||
raise ValueError(f"biography blade wave unitary residual: {r_blade:.3e}")
|
||||
return BiographyHolonomyBlade(
|
||||
blade=np.asarray(blade, dtype=np.float64),
|
||||
blade=blade_arr,
|
||||
n_steps=len(closed),
|
||||
trajectory_hash=_trajectory_hash(closed),
|
||||
closure=float(cond),
|
||||
|
|
|
|||
|
|
@ -723,10 +723,22 @@ def _procrustes_multivector_pairs(
|
|||
pair_residuals=pair_res,
|
||||
)
|
||||
|
||||
# Field conjugacy: sandwich residual, stacked multi-pair constraints.
|
||||
V, residual_norm = _field_conjugacy_versor(src_list, tgt_list)
|
||||
pair_res = tuple(procrustes_residual(s, t, V) for s, t in zip(src_list, tgt_list))
|
||||
residual_norm = float(np.sqrt(sum(r * r for r in pair_res) / len(pair_res)))
|
||||
# Field conjugacy / wave polar (ADR-0241 Slice 2): single non-null pair uses
|
||||
# WaveManifold.wave_analogical_polar as the canonical sandwich conjugator.
|
||||
# Multi-pair keeps the stacked conjugacy engine (same geometry family).
|
||||
# Null-point clouds already returned above (Kabsch point-cloud path).
|
||||
if len(src_list) == 1:
|
||||
from core.physics.wave_manifold import WaveManifold
|
||||
|
||||
V = WaveManifold().wave_analogical_polar(src_list[0], tgt_list[0])
|
||||
pair_res = (procrustes_residual(src_list[0], tgt_list[0], V),)
|
||||
residual_norm = float(pair_res[0])
|
||||
else:
|
||||
V, residual_norm = _field_conjugacy_versor(src_list, tgt_list)
|
||||
pair_res = tuple(
|
||||
procrustes_residual(s, t, V) for s, t in zip(src_list, tgt_list)
|
||||
)
|
||||
residual_norm = float(np.sqrt(sum(r * r for r in pair_res) / len(pair_res)))
|
||||
return ConformalProcrustesResult(
|
||||
versor=V,
|
||||
residual_norm=residual_norm,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import numpy as np
|
|||
from algebra.cl41 import N_COMPONENTS, geometric_product, reverse
|
||||
from algebra.rotor import rotor_power, word_transition_rotor
|
||||
from algebra.versor import versor_condition, versor_unit_residual
|
||||
from core.physics.wave_manifold import WaveManifold
|
||||
|
||||
_CLOSURE_TOL = 1e-6
|
||||
_NEAR_ZERO = 1e-12
|
||||
|
|
@ -98,11 +99,11 @@ def coherence_residual(F: np.ndarray) -> float:
|
|||
"""Public one-shot residual for tests and harnesses.
|
||||
|
||||
R = || F · reverse(F) − 1 ||_F (dual-checked against reverse(F)).
|
||||
|
||||
Canonical path (ADR-0241 Slice 2): :meth:`WaveManifold.measure_unitary_residual`
|
||||
— unitary wave amplitude drift, not a parallel residual implementation.
|
||||
"""
|
||||
F_arr = _as_mv(F)
|
||||
r = float(versor_unit_residual(F_arr))
|
||||
r_rev = float(versor_unit_residual(reverse(F_arr)))
|
||||
return max(r, r_rev)
|
||||
return WaveManifold().measure_unitary_residual(_as_mv(F))
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -202,8 +203,18 @@ class GoldTetherMonitor:
|
|||
raw :func:`coherence_residual` stays the fail-closed *closure* gate.
|
||||
"""
|
||||
F_arr = _as_mv(F)
|
||||
drift = coherence_residual(F_arr)
|
||||
drift_term = drift / self.epsilon_drift if self.epsilon_drift > 0.0 else drift
|
||||
# Unitary amplitude drift (wave substrate) + optional chiral readout.
|
||||
# Chiral charge is structurally ~0 on real even field-states (#19 family);
|
||||
# included as a non-negative integrity term so a future non-vacuous spinor
|
||||
# path can move the residual without a second API.
|
||||
wave = WaveManifold()
|
||||
drift = wave.measure_unitary_residual(F_arr)
|
||||
chiral = abs(float(wave.chiral_charge(F_arr)))
|
||||
drift_term = (
|
||||
(drift + chiral) / self.epsilon_drift
|
||||
if self.epsilon_drift > 0.0
|
||||
else (drift + chiral)
|
||||
)
|
||||
scale = float(np.linalg.norm(F_arr))
|
||||
if self.gold_invariants and scale > _NEAR_ZERO:
|
||||
min_dist = min(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ from algebra.cga import cga_inner
|
|||
from algebra.cl41 import N_COMPONENTS, grade_project
|
||||
from algebra.versor import versor_condition
|
||||
from core.physics.dynamic_manifold import conformal_procrustes
|
||||
from core.physics.wave_manifold import WaveManifold, WaveSpectralLeakageError
|
||||
|
||||
_ETA5 = np.diag([1.0, 1.0, 1.0, 1.0, -1.0]).astype(np.float64)
|
||||
_NEAR_ZERO = 1e-12
|
||||
|
|
@ -166,7 +167,9 @@ def surprise_residual(
|
|||
residual = x_arr - B @ coeffs
|
||||
return residual, float(np.linalg.norm(residual))
|
||||
|
||||
# --- 32-vector (Cl(4,1) multivector) branch: cga_inner projection --------
|
||||
# --- 32-vector (Cl(4,1) multivector) branch: wave spectral leakage -------
|
||||
# Canonical path (ADR-0241 Slice 2): metric proj via WaveManifold; no parallel
|
||||
# Euclidean Gram-Schmidt. Typed degenerate-span refusal preserved.
|
||||
if x_arr.shape[0] == N_COMPONENTS:
|
||||
if B.shape[0] != N_COMPONENTS and B.shape[1] == N_COMPONENTS:
|
||||
B = B.T
|
||||
|
|
@ -176,13 +179,10 @@ def surprise_residual(
|
|||
if k == 0:
|
||||
return x_arr.copy(), float(np.linalg.norm(x_arr))
|
||||
cols = [B[:, i] for i in range(k)]
|
||||
gram = np.array(
|
||||
[[cga_inner(cols[i], cols[j]) for j in range(k)] for i in range(k)],
|
||||
dtype=np.float64,
|
||||
)
|
||||
rhs = np.array([cga_inner(cols[i], x_arr) for i in range(k)], dtype=np.float64)
|
||||
coeffs = _metric_projection_coeffs(B, gram, rhs)
|
||||
residual = x_arr - B @ coeffs
|
||||
try:
|
||||
residual, energy = WaveManifold().compute_spectral_leakage(x_arr, cols)
|
||||
except WaveSpectralLeakageError as exc:
|
||||
raise SurpriseResidualError(exc.reason, **exc.disclosure) from exc
|
||||
|
||||
# Grade-support containment: residual is a linear combination of x and the
|
||||
# basis columns, so its grades can only be a subset of theirs. ``allowed``
|
||||
|
|
@ -199,7 +199,7 @@ def surprise_residual(
|
|||
"grade_leak", leaked=sorted(leaked), allowed=sorted(allowed)
|
||||
)
|
||||
|
||||
return residual, float(np.linalg.norm(residual))
|
||||
return residual, float(energy)
|
||||
|
||||
raise ValueError("surprise_residual expects 5-vector or 32-vector x")
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Off-serving until explicit gates; dual-checked unitary residual.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence, Tuple
|
||||
from typing import Any, Sequence, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
|
@ -28,6 +28,19 @@ _CLOSURE_TOL = 1e-6
|
|||
_NEAR_ZERO = 1e-12
|
||||
_NONSIMPLE_TOL = 1e-6
|
||||
|
||||
|
||||
class WaveSpectralLeakageError(ValueError):
|
||||
"""Fail-closed spectral leakage (metric-degenerate resonant span).
|
||||
|
||||
Mapped to :class:`core.physics.surprise.SurpriseResidualError` at the
|
||||
surprise boundary so discovery / dual contracts keep a stable error type.
|
||||
"""
|
||||
|
||||
def __init__(self, reason: str, **disclosure: Any) -> None:
|
||||
self.reason = reason
|
||||
self.disclosure = dict(disclosure)
|
||||
super().__init__(f"spectral_leakage refused [{reason}]: {self.disclosure}")
|
||||
|
||||
# Unit pseudoscalar I₅ = e1 e2 e3 e4 e5 (central; I² = −1 in Cl(4,1)).
|
||||
_I5 = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
_I5[31] = 1.0
|
||||
|
|
@ -139,12 +152,21 @@ def _metric_project(
|
|||
)
|
||||
rhs = np.array([cga_inner(cols[i], x_arr) for i in range(k)], dtype=np.float64)
|
||||
# Fail-closed on metric-degenerate span (null direction with no reciprocal).
|
||||
rank_b = int(np.linalg.matrix_rank(np.column_stack(cols)))
|
||||
Bmat = np.column_stack(cols)
|
||||
rank_b = int(np.linalg.matrix_rank(Bmat))
|
||||
rank_g = int(np.linalg.matrix_rank(gram))
|
||||
if rank_g < rank_b:
|
||||
raise ValueError(
|
||||
f"spectral_leakage: degenerate metric span "
|
||||
f"(rank_basis={rank_b}, rank_gram={rank_g})"
|
||||
_u, _sv, vh = np.linalg.svd(gram)
|
||||
degenerate_combo = [round(float(v), 6) for v in vh[-1]]
|
||||
null_columns = [
|
||||
i for i in range(k) if abs(float(gram[i, i])) < _NEAR_ZERO
|
||||
]
|
||||
raise WaveSpectralLeakageError(
|
||||
"degenerate_metric_span",
|
||||
rank_basis=rank_b,
|
||||
rank_gram=rank_g,
|
||||
null_columns=null_columns,
|
||||
degenerate_combo=degenerate_combo,
|
||||
)
|
||||
coeffs, *_ = np.linalg.lstsq(gram, rhs, rcond=None)
|
||||
projection = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
|
|
@ -231,17 +253,17 @@ class WaveManifold:
|
|||
) -> np.ndarray:
|
||||
"""Recover sandwich conjugator R with ψ_B ≈ R ψ_A ~R (polar / conjugacy).
|
||||
|
||||
Delegates to field conjugacy in :func:`conformal_procrustes` (Kabsch path
|
||||
when null-points; conjugacy otherwise). Returns a closed unit versor.
|
||||
Canonical single-field analogy rotor. Uses the field-conjugacy engine in
|
||||
``dynamic_manifold`` (lazy import — avoids import cycle; Procrustes
|
||||
multi-pair path calls this for single non-null pairs).
|
||||
"""
|
||||
# Local import keeps module graph light and avoids cycles at import time.
|
||||
from core.physics.dynamic_manifold import conformal_procrustes
|
||||
# Lazy import: dynamic_manifold may call WaveManifold at runtime.
|
||||
from core.physics.dynamic_manifold import _field_conjugacy_versor
|
||||
|
||||
psi_A = _as_mv(psi_A, "ψ_A")
|
||||
psi_B = _as_mv(psi_B, "ψ_B")
|
||||
R, _residual = conformal_procrustes(psi_A, psi_B)
|
||||
R_arr = _as_mv(R, "R_polar")
|
||||
return _require_closed_rotor(R_arr, name="R_polar")
|
||||
psi_A_arr = _as_mv(psi_A, "ψ_A")
|
||||
psi_B_arr = _as_mv(psi_B, "ψ_B")
|
||||
R, _residual = _field_conjugacy_versor([psi_A_arr], [psi_B_arr])
|
||||
return _require_closed_rotor(R, name="R_polar")
|
||||
|
||||
# --- Chiral spinor charge ------------------------------------------------
|
||||
|
||||
|
|
@ -266,4 +288,4 @@ class WaveManifold:
|
|||
)
|
||||
|
||||
|
||||
__all__ = ["WaveManifold"]
|
||||
__all__ = ["WaveManifold", "WaveSpectralLeakageError"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# ADR-0241: Wave-Field Driven Hyperbolic Atlas and Resonant Algebraic Cognition
|
||||
|
||||
**Status**: Proposed — substrate implemented (`core/physics/wave_manifold.py`); operator subsumption pending (acceptance path: Slice-2 wiring + Joshua review)
|
||||
**Status**: Proposed — substrate + Slice-2 operator subsumption implemented (`wave_manifold` + surprise/Procrustes/GoldTether/biography delegates); acceptance path: Joshua review + merge
|
||||
**Date**: 2026-07-13
|
||||
**Deciders**: Joshua Shay + multi-model R&D
|
||||
**Traceability**: Issue #14, parent #10
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
| W2 | Spectral leakage surprise | ADR-0241 §2.4B | 🟢 substrate landed (metric proj) | ADR-0241 |
|
||||
| W3 | Wave polar analogy (Procrustes upgrade) | ADR-0241 §2.4A | 🟢 substrate landed (conjugacy polar) | ADR-0241 |
|
||||
| W4 | Unitary residual + chiral charge readout | ADR-0241 §2.4C–D | 🟢 substrate landed (Q structural 0 in real Cl(4,1); see §12) | ADR-0241 / #18 |
|
||||
| W5 | Biography resonant lock-in | ADR-0241 + ADR-0240 | ⚫ not started | ADR-0241 |
|
||||
| W5 | Biography resonant lock-in | ADR-0241 + ADR-0240 | 🟡 unitary lock-in on integrate; full holographic store deferred | ADR-0241 |
|
||||
| W6 | `core_ha` deprecation / absorption | deprecation plan | 🟡 docs-only (no live tree) | ADR-0241 |
|
||||
| — | Biography holonomy | (ADR-0240; not in blueprints) | 🟢 sound (pointwise) | — |
|
||||
| — | Temporal admissibility gate | (ADR-0240; not in blueprints) | 🟢 sound | — |
|
||||
|
|
@ -251,13 +251,12 @@ PY
|
|||
|
||||
---
|
||||
|
||||
## 12. Wave-field substrate (ADR-0241) — 🟢 substrate + ⚫ subsumption pending
|
||||
## 12. Wave-field substrate (ADR-0241) — 🟢 substrate + Slice-2 subsumption
|
||||
|
||||
> **Status (2026-07-14):** ADR + deprecation plan + `core/physics/wave_manifold.py`
|
||||
> landed on `feat/third-door-wave-field-substrate`. Behavioral suite
|
||||
> `tests/test_adr_0241_wave_manifold.py` is **GREEN**. Operator subsumption
|
||||
> (surprise / Procrustes / GoldTether / biography **delegate into** wave primitives)
|
||||
> is still pending Slice 2 — substrate exists; parallel path not yet collapsed.
|
||||
> + Slice-2 operator subsumption on `feat/third-door-wave-field-substrate`.
|
||||
> Behavioral suite `tests/test_adr_0241_wave_manifold.py` is **GREEN**. Third-Door
|
||||
> operators **delegate into** wave primitives (no parallel residual/projection path).
|
||||
|
||||
### Spec (ADR-0241)
|
||||
- Continuous multivector wave-field \(\psi \in Cl(4,1)\) (32-coeff) as the representation layer under Cartan/Procrustes, Surprise, GoldTether, Biography.
|
||||
|
|
@ -274,10 +273,20 @@ PY
|
|||
- Containment: no serve-path import of `wave_manifold` until explicit gate; physics still never imports teaching.
|
||||
- #18 bootstrap/prune of \(\mathcal{I}_{gold}\) **stays deferred** while wave GoldTether subsumption lands.
|
||||
|
||||
### Slice-2 subsumption map (landed)
|
||||
| Operator | Delegation |
|
||||
|----------|------------|
|
||||
| `surprise_residual` (32-vec) | `WaveManifold.compute_spectral_leakage` |
|
||||
| `conformal_procrustes` single non-null field pair | `WaveManifold.wave_analogical_polar` |
|
||||
| Null-point / (5,K) clouds | Kabsch point-cloud path retained (compatibility) |
|
||||
| `coherence_residual` / GoldTether drift | `WaveManifold.measure_unitary_residual` (+ chiral term in harmonized residual) |
|
||||
| `integrate_biography` | unitary residual lock-in on trajectory + blade; encode still `holonomy_encode` |
|
||||
|
||||
### What is not done
|
||||
- Subsumption of `surprise` / `dynamic_manifold` / `goldtether` / `biography` into wave primitives (Slice 2).
|
||||
- Biography resonant lock-in (W5).
|
||||
- Multi-pair field conjugacy still uses stacked conjugacy engine directly (same family; not dual path).
|
||||
- Full resonant standing-wave memory / holographic recall store (W5 beyond unitary lock-in).
|
||||
- Rust/MLX acceleration of exp-map / cross-spectral (optional later).
|
||||
- #18 gold-set bootstrap/prune still deferred.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -291,7 +300,7 @@ PY
|
|||
| Grade-5 pseudoscalar preservation gate — ⚪ RETIRED (vacuous; see §5) | #19 (closed) |
|
||||
| Surprise: metric projection + productivity polarity + DiscoveryCandidate wiring — 🟢 done | #20 (math #26; wiring #31) |
|
||||
| Absent proposals: sensorimotor + ADR-DAG | #21 |
|
||||
| Wave-field substrate (unitary, leakage, polar, chiral) — 🟢 substrate; subsumption pending | ADR-0241 |
|
||||
| Wave-field substrate + Slice-2 operator subsumption — 🟢 | ADR-0241 |
|
||||
| `core_ha` deprecation — 🟡 docs-only (no live tree) | ADR-0241 / deprecation plan |
|
||||
|
||||
Closing a gap = flip its `xfail` in `tests/test_third_door_blueprint_fidelity.py` (or the ADR-0241 suite) to a passing behavioral test and delete the matching characterization lock. That is the definition of "done right" here.
|
||||
|
|
|
|||
|
|
@ -215,3 +215,54 @@ def test_wave_manifold_module_does_not_import_teaching():
|
|||
assert not alias.name.startswith("teaching")
|
||||
if isinstance(node, ast.ImportFrom) and node.module:
|
||||
assert not node.module.startswith("teaching")
|
||||
|
||||
|
||||
# --- Slice 2: operator subsumption (no parallel path) ----------------------
|
||||
|
||||
|
||||
def test_surprise_residual_delegates_to_wave_spectral_leakage():
|
||||
"""32-vec surprise residual matches WaveManifold.compute_spectral_leakage."""
|
||||
from core.physics.surprise import surprise_residual
|
||||
|
||||
M = WaveManifold()
|
||||
mode = _e(1) + 0.5 * _e(3)
|
||||
mode = mode / float(np.linalg.norm(mode))
|
||||
x = 0.7 * mode + 0.4 * _e(2)
|
||||
B = mode.reshape(N_COMPONENTS, 1)
|
||||
sur_vec, sur_n = surprise_residual(x, B)
|
||||
leak_vec, leak_n = M.compute_spectral_leakage(x, [mode])
|
||||
assert np.allclose(sur_vec, leak_vec, atol=1e-12)
|
||||
assert abs(sur_n - leak_n) < 1e-12
|
||||
|
||||
|
||||
def test_coherence_residual_delegates_to_wave_unitary():
|
||||
"""GoldTether coherence_residual is WaveManifold.measure_unitary_residual."""
|
||||
from core.physics.goldtether import coherence_residual
|
||||
|
||||
M = WaveManifold()
|
||||
psi = _unit_rotor(0.42, plane=8)
|
||||
assert abs(coherence_residual(psi) - M.measure_unitary_residual(psi)) < 1e-15
|
||||
|
||||
|
||||
def test_conformal_procrustes_single_field_uses_wave_polar():
|
||||
"""Single non-null field Procrustes recovers the same conjugator as wave polar."""
|
||||
from core.physics.dynamic_manifold import conformal_procrustes
|
||||
|
||||
M = WaveManifold()
|
||||
psi_A = _unit_rotor(0.15, plane=6)
|
||||
R_true = _unit_rotor(0.55, plane=10)
|
||||
psi_B = versor_apply(R_true, psi_A)
|
||||
V_proc, res = conformal_procrustes(psi_A, psi_B)
|
||||
V_wave = M.wave_analogical_polar(psi_A, psi_B)
|
||||
# Both must sandwich A → B; residual small.
|
||||
assert res < 1e-5
|
||||
err_p = min(
|
||||
float(np.linalg.norm(versor_apply(V_proc, psi_A) - psi_B)),
|
||||
float(np.linalg.norm(versor_apply(-V_proc, psi_A) - psi_B)),
|
||||
)
|
||||
err_w = min(
|
||||
float(np.linalg.norm(versor_apply(V_wave, psi_A) - psi_B)),
|
||||
float(np.linalg.norm(versor_apply(-V_wave, psi_A) - psi_B)),
|
||||
)
|
||||
assert err_p < 1e-5
|
||||
assert err_w < 1e-5
|
||||
|
|
|
|||
Loading…
Reference in a new issue