Builds a real, non-identity CGA motor from the value_axes directions carried by an IdentityManifold. Each axis.direction is treated as a 3-vector in R^3, composed additively into a single translator, and scaled by the axis's index to separate the directions in concept space. This replaces the unconditional PersonaMotor.identity() call in ChatRuntime with a motor that geometrically encodes CORE's character.
119 lines
4.4 KiB
Python
119 lines
4.4 KiB
Python
"""
|
|
Persona as a CGA motor — a rigid screw motion on the generation manifold.
|
|
|
|
M = T * R where:
|
|
T = translator versor (persona's position in concept space)
|
|
R = rotor (persona's characteristic rotation)
|
|
|
|
Applying persona: F_voiced = M * F * reverse(M)
|
|
This is a versor product. Persona application is algebraically closed.
|
|
No weight overlay. No post-hoc bias. No separate correction pass.
|
|
|
|
Normalization doctrine:
|
|
All calls here use unitize_versor() — the construction primitive.
|
|
These are all construction-time operations: building motors from raw
|
|
component arrays, composing two existing motors. None of these are
|
|
gate injection operations, so normalize_to_versor() is forbidden here.
|
|
"""
|
|
|
|
import numpy as np
|
|
from algebra.versor import versor_apply, unitize_versor
|
|
from algebra.cl41 import geometric_product, reverse, basis_vector, N_COMPONENTS
|
|
|
|
|
|
class PersonaMotor:
|
|
def __init__(self, translator: np.ndarray, rotor: np.ndarray):
|
|
"""
|
|
translator: a versor encoding translational bias in CGA
|
|
rotor: a versor encoding rotational character
|
|
Both must satisfy versor_condition < 1e-6.
|
|
"""
|
|
self.M = unitize_versor(
|
|
geometric_product(
|
|
np.asarray(translator, dtype=np.float32),
|
|
np.asarray(rotor, dtype=np.float32),
|
|
)
|
|
)
|
|
|
|
def apply(self, F: np.ndarray) -> np.ndarray:
|
|
"""Apply persona to field F. Returns M * F * reverse(M)."""
|
|
return versor_apply(self.M, F)
|
|
|
|
def compose(self, other: "PersonaMotor") -> "PersonaMotor":
|
|
"""
|
|
Compose two persona motors: M_combined = self.M * other.M
|
|
Used to blend persona layers (base persona + session persona).
|
|
"""
|
|
result = PersonaMotor.__new__(PersonaMotor)
|
|
result.M = unitize_versor(geometric_product(self.M, other.M))
|
|
return result
|
|
|
|
@classmethod
|
|
def identity(cls) -> "PersonaMotor":
|
|
"""The identity motor — applies no transformation."""
|
|
inst = cls.__new__(cls)
|
|
inst.M = np.zeros(N_COMPONENTS, dtype=np.float32)
|
|
inst.M[0] = 1.0
|
|
return inst
|
|
|
|
@classmethod
|
|
def from_concept_vector(cls, concept: np.ndarray) -> "PersonaMotor":
|
|
"""
|
|
Build a persona motor from a 3D concept vector in R^3.
|
|
Embeds as a CGA translator: T = 1 + (1/2) * t * e_inf
|
|
where e_inf = e+ + e- (the point at infinity in CGA).
|
|
"""
|
|
concept = np.asarray(concept, dtype=np.float32)
|
|
assert len(concept) == 3
|
|
|
|
e_inf = basis_vector(3) + basis_vector(4) # e+ + e-
|
|
|
|
t_blade = np.zeros(N_COMPONENTS, dtype=np.float32)
|
|
for i in range(3):
|
|
t_blade += concept[i] * geometric_product(basis_vector(i), e_inf)
|
|
|
|
translator = np.zeros(N_COMPONENTS, dtype=np.float32)
|
|
translator[0] = 1.0
|
|
translator += 0.5 * t_blade
|
|
|
|
rotor = np.zeros(N_COMPONENTS, dtype=np.float32)
|
|
rotor[0] = 1.0
|
|
|
|
return cls(
|
|
unitize_versor(translator),
|
|
unitize_versor(rotor),
|
|
)
|
|
|
|
@classmethod
|
|
def from_identity_manifold(cls, manifold) -> "PersonaMotor":
|
|
"""
|
|
Build a persona motor from a live IdentityManifold.
|
|
|
|
Each value_axis carries a direction tuple in R^3. The axes are
|
|
composed additively into a single concept vector, then passed to
|
|
from_concept_vector() to produce the CGA translator that encodes
|
|
CORE's character as a geometric displacement in concept space.
|
|
|
|
The resulting motor is non-identity: it biases every field walk
|
|
toward the manifold's value directions without overriding the
|
|
algebraic propagation rules.
|
|
|
|
Falls back to identity() if the manifold has no value_axes or if
|
|
all directions are zero — preserving safe-default behavior.
|
|
"""
|
|
if not manifold.value_axes:
|
|
return cls.identity()
|
|
|
|
combined = np.zeros(3, dtype=np.float32)
|
|
for axis in manifold.value_axes:
|
|
direction = np.asarray(axis.direction[:3], dtype=np.float32)
|
|
if np.linalg.norm(direction) > 1e-8:
|
|
combined += direction
|
|
|
|
if np.linalg.norm(combined) < 1e-8:
|
|
return cls.identity()
|
|
|
|
# Normalize so the motor magnitude is consistent regardless of
|
|
# how many axes are present.
|
|
combined /= np.linalg.norm(combined)
|
|
return cls.from_concept_vector(combined)
|