diff --git a/core/physics/__init__.py b/core/physics/__init__.py index ae2d6c75..215b11bd 100644 --- a/core/physics/__init__.py +++ b/core/physics/__init__.py @@ -82,6 +82,16 @@ from core.physics.holographic_vault import ( HolographicVaultStore, SealedMode, ) +from core.physics.wave_energy_boundary import ( + CrystallizationDecision, + assess_wave_trajectory, + crystallization_for_holographic_seal, + energy_profile_from_wave, + fibonacci_tau_schedule, + recency_band_index, + wave_unitary_residual, +) +from core.physics.fibonacci_search import fibonacci_number __all__ = [ "SalienceOperator", "SalienceMap", "FieldRegion", @@ -116,4 +126,12 @@ __all__ = [ "assess_trajectory", "energy_boundary_ok", "relative_holonomy", "trajectory_divergence", "HolographicVaultError", "HolographicVaultStore", "SealedMode", + "CrystallizationDecision", + "assess_wave_trajectory", + "crystallization_for_holographic_seal", + "energy_profile_from_wave", + "fibonacci_tau_schedule", + "recency_band_index", + "wave_unitary_residual", + "fibonacci_number", ] diff --git a/core/physics/fibonacci_search.py b/core/physics/fibonacci_search.py index 2d4b3df3..37276900 100644 --- a/core/physics/fibonacci_search.py +++ b/core/physics/fibonacci_search.py @@ -41,7 +41,7 @@ class SearchTrace: certificate: dict = field(default_factory=dict) -def _fibonacci(n: int) -> int: +def fibonacci_number(n: int) -> int: """F_0=0, F_1=1, … standard Fibonacci. n may be 0.""" if n < 0: raise ValueError("fibonacci index must be non-negative") @@ -51,6 +51,11 @@ def _fibonacci(n: int) -> int: return a +def _fibonacci(n: int) -> int: + """Internal alias — prefer :func:`fibonacci_number` at call sites.""" + return fibonacci_number(n) + + def _assert_sampled_unimodality(eval_values: dict[float, float]) -> None: """Fail-closed if sorted samples are not unimodal about the observed min.""" sorted_points = sorted(eval_values.keys()) @@ -168,6 +173,8 @@ def fibonacci_section_search( __all__ = [ + "fibonacci_number", + "BoundedUnimodalObjective", "SearchTrace", "fibonacci_section_search", diff --git a/core/physics/wave_energy_boundary.py b/core/physics/wave_energy_boundary.py new file mode 100644 index 00000000..4474bdab --- /dev/null +++ b/core/physics/wave_energy_boundary.py @@ -0,0 +1,219 @@ +"""P10 Trace B — wave unitary residual → energy boundary + multi-scale τ. + +ADR-0241 cohesion package P10: + +1. **Wire wave residual into energy / trajectory gates** — coherence is not a + free-floating float; it is :meth:`WaveManifold.measure_unitary_residual`. +2. **Multi-scale recency** ``τ_n = F_n · τ_0`` as a constants table (not dogma). +3. **Crystallization E0–E1 → holographic seal policy** — only low-energy + classes with closed residual may SPECULATIVE-seal; COHERENT still requires + authorized teaching review outside this module. + +Serve path remains quarantined (no import from ``chat/runtime.py``). +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Sequence + +import numpy as np + +from core.physics.energy import EnergyClass, EnergyProfile, FieldEnergyOperator +from core.physics.fibonacci_search import fibonacci_number +from core.physics.trajectory_invariants import ( + TrajectoryAssessment, + assess_trajectory, +) +from core.physics.wave_manifold import WaveManifold + +_DEFAULT_EPSILON_DRIFT = 1e-6 +_DEFAULT_TAU0 = 1.0 + + +def wave_unitary_residual( + psi: np.ndarray, + *, + manifold: WaveManifold | None = None, + epsilon_drift: float = _DEFAULT_EPSILON_DRIFT, +) -> float: + """Unitary residual ‖ψ~ψ − 1‖_F dual-checked via WaveManifold.""" + m = manifold if manifold is not None else WaveManifold(epsilon_drift=epsilon_drift) + return float(m.measure_unitary_residual(psi)) + + +def energy_profile_from_wave( + psi: np.ndarray, + *, + operator: FieldEnergyOperator | None = None, + manifold: WaveManifold | None = None, + epsilon_drift: float = _DEFAULT_EPSILON_DRIFT, + **compute_kwargs: object, +) -> EnergyProfile: + """Build an EnergyProfile with coherence_residual from the wave field. + + Structural axes (convergence, activation, aspect) remain caller-supplied; + residual is never invented — it is measured on ψ. + """ + residual = wave_unitary_residual( + psi, manifold=manifold, epsilon_drift=epsilon_drift + ) + op = operator if operator is not None else FieldEnergyOperator() + # FieldEnergyOperator.compute takes coherence_residual as float kwarg. + kwargs = dict(compute_kwargs) + kwargs["coherence_residual"] = residual + return op.compute(**kwargs) # type: ignore[arg-type] + + +def assess_wave_trajectory( + versors: Sequence[np.ndarray], + *, + eps_trajectory: float = 1e-5, + kappa: float = 1.0, + dt: float = 1.0, + epsilon_drift: float = _DEFAULT_EPSILON_DRIFT, + manifold: WaveManifold | None = None, +) -> TrajectoryAssessment: + """Trajectory gate with exertion energy from max wave unitary residual. + + ``E_exertion`` = max residual along the path. + ``E_sensory`` = ``epsilon_drift`` (GoldTether-scale sensory budget). + Closed unit paths stay under the boundary when residual ≤ κ · ε. + """ + if not versors: + # Delegate fail-closed empty handling to trajectory_invariants. + return assess_trajectory( + versors, + E_exertion=0.0, + E_sensory=float(epsilon_drift), + eps_trajectory=eps_trajectory, + kappa=kappa, + dt=dt, + ) + residuals = [ + wave_unitary_residual(v, manifold=manifold, epsilon_drift=epsilon_drift) + for v in versors + ] + e_exertion = float(max(residuals)) + e_sensory = float(epsilon_drift) + return assess_trajectory( + versors, + E_exertion=e_exertion, + E_sensory=e_sensory, + eps_trajectory=eps_trajectory, + kappa=kappa, + dt=dt, + ) + + +def fibonacci_tau_schedule( + tau0: float = _DEFAULT_TAU0, + *, + levels: int = 8, +) -> tuple[float, ...]: + """Multi-scale recency hierarchy τ_n = F_n · τ_0 for n = 1..levels. + + Constants table only — not a runtime dogma. F_1=1, F_2=1, F_3=2, … + """ + t0 = float(tau0) + if not (t0 > 0.0) or t0 != t0: # NaN check via != + raise ValueError("tau0 must be a positive finite scalar") + n = int(levels) + if n < 1: + raise ValueError("levels must be >= 1") + return tuple(float(fibonacci_number(i) * t0) for i in range(1, n + 1)) + + +def recency_band_index(age: float, taus: Sequence[float]) -> int: + """Smallest band index with age ≤ τ_n, or ``len(taus)`` if beyond schedule.""" + a = float(age) + if a < 0.0: + raise ValueError("age must be non-negative") + for i, tau in enumerate(taus): + if a <= float(tau) + 1e-15: + return i + return len(taus) + + +@dataclass(frozen=True, slots=True) +class CrystallizationDecision: + """E0–E1 crystallization gate aligned with holographic SPECULATIVE seal policy.""" + + energy: EnergyProfile + unitary_residual: float + vault_candidate: bool + residual_closed: bool + may_speculative_seal: bool + reason: str + epsilon_drift: float + + def as_dict(self) -> dict[str, object]: + return { + "energy_class": self.energy.energy_class.value, + "unitary_residual": self.unitary_residual, + "vault_candidate": self.vault_candidate, + "residual_closed": self.residual_closed, + "may_speculative_seal": self.may_speculative_seal, + "reason": self.reason, + "epsilon_drift": self.epsilon_drift, + } + + +def crystallization_for_holographic_seal( + psi: np.ndarray, + *, + epsilon_drift: float = _DEFAULT_EPSILON_DRIFT, + manifold: WaveManifold | None = None, + operator: FieldEnergyOperator | None = None, + **energy_kwargs: object, +) -> CrystallizationDecision: + """Decide whether ψ may enter the SPECULATIVE holographic seal path. + + Policy (Trace B ↔ Trace A): + * residual must be closed (≤ epsilon_drift) — fail-closed, no repair + * energy class must be vault_candidate (E0/E1) + * both required for ``may_speculative_seal`` + + COHERENT promotion is never authorized here. + """ + residual = wave_unitary_residual( + psi, manifold=manifold, epsilon_drift=epsilon_drift + ) + energy = energy_profile_from_wave( + psi, + operator=operator, + manifold=manifold, + epsilon_drift=epsilon_drift, + **energy_kwargs, + ) + vault = bool(energy.energy_class.vault_candidate) + closed = bool(residual <= float(epsilon_drift)) + may_seal = vault and closed + if may_seal: + reason = "e0_e1_closed_residual_speculative_seal_ok" + elif not closed: + reason = "residual_not_closed" + elif not vault: + reason = "energy_class_not_vault_candidate" + else: + reason = "refused" + return CrystallizationDecision( + energy=energy, + unitary_residual=residual, + vault_candidate=vault, + residual_closed=closed, + may_speculative_seal=may_seal, + reason=reason, + epsilon_drift=float(epsilon_drift), + ) + + +__all__ = [ + "CrystallizationDecision", + "assess_wave_trajectory", + "crystallization_for_holographic_seal", + "energy_profile_from_wave", + "fibonacci_tau_schedule", + "recency_band_index", + "wave_unitary_residual", +] diff --git a/docs/adr/ADR-0241-wave-field-driven-hyperbolic-atlas-and-resonant-cognition.md b/docs/adr/ADR-0241-wave-field-driven-hyperbolic-atlas-and-resonant-cognition.md index 1f518531..49a37d03 100644 --- a/docs/adr/ADR-0241-wave-field-driven-hyperbolic-atlas-and-resonant-cognition.md +++ b/docs/adr/ADR-0241-wave-field-driven-hyperbolic-atlas-and-resonant-cognition.md @@ -1,6 +1,6 @@ # ADR-0241: Wave-Field Driven Hyperbolic Atlas and Resonant Algebraic Cognition -**Status**: Proposed — substrate + entity suite + ADR-0242 packing/Fibonacci + P9 Trace A wave_seam (SPECULATIVE holographic seal / hypothesis-vs-evidence reconstruct); remaining cohesion packages P10 energy + P12 governance; acceptance path: Joshua review +**Status**: Proposed — substrate + entity suite + ADR-0242 packing/Fibonacci + P9 Trace A wave_seam + P10 Trace B wave_energy_boundary (residual→energy/τ/crystallization); remaining cohesion package P12 governance (+ optional P11 Rust); acceptance path: Joshua review **Date**: 2026-07-13 **Deciders**: Joshua Shay + multi-model R&D **Traceability**: Issue #14, parent #10 diff --git a/docs/research/third-door-blueprint-fidelity.md b/docs/research/third-door-blueprint-fidelity.md index f83878a2..f5c6c848 100644 --- a/docs/research/third-door-blueprint-fidelity.md +++ b/docs/research/third-door-blueprint-fidelity.md @@ -302,6 +302,7 @@ PY | Golden-Angle atlas packing \(d_{\min}=0.12\) | 🟢 ADR-0242 (`atlas_packing`; CGA null-point \(d\)) | | Fibonacci κ search | 🟢 ADR-0242 (`fibonacci_search`) | | Contemplation Trace A SPECULATIVE holographic seal (P9) | 🟢 `core/contemplation/wave_seam.py` (hypothesis vs COHERENT evidence) | +| Energy boundary + multi-scale τ (P10 Trace B) | 🟢 `wave_energy_boundary` (wave residual → energy/trajectory; τ_n=F_n·τ_0; E0–E1 crystallization) | ### Subsumption map (Slice 2–3) | Operator | Delegation | @@ -337,6 +338,7 @@ PY | Durable holographic vault spectrum — 🟢 HolographicVaultStore | ADR-0241 | | Entity cohesion I-01…I-05 + Trace A/B | cohesion master plan | | Contemplation Trace A SPECULATIVE seal (P9) — 🟢 wave_seam | ADR-0241 P9 | +| Energy boundary + multi-scale τ (P10) — 🟢 wave_energy_boundary | ADR-0241 P10 | | Atlas packing + Fibonacci κ (ADR-0242) — 🟢 packing + search | PR #37 / ADR-0242 | Closing a gap = flip its `xfail` in `tests/test_third_door_blueprint_fidelity.py` (or the ADR-0241 / cohesion suite) to a passing behavioral test and delete the matching characterization lock. That is the definition of "done right" here. diff --git a/tests/test_adr_0241_wave_energy_boundary.py b/tests/test_adr_0241_wave_energy_boundary.py new file mode 100644 index 00000000..9f09cd70 --- /dev/null +++ b/tests/test_adr_0241_wave_energy_boundary.py @@ -0,0 +1,151 @@ +"""P10 Trace B — wave residual → energy boundary + Fibonacci τ + crystallization. + +ADR-0241 cohesion package P10: + * Unitary residual from WaveManifold feeds energy / trajectory gates. + * Multi-scale recency τ_n = F_n · τ_0 is a constants table (not dogma). + * E0–E1 crystallization + closed residual may SPECULATIVE-seal; else refuse. + * Serve remains quarantined from this module. +""" + +from __future__ import annotations + +import ast +from pathlib import Path + +import numpy as np +import pytest + +from algebra.rotor import make_rotor_from_angle +from core.physics.energy import EnergyClass +from core.physics.wave_energy_boundary import ( + CrystallizationDecision, + assess_wave_trajectory, + crystallization_for_holographic_seal, + energy_profile_from_wave, + fibonacci_tau_schedule, + recency_band_index, + wave_unitary_residual, +) +from core.physics.trajectory_invariants import TrajectoryInvariantError + +_ROOT = Path(__file__).resolve().parents[1] + + +def _closed(angle: float = 0.3, plane: int = 6) -> np.ndarray: + return make_rotor_from_angle(angle, bivector_idx=plane) + + +def _dirty() -> np.ndarray: + v = np.zeros(32, dtype=np.float64) + v[0] = 0.5 + v[1] = 0.5 + return v + + +# --- Wave residual wiring --------------------------------------------------- + + +def test_wave_unitary_residual_closed_under_eps(): + r = wave_unitary_residual(_closed(0.25)) + assert r < 1e-6 + + +def test_wave_unitary_residual_dirty_above_eps(): + r = wave_unitary_residual(_dirty()) + assert r > 1e-6 + + +def test_energy_profile_from_wave_uses_residual(): + profile = energy_profile_from_wave(_closed(0.1)) + assert profile.coherence_residual < 1e-6 + # Low residual + defaults → crystallizable class (E0/E1). + assert profile.energy_class in {EnergyClass.E0, EnergyClass.E1} + + +def test_energy_profile_from_wave_high_residual_feeds_raw(): + profile = energy_profile_from_wave(_dirty()) + # Operator clamps residual into [0,1] for the scalar mix. + assert profile.coherence_residual > 0.0 + + +def test_assess_wave_trajectory_closed_path_energy_ok(): + steps = [_closed(0.1 * i, plane=6) for i in range(1, 5)] + assessment = assess_wave_trajectory(steps) + assert assessment.energy_ok is True + assert assessment.n_steps == 4 + assert assessment.within_replay_bound is True + + +def test_assess_wave_trajectory_refuses_empty(): + with pytest.raises(TrajectoryInvariantError): + assess_wave_trajectory([]) + + +# --- Fibonacci multi-scale τ ------------------------------------------------ + + +def test_fibonacci_tau_schedule_matches_F_n_tau0(): + # F_1=1, F_2=1, F_3=2, F_4=3, F_5=5 + taus = fibonacci_tau_schedule(tau0=0.5, levels=5) + assert taus == (0.5, 0.5, 1.0, 1.5, 2.5) + + +def test_fibonacci_tau_schedule_rejects_bad_inputs(): + with pytest.raises(ValueError): + fibonacci_tau_schedule(tau0=0.0, levels=3) + with pytest.raises(ValueError): + fibonacci_tau_schedule(tau0=1.0, levels=0) + + +def test_recency_band_index_progressive(): + taus = fibonacci_tau_schedule(tau0=1.0, levels=5) # 1,1,2,3,5 + assert recency_band_index(0.5, taus) == 0 + assert recency_band_index(1.0, taus) == 0 + assert recency_band_index(1.5, taus) == 2 + assert recency_band_index(10.0, taus) == len(taus) # beyond schedule + + +# --- Crystallization ↔ holographic seal policy ------------------------------ + + +def test_crystallization_closed_e0_may_speculative_seal(): + decision = crystallization_for_holographic_seal(_closed(0.12)) + assert isinstance(decision, CrystallizationDecision) + assert decision.residual_closed is True + assert decision.vault_candidate is True + assert decision.may_speculative_seal is True + assert decision.energy.energy_class.vault_candidate is True + + +def test_crystallization_dirty_refuses_seal(): + decision = crystallization_for_holographic_seal(_dirty()) + assert decision.residual_closed is False + assert decision.may_speculative_seal is False + + +def test_crystallization_high_activity_not_vault_candidate(): + # Force E3/E4 via structural inputs while residual stays closed. + decision = crystallization_for_holographic_seal( + _closed(0.05), + convergence_density=20, + activation_count=20, + current_cycle=1, + last_activation_cycle=1, + anchor_adjacent=True, + ) + assert decision.residual_closed is True + assert decision.vault_candidate is False + assert decision.may_speculative_seal is False + + +# --- Serve quarantine ------------------------------------------------------- + + +def test_serve_runtime_does_not_import_wave_energy_boundary(): + tree = ast.parse((_ROOT / "chat/runtime.py").read_text(encoding="utf-8")) + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom) and node.module: + assert "wave_energy_boundary" not in node.module + if isinstance(node, ast.Import): + for alias in node.names: + assert "wave_energy_boundary" not in alias.name diff --git a/tests/test_third_door_cohesion.py b/tests/test_third_door_cohesion.py index 8a818d18..2b109bec 100644 --- a/tests/test_third_door_cohesion.py +++ b/tests/test_third_door_cohesion.py @@ -62,6 +62,7 @@ def test_phase0_a04_serve_path_quarantines_wave_and_fibonacci(): "fibonacci_search", "atlas_packing", "wave_seam", # P9 Trace A — contemplation only, never serve + "wave_energy_boundary", # P10 Trace B — energy/τ gate, never serve } banned_substrings = ( "wave_manifold", @@ -69,6 +70,7 @@ def test_phase0_a04_serve_path_quarantines_wave_and_fibonacci(): "fibonacci_search", "atlas_packing", "wave_seam", + "wave_energy_boundary", ) for node in ast.walk(tree): if isinstance(node, ast.Import):