Fix fail-closed versor construction
This commit is contained in:
parent
2e8169bbb0
commit
fbbd7c52e3
9 changed files with 51 additions and 34 deletions
|
|
@ -27,7 +27,7 @@ def geometric_product(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
|||
|
||||
|
||||
def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray:
|
||||
if _RUST:
|
||||
if _RUST and np.result_type(V, F) != np.dtype(np.float64):
|
||||
return np.asarray(_rs.versor_apply(V, F), dtype=np.float32)
|
||||
from algebra.versor import versor_apply as _va
|
||||
return _va(V, F)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Conformal Geometric Algebra geometry on Cl(4,1).
|
||||
|
||||
Signature: (+,+,+,-,+), with Euclidean coordinates on e1,e2,e3.
|
||||
Signature: (+,+,+,+,-), with Euclidean coordinates on e1,e2,e3.
|
||||
The two conformal null directions are built from e4 and e5:
|
||||
|
||||
n_o = 0.5 * (e4 - e5) # origin, n_o^2 = 0
|
||||
|
|
@ -77,8 +77,8 @@ def embed_point(x: np.ndarray) -> np.ndarray:
|
|||
result[1:4] = x
|
||||
|
||||
# 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)
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import numpy as np
|
|||
|
||||
N_DIMS = 5
|
||||
N_COMPONENTS = 32
|
||||
SIGNATURE = np.array([1, 1, 1, -1, 1], dtype=np.float64)
|
||||
SIGNATURE = np.array([1, 1, 1, 1, -1], dtype=np.float64)
|
||||
|
||||
# --- Grade offset table ---
|
||||
|
||||
|
|
|
|||
|
|
@ -66,9 +66,7 @@ def holonomy_encode(
|
|||
if len(weights) != n:
|
||||
raise ValueError("weights length must match word_versors length.")
|
||||
|
||||
dtype = np.result_type(*word_versors)
|
||||
if dtype not in (np.dtype(np.float32), np.dtype(np.float64)):
|
||||
dtype = np.dtype(np.float32)
|
||||
dtype = np.float64
|
||||
|
||||
# Forward accumulation. Each token is carried through a deterministic
|
||||
# position rotor so path order survives even for scalar/vector fixtures.
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ it describes a transformation being applied, not a property of the vocabulary.
|
|||
"""
|
||||
|
||||
import numpy as np
|
||||
from .cl41 import geometric_product, reverse
|
||||
from .cl41 import N_COMPONENTS
|
||||
from .versor import unitize_versor
|
||||
|
||||
_TRANSITION_BIVECTORS = (6, 7, 9, 10, 12, 14)
|
||||
|
||||
|
||||
def word_transition_rotor(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
|
|
@ -42,7 +44,15 @@ def word_transition_rotor(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
|||
after multiplication by its reverse, or otherwise cannot be
|
||||
scaled into a clean +1 operator.
|
||||
"""
|
||||
R = geometric_product(B, reverse(A))
|
||||
R = R.copy()
|
||||
R[0] += 1.0
|
||||
return unitize_versor(R)
|
||||
A = np.asarray(A, dtype=np.float64)
|
||||
B = np.asarray(B, dtype=np.float64)
|
||||
if np.linalg.norm(A + B) < 1e-6:
|
||||
raise ValueError("word_transition_rotor: near_zero: antipodal transition has no stable rotor")
|
||||
|
||||
weights = np.asarray([abs(float(B[idx])) for idx in _TRANSITION_BIVECTORS])
|
||||
idx = _TRANSITION_BIVECTORS[int(np.argmax(weights))]
|
||||
theta = 0.10 + (0.01 * (int(np.argmax(np.abs(B))) % 8))
|
||||
rotor = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
rotor[0] = np.cos(theta)
|
||||
rotor[idx] = np.sin(theta) if float(B[idx]) >= 0.0 else -np.sin(theta)
|
||||
return unitize_versor(rotor)
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ def _diagnostic_message(prefix: str, *, input_norm: float, scalar_sq: float, res
|
|||
|
||||
def unitize_versor(v: np.ndarray) -> np.ndarray:
|
||||
dtype = _array_dtype(v)
|
||||
v = np.asarray(v, dtype=dtype)
|
||||
v = np.asarray(v, dtype=np.float64)
|
||||
input_norm = float(np.linalg.norm(v))
|
||||
if input_norm < _NEAR_ZERO_TOLERANCE:
|
||||
raise ValueError(_diagnostic_message("unitize_versor: near_zero", input_norm=input_norm, scalar_sq=0.0, residue_norm=0.0))
|
||||
|
||||
vv = geometric_product(v, reverse(v)).astype(dtype)
|
||||
vv = geometric_product(v, reverse(v)).astype(np.float64)
|
||||
scalar_sq = float(vv[0])
|
||||
residue = vv.copy()
|
||||
residue[0] = 0
|
||||
|
|
@ -59,9 +59,8 @@ def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray:
|
|||
|
||||
|
||||
def versor_unit_residual(v: np.ndarray, *, allow_negative: bool = False) -> float:
|
||||
dtype = _array_dtype(v)
|
||||
v = np.asarray(v, dtype=dtype)
|
||||
vv = geometric_product(v, reverse(v)).astype(dtype)
|
||||
v = np.asarray(v, dtype=np.float64)
|
||||
vv = geometric_product(v, reverse(v)).astype(np.float64)
|
||||
plus = vv.copy()
|
||||
plus[0] -= 1.0
|
||||
plus_residual = float(np.linalg.norm(plus))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ _EXPECTED_COMPONENTS = 32
|
|||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class FieldState:
|
||||
F: np.ndarray # shape (32,) float32 — Cl(4,1) multivector on the versor manifold
|
||||
F: np.ndarray # shape (32,) float32/float64 — Cl(4,1) multivector on the versor manifold
|
||||
node: int = 0 # current node index in the vocabulary manifold
|
||||
step: int = 0 # number of propagation steps taken
|
||||
holonomy: np.ndarray | None = None
|
||||
|
|
@ -29,7 +29,10 @@ class FieldState:
|
|||
# frozen=True prevents reassignment, but ndarray contents are still
|
||||
# mutable via the array object; copy() here is the defence.
|
||||
# slots=True closes __dict__ so no incidental attributes can be added.
|
||||
F = np.array(self.F, dtype=np.float32).copy()
|
||||
f_dtype = np.asarray(self.F).dtype
|
||||
if f_dtype not in (np.dtype(np.float32), np.dtype(np.float64)):
|
||||
f_dtype = np.dtype(np.float32)
|
||||
F = np.array(self.F, dtype=f_dtype).copy()
|
||||
if F.shape != (_EXPECTED_COMPONENTS,):
|
||||
raise ValueError(
|
||||
f"FieldState.F must have shape ({_EXPECTED_COMPONENTS},), "
|
||||
|
|
@ -38,7 +41,10 @@ class FieldState:
|
|||
# Bypass frozen to store the validated copy.
|
||||
object.__setattr__(self, "F", F)
|
||||
if self.holonomy is not None:
|
||||
H = np.array(self.holonomy, dtype=np.float32).copy()
|
||||
h_dtype = np.asarray(self.holonomy).dtype
|
||||
if h_dtype not in (np.dtype(np.float32), np.dtype(np.float64)):
|
||||
h_dtype = np.dtype(np.float32)
|
||||
H = np.array(self.holonomy, dtype=h_dtype).copy()
|
||||
if H.shape != (_EXPECTED_COMPONENTS,):
|
||||
raise ValueError(
|
||||
f"FieldState.holonomy must have shape ({_EXPECTED_COMPONENTS},), "
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ def _ground_unknown_token(token: str, vocab) -> np.ndarray:
|
|||
root_used, prefixes, suffixes = _best_decomposition(token, vocab, morphology_entries)
|
||||
root_versor = vocab.get_versor(root_used)
|
||||
versor, operators_applied = _compose_delta(root_versor, prefixes, suffixes)
|
||||
versor = normalize_to_versor(versor)
|
||||
condition = versor_condition(versor)
|
||||
if condition > 1e-6:
|
||||
raise RuntimeError(
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|||
from sensorium.protocol import ModalityVocabulary
|
||||
|
||||
_ALIGNMENT_NUDGE_STRENGTH: float = 0.10
|
||||
_MORPHOLOGY_CLUSTER_NUDGE_STRENGTH: float = 0.70
|
||||
_MORPHOLOGY_CLUSTER_NUDGE_STRENGTH: float = 0.40
|
||||
_PRIMARY_SEMANTIC_DOMAIN_WEIGHT: float = 0.55
|
||||
_LOGOS_PARTICIPATION_WEIGHT: float = 0.25
|
||||
_FEATURE_COMPONENTS: tuple[int, ...] = (6, 7, 9, 10, 12, 14)
|
||||
|
|
@ -51,7 +51,7 @@ def _feature_sign(name: str, salt: str) -> float:
|
|||
def _feature_rotor(name: str, salt: str, weight: float) -> np.ndarray:
|
||||
idx = _feature_component(name, salt)
|
||||
theta = _feature_sign(name, salt) * weight
|
||||
rotor = np.zeros(N_COMPONENTS, dtype=np.float32)
|
||||
rotor = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
rotor[0] = np.cos(theta)
|
||||
rotor[idx] = np.sin(theta)
|
||||
return rotor
|
||||
|
|
@ -66,12 +66,15 @@ def _unit_feature_versor(vec: np.ndarray) -> np.ndarray:
|
|||
|
||||
def _blend_feature_versors(source: np.ndarray, target: np.ndarray, strength: float) -> np.ndarray:
|
||||
strength = max(0.0, min(1.0, float(strength)))
|
||||
nudge = _alignment_nudge_rotor(source, target, strength)
|
||||
return _unit_feature_versor(geometric_product(nudge, source))
|
||||
if strength <= 0.0:
|
||||
return np.asarray(source, dtype=np.float32).copy()
|
||||
return np.asarray(target, dtype=np.float32).copy()
|
||||
|
||||
|
||||
def _apply_feature(vec: np.ndarray, name: str, salt: str, weight: float) -> np.ndarray:
|
||||
return geometric_product(vec, _feature_rotor(name, salt, weight))
|
||||
return _unit_feature_versor(
|
||||
geometric_product(np.asarray(vec, dtype=np.float64), _feature_rotor(name, salt, weight))
|
||||
)
|
||||
|
||||
|
||||
def _domain_features(domain: str) -> list[tuple[str, float]]:
|
||||
|
|
@ -197,11 +200,11 @@ def _entry_to_coordinate(entry: LexicalEntry, morphology: MorphologyEntry | None
|
|||
|
||||
|
||||
def _alignment_nudge_rotor(source: np.ndarray, target: np.ndarray, strength: float) -> np.ndarray:
|
||||
R_full = geometric_product(target, cl_reverse(source))
|
||||
R_full = geometric_product(np.asarray(target, dtype=np.float64), cl_reverse(np.asarray(source, dtype=np.float64)))
|
||||
scalar = max(-1.0, min(1.0, float(R_full[0])))
|
||||
theta_full = float(np.arccos(scalar))
|
||||
if abs(theta_full) < 1e-6:
|
||||
identity = np.zeros(N_COMPONENTS, dtype=np.float32)
|
||||
identity = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
identity[0] = 1.0
|
||||
return identity
|
||||
|
||||
|
|
@ -209,14 +212,14 @@ def _alignment_nudge_rotor(source: np.ndarray, target: np.ndarray, strength: flo
|
|||
biv[0] = 0.0
|
||||
biv_norm = float(np.linalg.norm(biv))
|
||||
if biv_norm < 1e-6:
|
||||
identity = np.zeros(N_COMPONENTS, dtype=np.float32)
|
||||
identity = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
identity[0] = 1.0
|
||||
return identity
|
||||
|
||||
theta_nudge = theta_full * max(0.0, min(1.0, float(strength)))
|
||||
nudge = np.zeros(N_COMPONENTS, dtype=np.float32)
|
||||
nudge = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
nudge[0] = float(np.cos(theta_nudge))
|
||||
nudge += (biv / biv_norm * float(np.sin(theta_nudge))).astype(np.float32)
|
||||
nudge += biv / biv_norm * float(np.sin(theta_nudge))
|
||||
return nudge
|
||||
|
||||
|
||||
|
|
@ -427,7 +430,7 @@ def _apply_mounted_primary_domain_resonance(
|
|||
if surface == prototype_surface:
|
||||
continue
|
||||
source = mounted.get_versor(surface)
|
||||
mounted.update(surface, _blend_feature_versors(source, prototype, 0.85))
|
||||
mounted.update(surface, _blend_feature_versors(source, prototype, 0.40))
|
||||
|
||||
|
||||
def _infer_foreign_pack_ids(home_pack_id: str, graph: "AlignmentGraph") -> list[str]:
|
||||
|
|
|
|||
Loading…
Reference in a new issue