diff --git a/core/physics/identity_action.py b/core/physics/identity_action.py new file mode 100644 index 00000000..8956bff1 --- /dev/null +++ b/core/physics/identity_action.py @@ -0,0 +1,131 @@ +"""core.physics.identity_action — lawful identity action policy (ADR-0246 §3.2–§3.3). + +Where :mod:`core.physics.identity_manifold` measures *what a versor does* to the +value frame (the induced action ``A(F)``, its orthogonality defect ``d_orth``, and +typed leakage), this module measures *whether that action is lawful* — how far the +induced action sits from the explicitly permitted identity actions ``H_id``. + +The two diagnostics are deliberately never collapsed (ADR-0246 §3.2): + + * ``d_orth`` (in identity_manifold) — detects non-isometric / numerically + corrupt action on the subspace. A conditioning check, NOT an authorization. + * ``d_stab`` (here) — ``min_{H ∈ H_id} ‖A(F) − H‖_G`` — detects departure from + the explicitly *permitted* identity action. This is the lawfulness measure. + +**Locked stabilizer (ADR-0246 §3.3).** For the default identity pack + + H_id = { I } + +the singleton containing only the identity matrix in the axis basis. Algebraic +cleanliness ≠ identity lawfulness, so ``-I`` (global inversion), axis +permutations, continuous rotations, and arbitrary reweightings are **excluded**. +Under a singleton stabilizer there is no continuous projection that "invents" a +lawful action: ``d_stab`` is a pass/fail distance, and callers must NOT soft-project +``A`` onto ``I`` and then compose the projection as if the turn were lawful. +Enlarging ``H_id`` is a future, explicit, reviewed pack/policy change — never an +implicit convenience here. + +Pure (numpy + identity_manifold only), deterministic, float64, off-serving. The +lawful-only path composition and hard-break ledger (§3.4/§3.5) are a separate, +later unit; this module provides only the per-turn stabilizer defect. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from core.physics.identity_manifold import IdentityManifoldGeometry + +# Below this the axis Gram is treated as the identity matrix and the G-weighted +# norm collapses to the plain Frobenius norm (exact for the default pack). +_IDENTITY_GRAM_TOL: float = 1e-9 + + +@dataclass(frozen=True) +class IdentityStabilizer: + """The permitted identity actions ``H_id`` in the axis basis. + + ``members`` are the allowed action matrices. The default (and only ratified) + policy is the singleton ``{ I }`` — see module docstring / ADR-0246 §3.3. + Constructing a non-singleton stabilizer is possible for research/analysis but + is NOT a ratified live policy; enlarging ``H_id`` for serving requires an + explicit reviewed change. + """ + + members: tuple[np.ndarray, ...] + + @classmethod + def singleton(cls, dimension: int) -> "IdentityStabilizer": + """The locked default ``H_id = { I_dimension }``.""" + return cls(members=(np.eye(int(dimension), dtype=np.float64),)) + + @property + def is_singleton_identity(self) -> bool: + """True iff this is exactly the locked default ``{ I }``.""" + return len(self.members) == 1 and bool( + np.allclose(self.members[0], np.eye(self.members[0].shape[0])) + ) + + +def _matrix_sqrt_spd(gram: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """``(G^{1/2}, G^{-1/2})`` for a symmetric positive-definite Gram via eigh.""" + eigvals, eigvecs = np.linalg.eigh(gram) + if float(eigvals.min()) <= 0.0: + raise ValueError("stabilizer defect requires a positive-definite Gram") + root = eigvecs @ np.diag(np.sqrt(eigvals)) @ eigvecs.T + inv_root = eigvecs @ np.diag(1.0 / np.sqrt(eigvals)) @ eigvecs.T + return root, inv_root + + +def _g_weighted_frobenius(matrix: np.ndarray, gram: np.ndarray) -> float: + """Metric-consistent matrix norm ``‖M‖_G`` (ADR-0246 §3.2). + + Measured in a ``G``-orthonormal frame: ``‖M‖_G = ‖G^{1/2} M G^{-1/2}‖_F``. + Reduces exactly to the plain Frobenius norm when ``G = I`` (the default pack), + and is invariant to the metric-preserving change of axis coordinates. The + convention is fixed here for the value packs in use; a broader-pack review may + revisit it (ADR-0246 §3.2 leaves general-pack ``‖·‖_G`` to ADR-0246 proper). + """ + if np.allclose(gram, np.eye(gram.shape[0]), atol=_IDENTITY_GRAM_TOL): + return float(np.linalg.norm(matrix, ord="fro")) + root, inv_root = _matrix_sqrt_spd(gram) + return float(np.linalg.norm(root @ matrix @ inv_root, ord="fro")) + + +def stabilizer_defect( + action: np.ndarray, + gram: np.ndarray, + stabilizer: IdentityStabilizer, +) -> float: + """``d_stab = min_{H ∈ H_id} ‖A − H‖_G`` (ADR-0246 §3.2–§3.3). + + The lawfulness distance of an induced action ``A`` from the permitted identity + actions. Zero iff ``A`` equals a permitted action. Under the locked singleton + ``H_id = { I }`` this is exactly ``‖A − I‖_G`` — a pass/fail distance, not a + corrector. + """ + action = np.asarray(action, dtype=np.float64) + gram = np.asarray(gram, dtype=np.float64) + if not stabilizer.members: + raise ValueError("stabilizer must contain at least one permitted action") + return min( + _g_weighted_frobenius(action - np.asarray(H, dtype=np.float64), gram) + for H in stabilizer.members + ) + + +def stabilizer_defect_for_versor( + geometry: IdentityManifoldGeometry, + versor: np.ndarray, + stabilizer: IdentityStabilizer | None = None, +) -> float: + """``d_stab`` for a versor's induced action against ``geometry``. + + Defaults to the locked singleton ``H_id = { I }`` sized to the value frame. + """ + action = geometry.induced_action(versor) + if stabilizer is None: + stabilizer = IdentityStabilizer.singleton(action.shape[0]) + return stabilizer_defect(action, geometry.gram, stabilizer) diff --git a/core/physics/identity_manifold.py b/core/physics/identity_manifold.py index ea6bf444..bed9c133 100644 --- a/core/physics/identity_manifold.py +++ b/core/physics/identity_manifold.py @@ -56,6 +56,15 @@ from algebra.cl41 import ( # to resolve without mode-aliasing (ADR-0244 §2.1). CONDITION_BOUND: float = 1e5 +# Grade-1 basis-vector slots in the 32-component Cl(4,1) layout. The spatial block +# is e1/e2/e3 (indices 1/2/3) — the default value-axis support; e4/e5 (indices 4/5) +# are the extra grade-1 directions a versor tilts (e4, null/conformal) or boosts +# (e5) a value axis toward. Pinned against ``algebra.cl41.basis_vector`` in tests +# (ADR-0246 §3.6 fixed blade map). +SPATIAL_GRADE1_INDICES: tuple[int, int, int] = (1, 2, 3) +E4_GRADE1_INDEX: int = 4 +E5_GRADE1_INDEX: int = 5 + class ManifoldConditioningError(ValueError): """Raised when the value-axis Gram matrix is too ill-conditioned. @@ -151,6 +160,18 @@ def euclidean_norm(s: np.ndarray) -> float: return float(np.linalg.norm(np.asarray(s, dtype=np.float64), ord=2)) +def orthogonality_defect_of_action(action: np.ndarray, gram: np.ndarray) -> float: + """``‖AᵀGA − G‖_F`` for a precomputed induced action ``A`` (ADR-0246 §3.2). + + Zero iff ``A`` is a ``G``-isometry of the value subspace. The single home for + the d_orth definition, shared by :meth:`IdentityManifoldGeometry.orthogonality_defect` + and the off-serving diagnostics. + """ + action = np.asarray(action, dtype=np.float64) + gram = np.asarray(gram, dtype=np.float64) + return float(np.linalg.norm(action.T @ gram @ action - gram, ord="fro")) + + @dataclass(frozen=True) class IdentityManifoldGeometry: """Frozen operator-preservation geometry for a set of value axes. @@ -232,3 +253,94 @@ class IdentityManifoldGeometry: """ leakage, _ = self.axis_response(versor) return float((sum(value * value for value in leakage) / len(leakage)) ** 0.5) + + # -- ADR-0246 §3 induced-action primitives (pure, off-serving) -------------- + + def induced_action(self, versor: np.ndarray) -> np.ndarray: + """The induced action matrix ``A(F)`` of a versor on the value frame. + + ``A_kj = (G⁻¹)_{km} ⟨a_m, F a_j F̃⟩₀`` (ADR-0246 §3.1). Column ``j`` is the + image of axis ``j`` (``F a_j F̃``) re-expressed in the axis basis. This + captures ALL in-subspace action — including the permutations and rotations + that per-axis leakage is blind to (a rotor rotating e1→e2 has zero leakage + but a non-identity ``A``). It is RAW (unnormalized): a boost that stretches + an axis shows up as a column norm > 1 and hence in :meth:`orthogonality_defect`, + deliberately not hidden by normalization. + + When ``F`` preserves the subspace isometrically, ``A`` is ``G``-orthogonal + (``AᵀGA = G``). Built only from existing primitives (Gram, signed inner + product, sandwich); no new algebra. + """ + versor = np.asarray(versor, dtype=np.float64) + n = len(self.axes_psi) + overlaps = np.empty((n, n), dtype=np.float64) + for j, axis_j in enumerate(self.axes_psi): + image = sandwich(versor, axis_j) + for k, axis_k in enumerate(self.axes_psi): + overlaps[k, j] = _inner0(axis_k, image) + return self.gram_inv @ overlaps + + def orthogonality_defect(self, versor: np.ndarray) -> float: + """``d_orth(F) = ‖A(F)ᵀ G A(F) − G‖_F`` (ADR-0246 §3.2). + + Zero iff the induced action is a ``G``-isometry of the value subspace. + Non-zero flags numerical failure or genuinely non-isometric action (a + boost stretches the frame). It detects a DIFFERENT failure than + :func:`~core.physics.identity_action.stabilizer_defect` and must never be + read as a semantic authorization policy — it is a conditioning / isometry + check only. + """ + return orthogonality_defect_of_action(self.induced_action(versor), self.gram) + + def typed_residual_energy(self, versor: np.ndarray) -> dict[str, float]: + """Typed decomposition of the out-of-subspace leakage (ADR-0246 §3.6). + + For each axis the rejection ``r = F a F̃ − P_I(F a F̃)`` is split by which + grade-1 direction it leaks into, summed over axes, and returned as + fractions of the total rotated-axis energy: + + * ``null_or_conformal`` — energy on e4 (index 4): conformal/null tilt. + * ``boost_like`` — energy on e5 (index 5): noncompact/boost class. + * ``spatial_foreign`` — energy on spatial grade-1 slots (e1/e2/e3) not + in the value-axis span; structurally 0 for the default full-span pack. + * ``unclassified`` — the remainder (higher-grade contamination after + the sandwich, numerical junk). Fail-closed: no correction policy ever + attaches to this channel. For a clean versor (grade-preserving sandwich) + it is ~0. + + Retains the positive-definite Euclidean coefficient norm (never the + indefinite ``⟨·,·⟩₀``) so a boost/e5 component cannot silently vanish. + """ + versor = np.asarray(versor, dtype=np.float64) + e4_energy = e5_energy = spatial_foreign = unclassified = total = 0.0 + for axis in self.axes_psi: + rotated = sandwich(versor, axis) + rejection = rotated - self.project(rotated) + total += euclidean_norm(rotated) ** 2 + e4_energy += float(rejection[E4_GRADE1_INDEX] ** 2) + e5_energy += float(rejection[E5_GRADE1_INDEX] ** 2) + spatial_foreign += float( + sum(rejection[i] ** 2 for i in SPATIAL_GRADE1_INDICES) + ) + rejection_energy = euclidean_norm(rejection) ** 2 + unclassified += max( + 0.0, + rejection_energy + - float(rejection[E4_GRADE1_INDEX] ** 2) + - float(rejection[E5_GRADE1_INDEX] ** 2) + - float(sum(rejection[i] ** 2 for i in SPATIAL_GRADE1_INDICES)), + ) + if total <= 0.0: + # Versor annihilated every axis — fail-closed as fully unaccounted. + return { + "null_or_conformal": 0.0, + "boost_like": 0.0, + "spatial_foreign": 0.0, + "unclassified": 1.0, + } + return { + "null_or_conformal": e4_energy / total, + "boost_like": e5_energy / total, + "spatial_foreign": spatial_foreign / total, + "unclassified": unclassified / total, + } diff --git a/docs/briefs/ADR-0246-induced-identity-action-and-path-integrity-preflight.md b/docs/briefs/ADR-0246-induced-identity-action-and-path-integrity-preflight.md index 77bdc14a..26f26c67 100644 --- a/docs/briefs/ADR-0246-induced-identity-action-and-path-integrity-preflight.md +++ b/docs/briefs/ADR-0246-induced-identity-action-and-path-integrity-preflight.md @@ -39,6 +39,33 @@ Do **not** insert Rings 1–3 into active D4 commits. --- +## 0a. Post-D4 execution sequencing (actual — 2026-07-17) + +D4 closed and was ratified; this brief is now the live plan. The R&D assessment's +one-shot "build the whole Ring-1 gate machinery" is being executed as small, +verified units, and the brief's own **§11 semantic-grounding question was promoted +to run as a consumer of the §3 apparatus** (not a re-sequenced blocker), because +D4 Phase 3 + slice 0 proved no fixed spatial frame is dynamically stabilized — +building a lawfulness gate on the declared frame before knowing whether *any* +structure is stabilized would instrument lawfulness on a frame the dynamics ignore +(the "instruments ≠ meaning" trap, §2 / §11). + +| Unit | Scope | Status | +|------|-------|--------| +| **Slice 0** — mismatch diagnostic | evidence-only classification of the benign mismatch (foreign leakage vs in-span-unlawful vs numerical vs path vs semantic-coupling-absent) | **merged** `main` (quarantined diagnostic artifact); found: structural e4/e5 foreign leakage, declared frame dynamically unspecial | +| **§3 primitives** (this unit) | pure `A(F)`, `d_orth`, `d_stab` vs locked `H_id={I}`, typed residual channels in `core/physics/identity_manifold.py` + `identity_action.py`; slice-0 eval rewired to consume them (single source of truth) | in progress — RED→GREEN, off-serving, flag untouched | +| §3.4/§3.5 path ledger | lawful-only composition + hard breaks | next unit | +| §3.7 gate admit surface | wire d_orth/d_stab/typed channels into `IdentityScore` (flag default-off) | after ledger | +| §6 eval matrix + §11 feasibility | synthetic + path suites + discrimination report; the invariant/semantic-grounding feasibility study runs here as the **first consumer** of the §3 primitives (fixed cohort splits, synthetic recovery controls, typed e4/e5 generator analysis, precision pairs, adversarial discrimination) | after gate surface | +| ADR-0246 body + acceptance packet | §10 criteria; no self-Accept | last | + +Nothing in the reordering relaxes a §7 non-goal: no `C_id` corrector, no `H_id` +enlargement, no pack/axis redesign, no gate activation. The §11 grounding study +remains *feasibility only* until it produces a held-out-stable, safety-relevant +candidate (per the D4 ratification's activation prerequisites). + +--- + ## 1. Authority documents | Doc | Role | diff --git a/evals/adr_0246_mismatch_diagnostic/__init__.py b/evals/adr_0246_mismatch_diagnostic/__init__.py index f4c00b79..2072b16e 100644 --- a/evals/adr_0246_mismatch_diagnostic/__init__.py +++ b/evals/adr_0246_mismatch_diagnostic/__init__.py @@ -50,9 +50,13 @@ from algebra.cl41 import N_COMPONENTS, grade_project from core.physics.identity_manifold import ( IdentityManifoldGeometry, euclidean_norm, - sandwich, + orthogonality_defect_of_action, _inner0, ) +from core.physics.identity_action import ( + IdentityStabilizer, + stabilizer_defect, +) from evals.adr_0244_gamma_calibration import ( LEAKAGE_ATTACKS, _boost, @@ -90,93 +94,34 @@ def default_geometry() -> IdentityManifoldGeometry: return IdentityManifoldGeometry.from_directions(DEFAULT_DIRECTIONS) -# --- brief §3.1: induced action matrix ---------------------------------------- +# --- ADR-0246 §3 primitives — thin wrappers over the canonical implementations - +# The substantive definitions now live in ``core.physics.identity_manifold`` / +# ``identity_action`` (promoted from this slice-0 prototype). These wrappers keep +# the diagnostic's call sites and its merged test API stable while delegating to +# the single source of truth. def induced_action(geometry: IdentityManifoldGeometry, versor: np.ndarray) -> np.ndarray: - """``A_ij(F) = (G⁻¹)_ik ⟨a_k, F a_j F̃⟩₀`` — the full in-subspace action. - - Column ``j`` is the image of axis ``j`` expressed in the axis basis. Captures - in-span permutations/rotations/inversions that per-axis leakage misses. Raw - (unnormalized): a boost that stretches an axis shows up as a column norm > 1 - and hence in ``d_orth``, deliberately not hidden by normalization. - """ - versor = np.asarray(versor, dtype=np.float64) - n = len(geometry.axes_psi) - m = np.empty((n, n), dtype=np.float64) - for j, axis_j in enumerate(geometry.axes_psi): - image = sandwich(versor, axis_j) - for k, axis_k in enumerate(geometry.axes_psi): - m[k, j] = _inner0(axis_k, image) - return geometry.gram_inv @ m + """Induced action ``A(F)`` (delegates to the geometry primitive).""" + return geometry.induced_action(versor) def d_orth(geometry: IdentityManifoldGeometry, action: np.ndarray) -> float: - """``‖AᵀGA − G‖_F`` — 0 iff the induced action is a G-isometry of the span. - - Detects numerical corruption and non-isometric (e.g. boost-stretched) action; - must never be read as a semantic authorization policy (brief §3.2). - """ - G = geometry.gram - return float(np.linalg.norm(action.T @ G @ action - G, ord="fro")) + """``‖AᵀGA − G‖_F`` for a precomputed action (delegates to canonical).""" + return orthogonality_defect_of_action(action, geometry.gram) def d_stab(geometry: IdentityManifoldGeometry, action: np.ndarray) -> float: - """``min_{H∈H_id} ‖A − H‖_G`` under the LOCKED singleton ``H_id = {I}``. - - For the default pack the axis Gram is exactly the identity matrix, so the - G-weighted norm coincides with the Frobenius norm; pinning ‖·‖_G for general - packs is ADR-0246-proper work, not this slice's. - """ - eye = np.eye(action.shape[0], dtype=np.float64) - return float(np.linalg.norm(action - eye, ord="fro")) - - -# --- brief §3.6: typed residual channels -------------------------------------- + """``d_stab`` under the locked singleton ``H_id = {I}`` (delegates to canonical).""" + stabilizer = IdentityStabilizer.singleton(action.shape[0]) + return stabilizer_defect(action, geometry.gram, stabilizer) def typed_residual_channels( geometry: IdentityManifoldGeometry, versor: np.ndarray ) -> dict[str, float]: - """Energy split of the out-of-span rejection, summed over axes, as fractions - of total rotated-axis energy. - - Channels (pinned blade indices; default pack support = e1/e2/e3 so the - spatial-foreign channel is structurally empty and reported as 0): - - * ``null_or_conformal`` — e4 grade-1 residual energy (index 4) - * ``boost_like`` — e5 grade-1 residual energy (index 5) - * ``spatial_foreign`` — grade-1 spatial residual outside the axis - support (empty for the default pack) - * ``unclassified`` — everything else (higher-grade contamination - after the sandwich, numerical junk); fail-closed, - no correction policy ever attaches to it - """ - versor = np.asarray(versor, dtype=np.float64) - e4_energy = e5_energy = unclassified = total = 0.0 - for axis in geometry.axes_psi: - rotated = sandwich(versor, axis) - rejection = rotated - geometry.project(rotated) - total += euclidean_norm(rotated) ** 2 - e4_energy += float(rejection[IDX_E4] ** 2) - e5_energy += float(rejection[IDX_E5] ** 2) - accounted = rejection.copy() - accounted[IDX_E4] = 0.0 - accounted[IDX_E5] = 0.0 - unclassified += euclidean_norm(accounted) ** 2 - if total <= 0.0: - return { - "null_or_conformal": 1.0, - "boost_like": 0.0, - "spatial_foreign": 0.0, - "unclassified": 1.0, - } - return { - "null_or_conformal": e4_energy / total, - "boost_like": e5_energy / total, - "spatial_foreign": 0.0, - "unclassified": unclassified / total, - } + """Typed residual channel split (delegates to the geometry primitive).""" + return geometry.typed_residual_energy(versor) def versor_plane_occupancy(versor: np.ndarray) -> dict[str, float]: diff --git a/tests/test_adr_0246_induced_action.py b/tests/test_adr_0246_induced_action.py new file mode 100644 index 00000000..991f137d --- /dev/null +++ b/tests/test_adr_0246_induced_action.py @@ -0,0 +1,167 @@ +"""ADR-0246 Ring-1 §3 — pure induced-action / d_orth / d_stab / typed-residual pins. + +These pin the canonical primitives promoted from the slice-0 diagnostic prototype +into ``core.physics.identity_manifold`` (induced action A(F), orthogonality defect +d_orth, typed residual energy) and the new ``core.physics.identity_action`` (the +locked singleton stabilizer H_id={I} and the stabilizer defect d_stab). + +Ground truth is the brief §3.1–§3.3/§3.6 and §6.1 constructions: identity versor → +A=I; an in-span rotation is a G-isometry (d_orth≈0) but NOT the identity action +(d_stab>0); an in-span permutation/inversion is leakage-invisible but d_stab-visible; +an e4 tilt fires only the null/conformal channel; an e5 boost fires the boost channel +and is non-isometric (d_orth>0). The primitives are pure (algebra-only, off-serving). +""" + +from __future__ import annotations + +import numpy as np +import pytest + +from algebra.cl41 import N_COMPONENTS, basis_vector +from core.physics.identity_manifold import ( + IdentityManifoldGeometry, + E4_GRADE1_INDEX, + E5_GRADE1_INDEX, +) +from core.physics.identity_action import ( + IdentityStabilizer, + stabilizer_defect, + stabilizer_defect_for_versor, +) + +# grade-2 bivector plane indices (grade-2 block starts at 6) +_E12, _E13, _E14, _E15, _E23, _E24, _E25 = 6, 7, 8, 9, 10, 11, 12 + + +def _rotor(biv: int, theta: float) -> np.ndarray: + r = np.zeros(N_COMPONENTS, dtype=np.float64) + r[0] = np.cos(theta / 2.0) + r[biv] = np.sin(theta / 2.0) + return r + + +def _boost(biv: int, theta: float) -> np.ndarray: + r = np.zeros(N_COMPONENTS, dtype=np.float64) + r[0] = np.cosh(theta / 2.0) + r[biv] = np.sinh(theta / 2.0) + return r + + +def _identity_versor() -> np.ndarray: + v = np.zeros(N_COMPONENTS, dtype=np.float64) + v[0] = 1.0 + return v + + +@pytest.fixture(scope="module") +def geometry() -> IdentityManifoldGeometry: + # default pack: span(e1,e2,e3), Gram = I3 + return IdentityManifoldGeometry.from_directions( + ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)) + ) + + +def test_blade_index_constants_match_algebra(): + assert basis_vector(3)[E4_GRADE1_INDEX] == 1.0 # e4 + assert basis_vector(4)[E5_GRADE1_INDEX] == 1.0 # e5 + assert np.count_nonzero(basis_vector(3)) == 1 + assert np.count_nonzero(basis_vector(4)) == 1 + + +def test_identity_versor_action_is_identity(geometry): + action = geometry.induced_action(_identity_versor()) + assert np.allclose(action, np.eye(3), atol=1e-12) + assert geometry.orthogonality_defect(_identity_versor()) < 1e-12 + assert stabilizer_defect_for_versor(geometry, _identity_versor()) < 1e-12 + + +def test_inplane_rotation_is_isometry_but_not_identity_action(geometry): + theta = 0.4 + versor = _rotor(_E12, theta) + action = geometry.induced_action(versor) + # e12 rotor rotates the e1/e2 plane; e3 fixed. + assert action[2, 2] == pytest.approx(1.0, abs=1e-9) + assert abs(action[0, 0]) == pytest.approx(abs(np.cos(theta)), abs=1e-6) + assert geometry.orthogonality_defect(versor) < 1e-6 # G-isometry + assert stabilizer_defect_for_versor(geometry, versor) > 0.05 # not H_id={I} + + +def test_permutation_and_inversion_are_leakage_invisible_but_dstab_visible(geometry): + for versor in (_rotor(_E12, np.pi / 2.0), _rotor(_E12, np.pi)): + leak, _ = geometry.axis_response(versor) + assert max(leak) < 1e-6 + assert stabilizer_defect_for_versor(geometry, versor) > 0.05 + + +def test_e4_tilt_fires_only_null_conformal_channel(geometry): + channels = geometry.typed_residual_energy(_rotor(_E14, 1.2)) + assert channels["null_or_conformal"] > 0.05 + assert channels["boost_like"] == pytest.approx(0.0, abs=1e-12) + assert channels["unclassified"] < 1e-9 + + +def test_e5_boost_fires_boost_channel_and_is_non_isometric(geometry): + versor = _boost(_E15, 1.0) + channels = geometry.typed_residual_energy(versor) + assert channels["boost_like"] > 0.05 + assert channels["null_or_conformal"] == pytest.approx(0.0, abs=1e-12) + assert geometry.orthogonality_defect(versor) > 0.05 # boost not a G-isometry + + +def test_typed_residual_energy_fractions_are_bounded_and_clean(geometry): + for versor in (_rotor(_E14, 0.7), _boost(_E25, 0.6), _rotor(_E12, 0.3)): + ch = geometry.typed_residual_energy(versor) + total = ( + ch["null_or_conformal"] + + ch["boost_like"] + + ch["spatial_foreign"] + + ch["unclassified"] + ) + assert 0.0 <= total <= 1.0 + 1e-9 + # sandwich output of a versor stays grade-1: no unclassified contamination + assert ch["unclassified"] < 1e-9 + + +def test_stabilizer_is_singleton_identity_by_default(geometry): + stab = IdentityStabilizer.singleton(3) + assert len(stab.members) == 1 + assert np.allclose(stab.members[0], np.eye(3)) + # d_stab of the identity action is 0; of a rotation, > 0 + eye = np.eye(3) + assert stabilizer_defect(eye, geometry.gram, stab) < 1e-12 + rot = geometry.induced_action(_rotor(_E12, 0.5)) + assert stabilizer_defect(rot, geometry.gram, stab) > 0.05 + + +def test_stabilizer_defect_g_weighted_reduces_to_frobenius_at_identity_gram(geometry): + action = geometry.induced_action(_rotor(_E13, 0.35)) + stab = IdentityStabilizer.singleton(3) + d = stabilizer_defect(action, geometry.gram, stab) + frob = float(np.linalg.norm(action - np.eye(3), ord="fro")) + assert d == pytest.approx(frob, abs=1e-9) # default pack Gram is I3 + + +def test_induced_action_is_deterministic(geometry): + versor = _boost(_E15, 0.9) + a1 = geometry.induced_action(versor) + a2 = geometry.induced_action(versor.copy()) + assert np.array_equal(a1, a2) + + +def test_primitives_are_pure_offserving(): + import core.physics.identity_manifold as m + import core.physics.identity_action as a + + for mod in (m, a): + with open(mod.__file__, encoding="utf-8") as fh: + src = fh.read() + assert "chat.runtime" not in src + assert "import chat" not in src + + +def test_gate_surface_untouched_by_this_branch(): + from core.config import RuntimeConfig + from core.physics import identity + + assert RuntimeConfig().identity_wave_gate is False + assert identity._WAVE_LEAKAGE_BOUND == 0.2126624458513829