From ba45158f8eed27549f906b8cfce0b6f259350db5 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 13 May 2026 12:51:49 -0700 Subject: [PATCH] Fix CGA null embedding convention --- algebra/cga.py | 54 +++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/algebra/cga.py b/algebra/cga.py index e142572b..7eced8e2 100644 --- a/algebra/cga.py +++ b/algebra/cga.py @@ -1,16 +1,30 @@ """ Conformal Geometric Algebra geometry on Cl(4,1). -Key identity: for null vectors X, Y on the horosphere, - cga_inner(X, Y) = -d(X, Y)^2 / 2 -where d is Euclidean distance. +Signature: (+,+,+,+,-), with Euclidean coordinates on e1,e2,e3. +The two conformal null directions are built from e4 and e5: + + n_o = 0.5 * (e5 - e4) # origin, n_o^2 = 0 + n_inf = e4 + e5 # infinity, n_inf^2 = 0 + n_o · n_inf = -1 + +A Euclidean point x embeds as: + + X = x + n_o + 0.5 * |x|^2 * n_inf + +Then X·X = 0 and X·Y = -0.5 * ||x-y||^2. This is the ONLY distance metric in CORE-AI. No cosine similarity. No L2 norm. No approximate indexing. """ import numpy as np -from .cl41 import geometric_product, reverse, scalar_part, basis_vector, N_COMPONENTS +from .cl41 import geometric_product, scalar_part, basis_vector, N_COMPONENTS + +# Basis-vector component indices for e4/e5 inside the grade-1 block. +# component 1=e1, 2=e2, 3=e3, 4=e4, 5=e5. +_E4_IDX = 4 +_E5_IDX = 5 def cga_inner(X: np.ndarray, Y: np.ndarray) -> float: @@ -28,7 +42,6 @@ def outer_product(X: np.ndarray, Y: np.ndarray) -> np.ndarray: Outer (wedge) product: X ^ Y. For a prompt versor X_p and response versor X_r, X_p ^ X_r is a grade-2 object encoding their geometric relationship. - A real (non-imaginary) result means the dialogue is coherent. """ XY = geometric_product(X, Y) YX = geometric_product(Y, X) @@ -36,35 +49,36 @@ def outer_product(X: np.ndarray, Y: np.ndarray) -> np.ndarray: def is_null(X: np.ndarray, tol: float = 1e-6) -> bool: - """Check if X lies on the null cone: X*X = 0.""" + """Check if X lies on the null cone: X·X = 0.""" return abs(cga_inner(X, X)) < tol def null_project(X: np.ndarray) -> np.ndarray: """ - Re-project X onto the null cone. - Call on vault entries periodically to correct floating-point null-cone drift. - This is numerical maintenance, not a heat shield. - Method: extract Euclidean part, re-embed via standard CGA point map. + Re-project X onto the null cone by extracting its Euclidean part and + re-embedding it with the canonical CGA point map. """ - euclidean = X[1:4].copy().astype(np.float32) - x_sq = float(np.dot(euclidean, euclidean)) - result = np.zeros(N_COMPONENTS, dtype=np.float32) - result[1:4] = euclidean - result[4] = 0.5 * x_sq # e+ coefficient - result[5] = 1.0 # e- coefficient - return result + euclidean = np.asarray(X, dtype=np.float32)[1:4].copy() + return embed_point(euclidean) def embed_point(x: np.ndarray) -> np.ndarray: """ Embed a Euclidean point x in R^3 into the CGA null cone. - Standard map: X = x + (1/2)|x|^2 * e+ + e- + + X = x + n_o + 0.5|x|^2 n_inf, + where n_o = 0.5(e5-e4), n_inf = e4+e5. """ x = np.asarray(x, dtype=np.float32) assert len(x) == 3, "embed_point expects a 3D vector" + + x_sq = float(np.dot(x, x)) result = np.zeros(N_COMPONENTS, dtype=np.float32) result[1:4] = x - result[4] = 0.5 * float(np.dot(x, x)) - result[5] = 1.0 + + # n_o + 0.5|x|^2 n_inf + # e4 coefficient: -0.5 + 0.5|x|^2 + # e5 coefficient: 0.5 + 0.5|x|^2 + result[_E4_IDX] = 0.5 * (x_sq - 1.0) + result[_E5_IDX] = 0.5 * (x_sq + 1.0) return result