Merge pull request 'feat(third-door): #21 trajectory invariants + ADR-DAG embedding' (#35) from feat/third-door-trajectory-invariants-adr-dag into main
Third-Door #21: trajectory invariants + ADR-DAG Ψ(M) embedding; Python geometry authority.
This commit is contained in:
commit
0b8e46ce5a
7 changed files with 584 additions and 7 deletions
23
core/adr/__init__.py
Normal file
23
core/adr/__init__.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""ADR-DAG conformal embedding (R&D-Revised §2.4 / issue #21).
|
||||
|
||||
Deterministic embedding of ADR markdown into Cl(4,1) bivector space and
|
||||
master-blade drift checks for proposal coherence.
|
||||
"""
|
||||
|
||||
from core.adr.validator import (
|
||||
AdrDagValidationError,
|
||||
embed_adr_markdown,
|
||||
master_architecture_blade,
|
||||
proposal_drift,
|
||||
simple_bivector_project,
|
||||
validate_proposal_against_master,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AdrDagValidationError",
|
||||
"embed_adr_markdown",
|
||||
"master_architecture_blade",
|
||||
"proposal_drift",
|
||||
"simple_bivector_project",
|
||||
"validate_proposal_against_master",
|
||||
]
|
||||
168
core/adr/validator.py
Normal file
168
core/adr/validator.py
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
"""
|
||||
core/adr/validator.py
|
||||
|
||||
ADR-DAG conformal embedding Ψ(M) (R&D-Revised §2.4 / #21).
|
||||
|
||||
SHA-256(M) → 10×3-byte segments → c_k ∈ [−1, 1]
|
||||
→ 10 basis bivectors (planes 6..15) → simple-bivector projection
|
||||
→ master blade = successive wedge of load-bearing ADR embeddings
|
||||
→ proposal drift = ‖B_p ∧ A_master‖
|
||||
|
||||
Cross-check: does **not** reimplement GeometricDelta ABI validation
|
||||
(``core/abi/geometric_delta_validator.py``). This module embeds ADR text into
|
||||
geometry; that module validates GeometricDelta envelopes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from typing import Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
from algebra.cl41 import N_COMPONENTS, geometric_product, grade_project
|
||||
|
||||
_BIVECTOR_PLANES = tuple(range(6, 16)) # 10 planes
|
||||
_NEAR_ZERO = 1e-12
|
||||
_SIMPLE_G4_TOL = 1e-9
|
||||
|
||||
|
||||
class AdrDagValidationError(ValueError):
|
||||
"""Fail-closed refusal from ADR-DAG embedding / drift checks."""
|
||||
|
||||
def __init__(self, reason: str, **disclosure) -> None:
|
||||
self.reason = reason
|
||||
self.disclosure = dict(disclosure)
|
||||
super().__init__(f"adr_dag refused [{reason}]: {self.disclosure}")
|
||||
|
||||
|
||||
def _grade_mass(v: np.ndarray) -> int:
|
||||
for g in range(5, -1, -1):
|
||||
if float(np.linalg.norm(grade_project(v, g))) > _NEAR_ZERO:
|
||||
return g
|
||||
return 0
|
||||
|
||||
|
||||
def multivector_wedge(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
||||
"""Grade-raising wedge approximation: grade-project of geometric product.
|
||||
|
||||
For pure blades this matches the outer product grade. Used for master-blade
|
||||
assembly and drift (B_p ∧ A_master).
|
||||
"""
|
||||
a = np.asarray(A, dtype=np.float64)
|
||||
b = np.asarray(B, dtype=np.float64)
|
||||
ga, gb = _grade_mass(a), _grade_mass(b)
|
||||
target = min(5, ga + gb)
|
||||
if target == 0:
|
||||
return grade_project(geometric_product(a, b), 0)
|
||||
return grade_project(geometric_product(a, b), target).astype(np.float64)
|
||||
|
||||
|
||||
def simple_bivector_project(B: np.ndarray) -> np.ndarray:
|
||||
"""Project a multivector generator onto a simple bivector.
|
||||
|
||||
Spec intent: pure grade-2 support that is *simple* (single plane). Pure
|
||||
multiplane grade-2 has nontrivial ``⟨B B⟩₄``; we always collapse to the
|
||||
dominant plane when more than one plane is occupied (deterministic).
|
||||
"""
|
||||
arr = np.asarray(B, dtype=np.float64)
|
||||
if arr.shape != (N_COMPONENTS,):
|
||||
raise AdrDagValidationError("bad_shape", shape=tuple(arr.shape))
|
||||
B2 = grade_project(arr, 2).astype(np.float64)
|
||||
occupied = [i for i in _BIVECTOR_PLANES if abs(float(B2[i])) > _NEAR_ZERO]
|
||||
if len(occupied) <= 1:
|
||||
return B2
|
||||
# Multiplane → dominant-plane collapse (simple by construction).
|
||||
best_i = max(occupied, key=lambda i: abs(float(B2[i])))
|
||||
out = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
out[best_i] = float(B2[best_i])
|
||||
return out
|
||||
|
||||
|
||||
def embed_adr_markdown(markdown: str) -> np.ndarray:
|
||||
"""Ψ(M): deterministic SHA-256 → 10 bivector coefficients → simple project.
|
||||
|
||||
Identical markdown ⇒ identical 32-vector (replay pin).
|
||||
"""
|
||||
if not isinstance(markdown, str):
|
||||
raise AdrDagValidationError("not_str", type=type(markdown).__name__)
|
||||
# Empty markdown is a valid document identity (still deterministic).
|
||||
digest = hashlib.sha256(markdown.encode("utf-8")).digest() # 32 bytes
|
||||
B = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
for k, plane in enumerate(_BIVECTOR_PLANES):
|
||||
# 3-byte segments; last 2 hash bytes unused (spec: 10×3=30).
|
||||
chunk = digest[k * 3 : k * 3 + 3]
|
||||
u = int.from_bytes(chunk, "big") # 0 .. 2^24-1
|
||||
c = (u / float(0xFFFFFF)) * 2.0 - 1.0 # [-1, 1]
|
||||
B[plane] = c
|
||||
return simple_bivector_project(B)
|
||||
|
||||
|
||||
def master_architecture_blade(
|
||||
embeddings: Sequence[np.ndarray],
|
||||
) -> np.ndarray:
|
||||
"""Assemble load-bearing ADR embeddings into a master architecture blade.
|
||||
|
||||
Prefer successive wedge when non-degenerate; if a wedge step vanishes
|
||||
(parallel simple planes after projection), fall back to algebraic sum of
|
||||
simple bivectors so the master never fabricates a zero blade.
|
||||
"""
|
||||
if not embeddings:
|
||||
raise AdrDagValidationError("empty_master_set")
|
||||
simples = [
|
||||
simple_bivector_project(np.asarray(e, dtype=np.float64))
|
||||
for e in embeddings
|
||||
]
|
||||
wedge = simples[0].copy()
|
||||
for i, e in enumerate(simples[1:], start=1):
|
||||
w = multivector_wedge(wedge, e)
|
||||
if float(np.linalg.norm(w)) > _NEAR_ZERO:
|
||||
wedge = w
|
||||
# else: parallel/collinear under wedge — keep prior wedge, continue
|
||||
if float(np.linalg.norm(wedge)) > _NEAR_ZERO:
|
||||
return wedge.astype(np.float64)
|
||||
# Full wedge chain degenerate: superposition master (still deterministic).
|
||||
acc = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
for e in simples:
|
||||
acc = acc + e
|
||||
if float(np.linalg.norm(acc)) < _NEAR_ZERO:
|
||||
raise AdrDagValidationError("degenerate_master_blade", at_index=0)
|
||||
return simple_bivector_project(acc)
|
||||
|
||||
|
||||
def proposal_drift(B_proposal: np.ndarray, A_master: np.ndarray) -> float:
|
||||
"""Drift = ‖B_p ∧ A_master‖ (Euclidean coeff norm of the wedge)."""
|
||||
Bp = simple_bivector_project(np.asarray(B_proposal, dtype=np.float64))
|
||||
Am = np.asarray(A_master, dtype=np.float64)
|
||||
if Am.shape != (N_COMPONENTS,):
|
||||
raise AdrDagValidationError("bad_master_shape", shape=tuple(Am.shape))
|
||||
w = multivector_wedge(Bp, Am)
|
||||
return float(np.linalg.norm(w))
|
||||
|
||||
|
||||
def validate_proposal_against_master(
|
||||
proposal_markdown: str,
|
||||
master_markdowns: Sequence[str],
|
||||
*,
|
||||
max_drift: float = 1.0,
|
||||
) -> tuple[bool, float, np.ndarray, np.ndarray]:
|
||||
"""Embed proposal + masters; return (ok, drift, B_p, A_master)."""
|
||||
if not master_markdowns:
|
||||
raise AdrDagValidationError("empty_master_set")
|
||||
masters = [embed_adr_markdown(m) for m in master_markdowns]
|
||||
A = master_architecture_blade(masters)
|
||||
Bp = embed_adr_markdown(proposal_markdown)
|
||||
d = proposal_drift(Bp, A)
|
||||
ok = bool(d <= float(max_drift) + _NEAR_ZERO)
|
||||
return ok, d, Bp, A
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AdrDagValidationError",
|
||||
"embed_adr_markdown",
|
||||
"master_architecture_blade",
|
||||
"multivector_wedge",
|
||||
"proposal_drift",
|
||||
"simple_bivector_project",
|
||||
"validate_proposal_against_master",
|
||||
]
|
||||
|
|
@ -69,6 +69,14 @@ from core.physics.temporal_gate import (
|
|||
)
|
||||
from core.physics.self_authorship import AuthorshipProposal, SelfAuthorshipMiner
|
||||
from core.physics.wave_manifold import WaveManifold
|
||||
from core.physics.trajectory_invariants import (
|
||||
TrajectoryAssessment,
|
||||
TrajectoryInvariantError,
|
||||
assess_trajectory,
|
||||
energy_boundary_ok,
|
||||
relative_holonomy,
|
||||
trajectory_divergence,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"SalienceOperator", "SalienceMap", "FieldRegion",
|
||||
|
|
@ -99,4 +107,7 @@ __all__ = [
|
|||
"TemporalDecision", "TemporalVerdict",
|
||||
"AuthorshipProposal", "SelfAuthorshipMiner",
|
||||
"WaveManifold",
|
||||
"TrajectoryAssessment", "TrajectoryInvariantError",
|
||||
"assess_trajectory", "energy_boundary_ok",
|
||||
"relative_holonomy", "trajectory_divergence",
|
||||
]
|
||||
|
|
|
|||
172
core/physics/trajectory_invariants.py
Normal file
172
core/physics/trajectory_invariants.py
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
"""
|
||||
core/physics/trajectory_invariants.py
|
||||
|
||||
Continuous-space trajectory invariants + zero-fabrication (R&D-Revised §2.2 / #21).
|
||||
|
||||
Python geometry-first surface (algebra/*). A Ring-1 Rust port may later mirror
|
||||
this contract under ``core-rs``; this module is the behavioral source of truth.
|
||||
|
||||
Invariants:
|
||||
* Relative holonomy H(t) = V_i · reverse(V_{i+1})
|
||||
* Divergence D = Σ log(1 + ‖H·reverse(H) − 1‖_F) · Δt (discrete integral)
|
||||
* Replay bound D < ε_trajectory
|
||||
* Hamiltonian energy boundary E_exertion ≤ κ · E_sensory
|
||||
* Zero-fabrication: refuse empty / non-closed trajectory steps
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
from algebra.cl41 import N_COMPONENTS, geometric_product, reverse
|
||||
from algebra.versor import versor_condition, versor_unit_residual
|
||||
|
||||
_CLOSURE_TOL = 1e-6
|
||||
_DEFAULT_EPS_TRAJECTORY = 1e-5
|
||||
_NEAR_ZERO = 1e-15
|
||||
|
||||
|
||||
class TrajectoryInvariantError(ValueError):
|
||||
"""Fail-closed refusal from trajectory invariant checks."""
|
||||
|
||||
def __init__(self, reason: str, **disclosure) -> None:
|
||||
self.reason = reason
|
||||
self.disclosure = dict(disclosure)
|
||||
super().__init__(f"trajectory_invariant refused [{reason}]: {self.disclosure}")
|
||||
|
||||
|
||||
def _as_versor(V: np.ndarray, name: str) -> np.ndarray:
|
||||
arr = np.asarray(V, dtype=np.float64)
|
||||
if arr.shape != (N_COMPONENTS,):
|
||||
raise TrajectoryInvariantError(
|
||||
"bad_shape", name=name, shape=tuple(arr.shape)
|
||||
)
|
||||
cond = float(versor_condition(arr))
|
||||
if cond >= _CLOSURE_TOL:
|
||||
raise TrajectoryInvariantError(
|
||||
"not_closed", name=name, versor_condition=cond
|
||||
)
|
||||
return arr
|
||||
|
||||
|
||||
def relative_holonomy(V1: np.ndarray, V2: np.ndarray) -> np.ndarray:
|
||||
"""H = V1 · reverse(V2) — relative transport between consecutive steps."""
|
||||
a = _as_versor(V1, "V1")
|
||||
b = _as_versor(V2, "V2")
|
||||
return geometric_product(a, reverse(b)).astype(np.float64)
|
||||
|
||||
|
||||
def holonomy_unit_residual(H: np.ndarray) -> float:
|
||||
"""‖H · reverse(H) − 1‖_F (dual-checked via versor_unit_residual)."""
|
||||
H_arr = np.asarray(H, dtype=np.float64)
|
||||
if H_arr.shape != (N_COMPONENTS,):
|
||||
raise TrajectoryInvariantError("bad_shape", name="H", shape=tuple(H_arr.shape))
|
||||
r = float(versor_unit_residual(H_arr))
|
||||
r_rev = float(versor_unit_residual(reverse(H_arr)))
|
||||
return max(r, r_rev)
|
||||
|
||||
|
||||
def trajectory_divergence(
|
||||
versors: Sequence[np.ndarray],
|
||||
*,
|
||||
dt: float = 1.0,
|
||||
) -> float:
|
||||
"""Discrete divergence integral D = Σ log(1 + residual(H_i)) · Δt.
|
||||
|
||||
Zero-fabrication: empty or single-step trajectories refused (no confabulated
|
||||
path). Each step must be a closed unit versor.
|
||||
"""
|
||||
if not versors:
|
||||
raise TrajectoryInvariantError("empty_trajectory")
|
||||
if len(versors) < 2:
|
||||
raise TrajectoryInvariantError(
|
||||
"trajectory_too_short", n=len(versors)
|
||||
)
|
||||
if float(dt) <= 0.0:
|
||||
raise TrajectoryInvariantError("non_positive_dt", dt=float(dt))
|
||||
|
||||
closed = [_as_versor(v, f"versors[{i}]") for i, v in enumerate(versors)]
|
||||
D = 0.0
|
||||
for i in range(len(closed) - 1):
|
||||
H = relative_holonomy(closed[i], closed[i + 1])
|
||||
r = holonomy_unit_residual(H)
|
||||
D += math.log1p(max(r, 0.0)) * float(dt)
|
||||
return float(D)
|
||||
|
||||
|
||||
def energy_boundary_ok(
|
||||
E_exertion: float,
|
||||
E_sensory: float,
|
||||
*,
|
||||
kappa: float = 1.0,
|
||||
) -> bool:
|
||||
"""Hamiltonian energy boundary: E_exertion ≤ κ · E_sensory.
|
||||
|
||||
Refuse negative energies (zero-fabrication of free action).
|
||||
"""
|
||||
ee = float(E_exertion)
|
||||
es = float(E_sensory)
|
||||
k = float(kappa)
|
||||
if ee < -_NEAR_ZERO or es < -_NEAR_ZERO:
|
||||
raise TrajectoryInvariantError(
|
||||
"negative_energy", E_exertion=ee, E_sensory=es
|
||||
)
|
||||
if k < 0.0:
|
||||
raise TrajectoryInvariantError("negative_kappa", kappa=k)
|
||||
return ee <= k * es + _NEAR_ZERO
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TrajectoryAssessment:
|
||||
"""Result of assessing a finite trajectory against #21 invariants."""
|
||||
|
||||
divergence: float
|
||||
within_replay_bound: bool
|
||||
energy_ok: bool
|
||||
n_steps: int
|
||||
eps_trajectory: float
|
||||
kappa: float
|
||||
E_exertion: float
|
||||
E_sensory: float
|
||||
|
||||
|
||||
def assess_trajectory(
|
||||
versors: Sequence[np.ndarray],
|
||||
*,
|
||||
E_exertion: float,
|
||||
E_sensory: float,
|
||||
eps_trajectory: float = _DEFAULT_EPS_TRAJECTORY,
|
||||
kappa: float = 1.0,
|
||||
dt: float = 1.0,
|
||||
) -> TrajectoryAssessment:
|
||||
"""Full trajectory gate: divergence bound + energy boundary."""
|
||||
D = trajectory_divergence(versors, dt=dt)
|
||||
eps = float(eps_trajectory)
|
||||
if eps <= 0.0:
|
||||
raise TrajectoryInvariantError("non_positive_eps", eps_trajectory=eps)
|
||||
e_ok = energy_boundary_ok(E_exertion, E_sensory, kappa=kappa)
|
||||
return TrajectoryAssessment(
|
||||
divergence=float(D),
|
||||
within_replay_bound=bool(D < eps),
|
||||
energy_ok=bool(e_ok),
|
||||
n_steps=len(versors),
|
||||
eps_trajectory=eps,
|
||||
kappa=float(kappa),
|
||||
E_exertion=float(E_exertion),
|
||||
E_sensory=float(E_sensory),
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TrajectoryAssessment",
|
||||
"TrajectoryInvariantError",
|
||||
"assess_trajectory",
|
||||
"energy_boundary_ok",
|
||||
"holonomy_unit_residual",
|
||||
"relative_holonomy",
|
||||
"trajectory_divergence",
|
||||
]
|
||||
|
|
@ -35,8 +35,8 @@
|
|||
| 4 | GoldTether residual + α law | Super §2.3, R&D §2.3/§5 | 🟢 residual+α + bootstrap gates + principal-axis prune (#24+#18) | #18 |
|
||||
| 5 | Grade-5 pseudoscalar invariant | Super §3.3 | ⚪ RETIRED — vacuous in odd-dim Cl(4,1) | #19 (closed) |
|
||||
| 6 | Surprise residual operator | Super §3.2 | 🟢 math + DiscoveryCandidate wiring landed (#26 + #31) | #20 |
|
||||
| 7 | Trajectory invariants + zero-fabrication | R&D §2.2 | ⚫ absent | #21 |
|
||||
| 8 | ADR-DAG conformal embedding | R&D §2.4 | ⚫ absent | #21 |
|
||||
| 7 | Trajectory invariants + zero-fabrication | R&D §2.2 | 🟢 Python geometry surface (`trajectory_invariants.py`) | #21 |
|
||||
| 8 | ADR-DAG conformal embedding | R&D §2.4 | 🟢 Python surface (`core/adr/validator.py`) | #21 |
|
||||
| W1 | WaveManifold unitary / sandwich step | ADR-0241 §2 | 🟢 | ADR-0241 |
|
||||
| W2 | Spectral leakage surprise | ADR-0241 §2.4B | 🟢 subsumed into `surprise_residual` | ADR-0241 |
|
||||
| W3 | Wave polar + multi-pair conjugacy | ADR-0241 §2.4A | 🟢 single polar + multi-pair thin wrap | ADR-0241 |
|
||||
|
|
@ -206,10 +206,23 @@ The null add-on is **untested**: `test_signature_aware_pca_keeps_nulls` classifi
|
|||
|
||||
---
|
||||
|
||||
## 9. Absent whole proposals — ⚫ (#21)
|
||||
## 9. Trajectory invariants + ADR-DAG — 🟢 Python surfaces (#21)
|
||||
|
||||
- **Trajectory invariants + zero-fabrication (R&D §2.2 → `core-rs/src/sensorimotor.rs`):** relative holonomy `H(t)=V₁Ṽ₂`, divergence integral `D < ε_trajectory`, Hamiltonian energy boundary `E_exertion ≤ κ·E_sensory`. Not landed. Gated by the Zig/Rust substrate doctrine (Ring-1 only).
|
||||
- **ADR-DAG conformal embedding (R&D §2.4 → `core/adr/validator.py`):** `Ψ(M)` = SHA-256 → 10 bivector coeffs → simple-bivector projection → master-blade wedge → drift check. Not landed. Cross-check `core/abi/geometric_delta_validator.py` before adding a parallel validator.
|
||||
> **Landed (2026-07-14):** geometry-first Python authorities. Rust Ring-1 port remains optional acceleration, not a second truth.
|
||||
|
||||
### Trajectory invariants (R&D §2.2 → `core/physics/trajectory_invariants.py`)
|
||||
- Relative holonomy `H = V_i · reverse(V_{i+1})`
|
||||
- Divergence `D = Σ log(1 + ‖H reverse(H) − 1‖) · Δt`; bound `D < ε_trajectory`
|
||||
- Energy boundary `E_exertion ≤ κ · E_sensory`
|
||||
- Zero-fabrication: empty / short / non-closed steps refused
|
||||
- Tests: `tests/test_third_door_trajectory_invariants.py`
|
||||
|
||||
### ADR-DAG conformal embedding (R&D §2.4 → `core/adr/validator.py`)
|
||||
- `Ψ(M)`: SHA-256 → 10×3-byte → `c_k ∈ [−1,1]` → planes 6..15 → simple-bivector project
|
||||
- Master blade = successive wedge of load-bearing ADR embeddings
|
||||
- Proposal drift = `‖B_p ∧ A_master‖`
|
||||
- Does **not** parallel `core/abi/geometric_delta_validator.py` (that validates GeometricDelta ABI envelopes)
|
||||
- Tests: `tests/test_third_door_adr_dag_embedding.py`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -297,7 +310,7 @@ PY
|
|||
- Durable holographic memory **vault store** (CRDT-backed standing-wave spectrum) — session registry only today.
|
||||
- Rust/MLX acceleration of exp-map / cross-spectral (ADR-0235 later).
|
||||
- Full ADR-0092 reviewer-service integration (promote remains caller-gated).
|
||||
- R&D #21 trajectory invariants + ADR-DAG embedding.
|
||||
- Optional Rust Ring-1 port of trajectory invariants (Python is authority today).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -310,7 +323,7 @@ PY
|
|||
| GoldTether gold-set + harmonized residual + α=Φ(R) + bootstrap/prune — 🟢 | #18 |
|
||||
| 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 |
|
||||
| Trajectory invariants + ADR-DAG embedding — 🟢 Python surfaces | #21 |
|
||||
| Wave-field substrate + operator subsumption (W1–W6) — 🟢 on branch | ADR-0241 |
|
||||
| `core_ha` deprecation — 🟢 no live tree + hygiene pin | ADR-0241 / deprecation plan |
|
||||
| Durable holographic vault spectrum — deferred | ADR-0241 follow-on |
|
||||
|
|
|
|||
98
tests/test_third_door_adr_dag_embedding.py
Normal file
98
tests/test_third_door_adr_dag_embedding.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
"""#21 B — ADR-DAG conformal embedding Ψ(M) (R&D §2.4)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from algebra.cl41 import grade_project
|
||||
from core.adr.validator import (
|
||||
AdrDagValidationError,
|
||||
embed_adr_markdown,
|
||||
master_architecture_blade,
|
||||
proposal_drift,
|
||||
simple_bivector_project,
|
||||
validate_proposal_against_master,
|
||||
)
|
||||
|
||||
|
||||
def test_embed_deterministic_replay():
|
||||
m = "# ADR-TEST\n\nBody of the decision.\n"
|
||||
a = embed_adr_markdown(m)
|
||||
b = embed_adr_markdown(m)
|
||||
assert np.array_equal(a, b)
|
||||
assert a.shape == (32,)
|
||||
|
||||
|
||||
def test_embed_differs_for_different_markdown():
|
||||
a = embed_adr_markdown("# ADR-A\nfoo")
|
||||
b = embed_adr_markdown("# ADR-B\nbar")
|
||||
assert not np.allclose(a, b)
|
||||
|
||||
|
||||
def test_embed_is_grade2_supported():
|
||||
B = embed_adr_markdown("# ADR-G2\ncontent")
|
||||
# After simple project: only grade-2 (and zeros elsewhere)
|
||||
for g in (0, 1, 3, 4, 5):
|
||||
assert float(np.linalg.norm(grade_project(B, g))) < 1e-12
|
||||
assert float(np.linalg.norm(grade_project(B, 2))) > 0.0
|
||||
|
||||
|
||||
def test_simple_bivector_project_collapses_multiplane():
|
||||
B = np.zeros(32, dtype=np.float64)
|
||||
B[6] = 0.9
|
||||
B[7] = 0.8
|
||||
B[8] = 0.1
|
||||
S = simple_bivector_project(B)
|
||||
# Dominant plane kept
|
||||
assert abs(S[6] - 0.9) < 1e-12 or abs(S[7] - 0.8) < 1e-12
|
||||
# At most one plane nonzero after collapse when multiplane non-simple
|
||||
n_planes = sum(1 for i in range(6, 16) if abs(S[i]) > 1e-12)
|
||||
assert n_planes == 1
|
||||
|
||||
|
||||
def test_master_blade_refuses_empty():
|
||||
with pytest.raises(AdrDagValidationError, match="empty"):
|
||||
master_architecture_blade([])
|
||||
|
||||
|
||||
def test_master_blade_from_two_adrs():
|
||||
e1 = embed_adr_markdown("# ADR-0003\nCoordinate dissolution.")
|
||||
e2 = embed_adr_markdown("# ADR-0006\nField energy.")
|
||||
A = master_architecture_blade([e1, e2])
|
||||
assert A.shape == (32,)
|
||||
assert float(np.linalg.norm(A)) > 0.0
|
||||
|
||||
|
||||
def test_proposal_drift_nonneg_and_deterministic():
|
||||
masters = [
|
||||
embed_adr_markdown("# M1\none"),
|
||||
embed_adr_markdown("# M2\ntwo"),
|
||||
]
|
||||
A = master_architecture_blade(masters)
|
||||
Bp = embed_adr_markdown("# Proposal\nnew idea")
|
||||
d1 = proposal_drift(Bp, A)
|
||||
d2 = proposal_drift(Bp, A)
|
||||
assert d1 == d2
|
||||
assert d1 >= 0.0
|
||||
|
||||
|
||||
def test_validate_proposal_returns_ok_flag():
|
||||
masters_md = ["# ADR-A\nalpha", "# ADR-B\nbeta"]
|
||||
ok, drift, Bp, A = validate_proposal_against_master(
|
||||
"# Proposal\ngamma", masters_md, max_drift=1e9
|
||||
)
|
||||
assert ok is True
|
||||
assert drift >= 0.0
|
||||
assert Bp.shape == (32,)
|
||||
assert A.shape == (32,)
|
||||
|
||||
|
||||
def test_validate_proposal_tight_max_drift_can_fail():
|
||||
masters_md = ["# ADR-A\nalpha", "# ADR-B\nbeta"]
|
||||
ok, drift, _Bp, _A = validate_proposal_against_master(
|
||||
"# Proposal\nentirely different body xyz", masters_md, max_drift=-1.0
|
||||
)
|
||||
# max_drift < 0 forces fail unless drift is negative (impossible)
|
||||
assert ok is False
|
||||
assert drift >= 0.0
|
||||
92
tests/test_third_door_trajectory_invariants.py
Normal file
92
tests/test_third_door_trajectory_invariants.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""#21 A — trajectory invariants + zero-fabrication (R&D §2.2)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from algebra.rotor import make_rotor_from_angle
|
||||
from algebra.versor import versor_condition
|
||||
from core.physics.trajectory_invariants import (
|
||||
TrajectoryInvariantError,
|
||||
assess_trajectory,
|
||||
energy_boundary_ok,
|
||||
relative_holonomy,
|
||||
trajectory_divergence,
|
||||
)
|
||||
|
||||
|
||||
def _id() -> np.ndarray:
|
||||
v = np.zeros(32, dtype=np.float64)
|
||||
v[0] = 1.0
|
||||
return v
|
||||
|
||||
|
||||
def test_relative_holonomy_identity_pair_is_identity():
|
||||
H = relative_holonomy(_id(), _id())
|
||||
assert versor_condition(H) < 1e-6
|
||||
assert abs(H[0] - 1.0) < 1e-9
|
||||
|
||||
|
||||
def test_relative_holonomy_order_sensitive():
|
||||
A = make_rotor_from_angle(0.3, bivector_idx=6)
|
||||
B = make_rotor_from_angle(0.9, bivector_idx=7)
|
||||
H_ab = relative_holonomy(A, B)
|
||||
H_ba = relative_holonomy(B, A)
|
||||
assert not np.allclose(H_ab, H_ba, atol=1e-9)
|
||||
|
||||
|
||||
def test_trajectory_divergence_zero_on_constant_path():
|
||||
path = [_id(), _id(), _id()]
|
||||
assert trajectory_divergence(path) == 0.0
|
||||
|
||||
|
||||
def test_trajectory_divergence_positive_on_moving_path():
|
||||
path = [
|
||||
make_rotor_from_angle(0.1 * i, bivector_idx=6) for i in range(1, 6)
|
||||
]
|
||||
D = trajectory_divergence(path)
|
||||
assert D > 0.0
|
||||
|
||||
|
||||
def test_trajectory_divergence_deterministic():
|
||||
path = [make_rotor_from_angle(0.2 * i) for i in range(1, 4)]
|
||||
assert trajectory_divergence(path) == trajectory_divergence(path)
|
||||
|
||||
|
||||
def test_trajectory_divergence_refuses_empty_and_short():
|
||||
with pytest.raises(TrajectoryInvariantError, match="empty"):
|
||||
trajectory_divergence([])
|
||||
with pytest.raises(TrajectoryInvariantError, match="too_short"):
|
||||
trajectory_divergence([_id()])
|
||||
|
||||
|
||||
def test_trajectory_divergence_refuses_non_closed():
|
||||
dirty = np.zeros(32, dtype=np.float64)
|
||||
dirty[0] = 0.5
|
||||
dirty[1] = 0.5
|
||||
with pytest.raises(TrajectoryInvariantError, match="not_closed"):
|
||||
trajectory_divergence([_id(), dirty])
|
||||
|
||||
|
||||
def test_energy_boundary_ok_and_refuse_negative():
|
||||
assert energy_boundary_ok(1.0, 2.0, kappa=1.0) is True
|
||||
assert energy_boundary_ok(3.0, 2.0, kappa=1.0) is False
|
||||
assert energy_boundary_ok(3.0, 2.0, kappa=2.0) is True
|
||||
with pytest.raises(TrajectoryInvariantError, match="negative_energy"):
|
||||
energy_boundary_ok(-0.1, 1.0)
|
||||
|
||||
|
||||
def test_assess_trajectory_replay_bound():
|
||||
path = [make_rotor_from_angle(0.05 * i) for i in range(1, 4)]
|
||||
a = assess_trajectory(
|
||||
path, E_exertion=0.5, E_sensory=1.0, eps_trajectory=1.0, kappa=1.0
|
||||
)
|
||||
assert a.energy_ok is True
|
||||
assert a.n_steps == 3
|
||||
assert a.divergence >= 0.0
|
||||
# Tight eps → may fail replay bound on moving path
|
||||
tight = assess_trajectory(
|
||||
path, E_exertion=0.5, E_sensory=1.0, eps_trajectory=1e-30, kappa=1.0
|
||||
)
|
||||
assert tight.within_replay_bound is False
|
||||
Loading…
Reference in a new issue