Fail closed on invalid versor construction
Make versor construction fail closed instead of synthesizing hash-derived fallback rotors. - remove pseudo-random construction fallback from unitize_versor - add signed residual helper for +1 field states vs ±1 manifold entries - validate vocab manifold entries with full residuals - document antipodal transition rotor failure contract - add focused invariant tests for versor closure and manifold validation
This commit is contained in:
parent
aadaf11612
commit
9723941a38
5 changed files with 197 additions and 145 deletions
|
|
@ -25,12 +25,22 @@ def word_transition_rotor(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
|||
encode a position. Call this from algebra-aware field logic; never
|
||||
store the result on a vocabulary structure.
|
||||
|
||||
Antipodal or near-antipodal inputs can make 1 + B * reverse(A) null or
|
||||
near-zero. That is an ill-conditioned transition construction, not a
|
||||
case for synthetic fallback. unitize_versor() must fail closed, and the
|
||||
caller must decide whether to skip, terminate, or choose another edge.
|
||||
|
||||
Args:
|
||||
A: Source versor, shape (32,), grade-normed to ±1.
|
||||
B: Target versor, shape (32,), grade-normed to ±1.
|
||||
|
||||
Returns:
|
||||
R: Unitized rotor in Cl(4,1), shape (32,).
|
||||
|
||||
Raises:
|
||||
ValueError: if the transition rotor is null, near-zero, non-scalar
|
||||
after multiplication by its reverse, or otherwise cannot be
|
||||
scaled into a clean +1 operator.
|
||||
"""
|
||||
R = geometric_product(B, reverse(A))
|
||||
R = R.copy()
|
||||
|
|
|
|||
|
|
@ -1,120 +1,55 @@
|
|||
"""
|
||||
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
|
||||
from .cl41 import geometric_product, reverse
|
||||
|
||||
__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.
|
||||
"versor_unit_residual",
|
||||
]
|
||||
|
||||
_CONSTRUCTION_RESIDUE_TOLERANCE = 1e-2
|
||||
_NEAR_ZERO_TOLERANCE = 1e-12
|
||||
|
||||
|
||||
def _array_dtype(v: np.ndarray) -> np.dtype:
|
||||
arr = np.asarray(v)
|
||||
return arr.dtype if arr.dtype in (np.dtype(np.float32), np.dtype(np.float64)) else np.dtype(np.float32)
|
||||
|
||||
|
||||
def _diagnostic_message(prefix: str, *, input_norm: float, scalar_sq: float, residue_norm: float) -> str:
|
||||
return f"{prefix}: input_norm={input_norm:.6e}, scalar_sq={scalar_sq:.6e}, residue_norm={residue_norm:.6e}"
|
||||
|
||||
|
||||
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)
|
||||
dtype = _array_dtype(v)
|
||||
v = np.asarray(v, dtype=dtype)
|
||||
vv = geometric_product(v, reverse(v))
|
||||
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)
|
||||
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)
|
||||
residue_norm = float(np.linalg.norm(residue))
|
||||
|
||||
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)
|
||||
if residue_norm >= _CONSTRUCTION_RESIDUE_TOLERANCE:
|
||||
raise ValueError(_diagnostic_message("unitize_versor: bad_residue", input_norm=input_norm, scalar_sq=scalar_sq, residue_norm=residue_norm))
|
||||
|
||||
if scalar_sq <= 0.0:
|
||||
raise ValueError(_diagnostic_message("unitize_versor: bad_scalar", input_norm=input_norm, scalar_sq=scalar_sq, residue_norm=residue_norm))
|
||||
|
||||
return (v * (1.0 / np.sqrt(scalar_sq))).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)
|
||||
|
|
@ -123,18 +58,19 @@ def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray:
|
|||
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)
|
||||
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)
|
||||
vv = vv.copy()
|
||||
vv[0] -= 1.0
|
||||
return float(np.linalg.norm(vv))
|
||||
plus = vv.copy()
|
||||
plus[0] -= 1.0
|
||||
plus_residual = float(np.linalg.norm(plus))
|
||||
if not allow_negative:
|
||||
return plus_residual
|
||||
minus = vv.copy()
|
||||
minus[0] += 1.0
|
||||
return min(plus_residual, float(np.linalg.norm(minus)))
|
||||
|
||||
|
||||
def versor_condition(v: np.ndarray) -> float:
|
||||
return versor_unit_residual(v, allow_negative=False)
|
||||
|
|
|
|||
|
|
@ -4,10 +4,17 @@ It verifies the core algebraic invariant of the entire system.
|
|||
"""
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from hypothesis import given, settings
|
||||
from hypothesis import strategies as st
|
||||
|
||||
from algebra.versor import versor_apply, unitize_versor, versor_condition
|
||||
from algebra.rotor import word_transition_rotor
|
||||
from algebra.versor import (
|
||||
unitize_versor,
|
||||
versor_apply,
|
||||
versor_condition,
|
||||
versor_unit_residual,
|
||||
)
|
||||
|
||||
|
||||
def _positive_unit_reflector(seed=None) -> np.ndarray:
|
||||
|
|
@ -16,8 +23,7 @@ def _positive_unit_reflector(seed=None) -> np.ndarray:
|
|||
|
||||
The current field action uses V * F * reverse(V), so the operator fixture
|
||||
must satisfy V * reverse(V) = +1, not -1. We therefore keep the fifth
|
||||
(negative-metric) basis component bounded below the positive four-space
|
||||
norm before construction-unitizing.
|
||||
basis component bounded below the positive four-space norm.
|
||||
"""
|
||||
rng = np.random.default_rng(seed)
|
||||
vec4 = rng.standard_normal(4).astype(np.float32)
|
||||
|
|
@ -38,7 +44,6 @@ def _positive_unit_reflector(seed=None) -> np.ndarray:
|
|||
@given(st.integers(min_value=0, max_value=99))
|
||||
@settings(max_examples=100)
|
||||
def test_versor_apply_preserves_manifold(seed):
|
||||
"""V*F*reverse(V) must be a versor if V and F are positive unit versors."""
|
||||
V = _positive_unit_reflector(seed)
|
||||
F = _positive_unit_reflector(seed + 1000)
|
||||
result = versor_apply(V, F)
|
||||
|
|
@ -46,18 +51,50 @@ def test_versor_apply_preserves_manifold(seed):
|
|||
assert cond < 1e-4, f"versor_apply broke the manifold: condition={cond:.2e}"
|
||||
|
||||
|
||||
def test_unitize_random_multivector_constructs_versor():
|
||||
"""
|
||||
unitize_versor() is the construction primitive for lifting raw
|
||||
deterministic coordinates into a valid versor.
|
||||
"""
|
||||
raw = np.random.default_rng(0).standard_normal(32).astype(np.float32)
|
||||
def test_unitize_clean_scalar_constructs_positive_unit_versor():
|
||||
raw = np.zeros(32, dtype=np.float32)
|
||||
raw[0] = 2.0
|
||||
V = unitize_versor(raw)
|
||||
assert versor_condition(V) < 1e-5
|
||||
assert np.allclose(V[0], 1.0, atol=1e-7)
|
||||
assert versor_condition(V) < 1e-7
|
||||
|
||||
|
||||
def test_unitize_rejects_non_scalar_residue_instead_of_hash_fallback():
|
||||
dirty = np.zeros(32, dtype=np.float32)
|
||||
dirty[0] = np.sqrt(0.5)
|
||||
dirty[1] = np.sqrt(0.5)
|
||||
|
||||
with pytest.raises(ValueError, match="bad_residue"):
|
||||
unitize_versor(dirty)
|
||||
|
||||
|
||||
def test_unitize_rejects_non_positive_scalar_norm():
|
||||
negative_norm = np.zeros(32, dtype=np.float32)
|
||||
negative_norm[5] = 1.0
|
||||
|
||||
with pytest.raises(ValueError, match="bad_scalar"):
|
||||
unitize_versor(negative_norm)
|
||||
|
||||
|
||||
def test_versor_unit_residual_can_accept_signed_manifold_versors():
|
||||
negative_norm = np.zeros(32, dtype=np.float32)
|
||||
negative_norm[5] = 1.0
|
||||
|
||||
assert versor_condition(negative_norm) > 1.0
|
||||
assert versor_unit_residual(negative_norm, allow_negative=True) < 1e-7
|
||||
|
||||
|
||||
def test_word_transition_rotor_fails_closed_for_antipodal_inputs():
|
||||
A = np.zeros(32, dtype=np.float32)
|
||||
A[0] = 1.0
|
||||
B = np.zeros(32, dtype=np.float32)
|
||||
B[0] = -1.0
|
||||
|
||||
with pytest.raises(ValueError, match="near_zero"):
|
||||
word_transition_rotor(A, B)
|
||||
|
||||
|
||||
def test_composition_closed():
|
||||
"""Two sequential versor_apply calls stay on the manifold."""
|
||||
V1 = _positive_unit_reflector(0)
|
||||
V2 = _positive_unit_reflector(1)
|
||||
F = _positive_unit_reflector(2)
|
||||
|
|
@ -67,7 +104,6 @@ def test_composition_closed():
|
|||
|
||||
|
||||
def test_identity_versor():
|
||||
"""Scalar 1 is a valid versor and applies as identity."""
|
||||
identity = np.zeros(32, dtype=np.float32)
|
||||
identity[0] = 1.0
|
||||
F = _positive_unit_reflector(42)
|
||||
|
|
|
|||
52
tests/test_vocab_manifold_invariants.py
Normal file
52
tests/test_vocab_manifold_invariants.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from vocab.manifold import VocabManifold
|
||||
|
||||
|
||||
def _identity() -> np.ndarray:
|
||||
v = np.zeros(32, dtype=np.float32)
|
||||
v[0] = 1.0
|
||||
return v
|
||||
|
||||
|
||||
def _negative_unit_vector() -> np.ndarray:
|
||||
v = np.zeros(32, dtype=np.float32)
|
||||
v[5] = 1.0
|
||||
return v
|
||||
|
||||
|
||||
def _scalar_norm_one_with_non_scalar_residue() -> np.ndarray:
|
||||
v = np.zeros(32, dtype=np.float32)
|
||||
v[0] = np.sqrt(0.5)
|
||||
v[1] = np.sqrt(0.5)
|
||||
return v
|
||||
|
||||
|
||||
def test_manifold_accepts_positive_unit_versor() -> None:
|
||||
manifold = VocabManifold()
|
||||
manifold.add("one", _identity())
|
||||
assert manifold.index_of("one") == 0
|
||||
|
||||
|
||||
def test_manifold_accepts_negative_unit_versor() -> None:
|
||||
"""Vocabulary manifold entries follow the mathematical ±1 versor contract."""
|
||||
manifold = VocabManifold()
|
||||
manifold.add("negative", _negative_unit_vector())
|
||||
assert manifold.index_of("negative") == 0
|
||||
|
||||
|
||||
def test_manifold_rejects_scalar_norm_shortcut_with_non_scalar_residue() -> None:
|
||||
"""Scalar grade-norm near one is insufficient when residue is non-scalar."""
|
||||
manifold = VocabManifold()
|
||||
|
||||
with pytest.raises(ValueError, match="non_scalar_residue"):
|
||||
manifold.add("dirty", _scalar_norm_one_with_non_scalar_residue())
|
||||
|
||||
|
||||
def test_manifold_update_rejects_non_scalar_residue() -> None:
|
||||
manifold = VocabManifold()
|
||||
manifold.add("clean", _identity())
|
||||
|
||||
with pytest.raises(ValueError, match="replacement versor residual"):
|
||||
manifold.update("clean", _scalar_norm_one_with_non_scalar_residue())
|
||||
|
|
@ -4,9 +4,10 @@ VocabManifold — the geometric vocabulary.
|
|||
Each word is a versor in Cl(4,1). nearest(F) finds the closest word
|
||||
by CGA inner product — no cosine similarity, no ANN index.
|
||||
|
||||
Invariant: every stored versor must satisfy the Cl(4,1) grade-norm
|
||||
condition |V * reverse(V)|_scalar ≈ ±1. This is enforced at insertion
|
||||
time in add() and at replacement time in update().
|
||||
Invariant: every stored versor must satisfy the full Cl(4,1) unit-versor
|
||||
condition V * reverse(V) ≈ ±1. This rejects non-scalar construction residue,
|
||||
not merely scalar grade-norm drift, and is enforced at insertion time in
|
||||
add() and at replacement time in update().
|
||||
|
||||
Normalization doctrine for this module:
|
||||
- Raw coordinate vectors (e.g. from external embeddings) must be
|
||||
|
|
@ -30,13 +31,41 @@ dispatches to the Rust extension when available.
|
|||
import numpy as np
|
||||
from algebra.backend import cga_inner
|
||||
from algebra.cl41 import geometric_product, reverse
|
||||
from algebra.versor import versor_unit_residual
|
||||
from language_packs.schema import MorphologyEntry
|
||||
|
||||
_MANIFOLD_RESIDUAL_TOLERANCE = 1e-5
|
||||
|
||||
|
||||
def _versor_diagnostics(v: np.ndarray) -> tuple[float, float, float]:
|
||||
product = geometric_product(v, reverse(v))
|
||||
scalar = float(product[0])
|
||||
residue = product.copy()
|
||||
residue[0] = 0.0
|
||||
residue_norm = float(np.linalg.norm(residue))
|
||||
residual = versor_unit_residual(v, allow_negative=True)
|
||||
return residual, scalar, residue_norm
|
||||
|
||||
|
||||
def _assert_manifold_versor(word: str, versor: np.ndarray, *, replacement: bool = False) -> None:
|
||||
residual, scalar, residue_norm = _versor_diagnostics(versor)
|
||||
if residual > _MANIFOLD_RESIDUAL_TOLERANCE:
|
||||
noun = "replacement versor" if replacement else "versor"
|
||||
action = "Call algebra.versor.unitize_versor() before update()." if replacement else (
|
||||
"If lifting from a raw array, call algebra.versor.unitize_versor() first."
|
||||
)
|
||||
raise ValueError(
|
||||
f"Word '{word}': {noun} residual {residual:.4e} exceeds "
|
||||
f"{_MANIFOLD_RESIDUAL_TOLERANCE:.1e}; scalar={scalar:.4f}, "
|
||||
f"non_scalar_residue={residue_norm:.4e}. Pass a clean Cl(4,1) "
|
||||
f"unit versor satisfying V*reverse(V)≈±1. {action}"
|
||||
)
|
||||
|
||||
|
||||
class VocabManifold:
|
||||
def __init__(self):
|
||||
self._words: list[str] = []
|
||||
self._versors: list[np.ndarray] = [] # each shape (32,), grade-normed to ±1
|
||||
self._versors: list[np.ndarray] = [] # each shape (32,), unit-versor ±1
|
||||
self._morphology_by_word: dict[str, MorphologyEntry] = {}
|
||||
self._language_by_word: dict[str, str] = {}
|
||||
self._transient_words: set[str] = set()
|
||||
|
|
@ -52,10 +81,10 @@ class VocabManifold:
|
|||
"""
|
||||
Register a word-versor pair.
|
||||
|
||||
Enforces the Cl(4,1) versor invariant: the scalar part of
|
||||
V * reverse(V) must be ≈ ±1. This rejects any raw coordinate
|
||||
vector or external embedding that has not been lifted into the
|
||||
algebra.
|
||||
Enforces the Cl(4,1) manifold invariant: V * reverse(V) must be
|
||||
approximately +1 or -1 as a full multivector residual, not merely
|
||||
in its scalar component. This rejects raw coordinates, external
|
||||
embeddings, and dirty construction products.
|
||||
|
||||
If your source is a raw float array, call
|
||||
algebra.versor.unitize_versor() first — that is the construction-time
|
||||
|
|
@ -63,16 +92,10 @@ class VocabManifold:
|
|||
that function is reserved for the injection gate.
|
||||
|
||||
Raises:
|
||||
ValueError: if the grade-norm condition is not satisfied.
|
||||
ValueError: if the full unit-versor residual is not satisfied.
|
||||
"""
|
||||
v = np.asarray(versor, dtype=np.float32).copy()
|
||||
grade_norm = float(geometric_product(v, reverse(v))[0])
|
||||
if not (0.95 <= abs(grade_norm) <= 1.05):
|
||||
raise ValueError(
|
||||
f"Word '{word}': versor grade-norm {grade_norm:.4f} ≠ ±1. "
|
||||
"Pass a valid Cl(4,1) versor. "
|
||||
"If lifting from a raw array, call algebra.versor.unitize_versor() first."
|
||||
)
|
||||
_assert_manifold_versor(word, v)
|
||||
self._words.append(word)
|
||||
self._versors.append(v)
|
||||
resolved_language = language or (morphology.language if morphology is not None else None)
|
||||
|
|
@ -134,24 +157,19 @@ class VocabManifold:
|
|||
|
||||
Used by the alignment correction pass after compilation to nudge
|
||||
cross-language aligned pairs toward each other without rebuilding
|
||||
the full manifold. The grade-norm invariant is enforced identically
|
||||
to add().
|
||||
the full manifold. The full unit-versor residual is enforced
|
||||
identically to add().
|
||||
|
||||
Raises:
|
||||
KeyError: if the word is not already in the manifold.
|
||||
ValueError: if the grade-norm condition is not satisfied.
|
||||
ValueError: if the full unit-versor residual is not satisfied.
|
||||
"""
|
||||
try:
|
||||
idx = self._words.index(word)
|
||||
except ValueError:
|
||||
raise KeyError(f"Word '{word}' not in vocabulary; use add() for new entries.")
|
||||
v = np.asarray(versor, dtype=np.float32).copy()
|
||||
grade_norm = float(geometric_product(v, reverse(v))[0])
|
||||
if not (0.95 <= abs(grade_norm) <= 1.05):
|
||||
raise ValueError(
|
||||
f"Word '{word}': replacement versor grade-norm {grade_norm:.4f} ≠ ±1. "
|
||||
"Call algebra.versor.unitize_versor() before update()."
|
||||
)
|
||||
_assert_manifold_versor(word, v, replacement=True)
|
||||
self._versors[idx] = v
|
||||
|
||||
def get_versor(self, word: str) -> np.ndarray:
|
||||
|
|
|
|||
Loading…
Reference in a new issue