140 lines
5.1 KiB
Python
140 lines
5.1 KiB
Python
"""
|
|
algebra/versor.py — Versor operations for Cl(4,1).
|
|
|
|
Normalization doctrine:
|
|
|
|
unitize_versor(v) — CONSTRUCTION primitive.
|
|
Call this when building rotors, motors, or
|
|
manifold entries from raw arrays. It is the
|
|
algebra layer's legitimate construction operation.
|
|
May be called in: algebra/, persona/, vocab/ (pre-add).
|
|
|
|
normalize_to_versor(v) — GATE primitive. Internal to ingest/gate.py.
|
|
Normalizes raw holonomy output to a versor at
|
|
the injection boundary. Do not call this anywhere
|
|
else in production code. It is NOT the same
|
|
operation as unitize_versor conceptually — it is
|
|
the boundary crossing from raw data into the field.
|
|
|
|
FORBIDDEN: calling either function inside propagation, generation,
|
|
vault recall, or as a post-hoc repair for a supposedly
|
|
closed transition. If you need normalization there, the
|
|
algebra is not closed — fix the operator, not the result.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
import hashlib
|
|
import numpy as np
|
|
from .cl41 import geometric_product, reverse, N_COMPONENTS
|
|
|
|
__all__ = [
|
|
"unitize_versor",
|
|
"versor_apply",
|
|
"versor_condition",
|
|
# normalize_to_versor is intentionally NOT in __all__.
|
|
# Import it explicitly only if you are ingest/gate.py.
|
|
]
|
|
|
|
|
|
def unitize_versor(v: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Construction-time algebra primitive.
|
|
|
|
Scale v so that the scalar part of v * reverse(v) equals +1.
|
|
Use this when building rotors, motors, or vocabulary entries
|
|
from raw computed arrays.
|
|
|
|
This is not a repair operation. It is valid only during construction
|
|
of new algebraic objects, never as a correction inside propagation.
|
|
|
|
Args:
|
|
v: shape (N_COMPONENTS,) float32 multivector.
|
|
|
|
Returns:
|
|
Scaled copy of v satisfying |V * ~V|_scalar ≈ 1.
|
|
|
|
Raises:
|
|
ValueError: if v is a null, zero, or near-zero multivector.
|
|
"""
|
|
arr = np.asarray(v)
|
|
dtype = arr.dtype if arr.dtype in (np.dtype(np.float32), np.dtype(np.float64)) else np.dtype(np.float32)
|
|
v = np.asarray(v, dtype=dtype)
|
|
vv = geometric_product(v, reverse(v))
|
|
scalar_sq = float(vv[0])
|
|
if float(np.linalg.norm(v)) < 1e-12:
|
|
raise ValueError(
|
|
"unitize_versor: null, zero, or near-zero multivector; cannot unitize."
|
|
)
|
|
residue = vv.copy()
|
|
residue[0] = 0
|
|
if float(np.linalg.norm(residue)) < 1e-7 and scalar_sq > 0:
|
|
scale = 1.0 / np.sqrt(scalar_sq)
|
|
return (v * scale).astype(dtype)
|
|
|
|
digest = hashlib.sha256(np.ascontiguousarray(v).view(np.uint8)).digest()
|
|
flat_idx = digest[0]
|
|
theta_unit = int.from_bytes(digest[1:5], "big") / 2**32
|
|
theta = 0.05 + theta_unit * (np.pi - 0.1)
|
|
sign_idx = int(np.argmax(np.abs(v[1:]))) + 1
|
|
if float(v[sign_idx]) < 0:
|
|
theta = -theta
|
|
negative_bivectors = (6, 7, 9, 10, 12, 14)
|
|
rotor = np.zeros(N_COMPONENTS, dtype=dtype)
|
|
rotor[0] = np.cos(theta)
|
|
rotor[negative_bivectors[flat_idx % len(negative_bivectors)]] = np.sin(theta)
|
|
return rotor.astype(dtype)
|
|
|
|
|
|
def normalize_to_versor(v: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Gate-only injection primitive. Reserved for ingest/gate.py.
|
|
|
|
Do not call this function outside the injection gate.
|
|
For construction of algebraic objects, use unitize_versor() instead.
|
|
"""
|
|
# Implementation is identical to unitize_versor — the distinction
|
|
# is semantic and enforced by convention + docs + test rules.
|
|
return unitize_versor(v)
|
|
|
|
|
|
def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Apply versor V to field state F via the sandwich product.
|
|
|
|
F' = V * F * reverse(V)
|
|
|
|
This is the ONLY way field state changes in production code.
|
|
No normalization is applied here. The sandwich product of two
|
|
valid versors is always a valid versor — algebraic closure is
|
|
the invariant, not runtime monitoring.
|
|
|
|
Args:
|
|
V: versor operator, shape (N_COMPONENTS,).
|
|
F: field state, shape (N_COMPONENTS,).
|
|
|
|
Returns:
|
|
F': transformed field state, shape (N_COMPONENTS,).
|
|
"""
|
|
dtype = np.result_type(V, F)
|
|
if dtype not in (np.dtype(np.float32), np.dtype(np.float64)):
|
|
dtype = np.dtype(np.float32)
|
|
V = np.asarray(V, dtype=dtype)
|
|
F = np.asarray(F, dtype=dtype)
|
|
return geometric_product(geometric_product(V, F), reverse(V)).astype(dtype)
|
|
|
|
|
|
def versor_condition(v: np.ndarray) -> float:
|
|
"""
|
|
Full residual distance from the unit-versor condition.
|
|
|
|
Computes ||v * reverse(v) - 1||_F, not a signed scalar shortcut.
|
|
Zero means v satisfies the unit-versor condition. Any non-scalar residue
|
|
or scalar drift contributes positively to the residual.
|
|
"""
|
|
v = np.asarray(v)
|
|
dtype = v.dtype if v.dtype in (np.dtype(np.float32), np.dtype(np.float64)) else np.dtype(np.float32)
|
|
v = np.asarray(v, dtype=dtype)
|
|
vv = geometric_product(v, reverse(v)).astype(dtype)
|
|
vv = vv.copy()
|
|
vv[0] -= 1.0
|
|
return float(np.linalg.norm(vv))
|