Fix CGA null embedding convention
This commit is contained in:
parent
4fe19c08ba
commit
ba45158f8e
1 changed files with 34 additions and 20 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue