Files
linear-coding-agent/ikario_processual/tests/test_state_tensor.py
David Blanc Brioir f6fe71e2f7 Add Ikario Architecture v2 - Phases 1-8 complete
Implements the processual architecture based on Whitehead's Process
Philosophy and Peirce's Semiotics. Core paradigm: "L'espace latent
pense. Le LLM traduit." (The latent space thinks. The LLM translates.)

Phase 1-4: Core semiotic cycle
- StateTensor 8x1024 (8 Peircean dimensions)
- Dissonance computation with hard negatives
- Fixation via 4 Peircean methods (Tenacity, Authority, A Priori, Science)
- LatentEngine orchestrating the full cycle

Phase 5: StateToLanguage
- LLM as pure translator (zero-reasoning, T=0)
- Projection on interpretable directions
- Reasoning markers detection (Amendment #4)

Phase 6: Vigilance
- x_ref (David) as guard-rail, NOT attractor
- Drift detection per dimension and globally
- Alerts: ok, warning, critical

Phase 7: Autonomous Daemon
- Two modes: CONVERSATION (always verbalize), AUTONOMOUS (~1000 cycles/day)
- Amendment #5: 50% probability on unresolved impacts
- TriggerGenerator with weighted random selection

Phase 8: Integration & Metrics
- ProcessMetrics for daily/weekly reports
- Health status monitoring
- Integration tests validating all modules

297 tests passing, version 0.7.0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 22:30:19 +01:00

298 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Tests pour StateTensor - Tenseur d'état 8×1024.
Exécuter: pytest ikario_processual/tests/test_state_tensor.py -v
"""
import numpy as np
import pytest
from datetime import datetime
# Import du module à tester
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from ikario_processual.state_tensor import (
StateTensor,
TensorDimension,
DIMENSION_NAMES,
EMBEDDING_DIM,
)
class TestStateTensorBasic:
"""Tests de base pour StateTensor."""
def test_create_empty_tensor(self):
"""Test création d'un tenseur vide."""
tensor = StateTensor(
state_id=0,
timestamp=datetime.now().isoformat(),
)
assert tensor.state_id == 0
assert tensor.firstness.shape == (EMBEDDING_DIM,)
assert tensor.valeurs.shape == (EMBEDDING_DIM,)
assert np.all(tensor.firstness == 0)
def test_create_with_values(self):
"""Test création avec valeurs."""
firstness = np.random.randn(EMBEDDING_DIM)
firstness = firstness / np.linalg.norm(firstness)
tensor = StateTensor(
state_id=1,
timestamp=datetime.now().isoformat(),
firstness=firstness,
)
assert np.allclose(tensor.firstness, firstness)
assert np.isclose(np.linalg.norm(tensor.firstness), 1.0)
def test_to_matrix(self):
"""Test conversion en matrice."""
tensor = StateTensor(
state_id=0,
timestamp=datetime.now().isoformat(),
)
matrix = tensor.to_matrix()
assert matrix.shape == (8, EMBEDDING_DIM)
def test_to_flat(self):
"""Test aplatissement."""
tensor = StateTensor(
state_id=0,
timestamp=datetime.now().isoformat(),
)
flat = tensor.to_flat()
assert flat.shape == (8 * EMBEDDING_DIM,)
assert flat.shape == (8192,)
def test_dimension_names(self):
"""Test que toutes les dimensions sont présentes."""
expected = [
"firstness", "secondness", "thirdness", "dispositions",
"orientations", "engagements", "pertinences", "valeurs"
]
assert DIMENSION_NAMES == expected
assert len(DIMENSION_NAMES) == 8
class TestStateTensorOperations:
"""Tests des opérations sur StateTensor."""
def test_copy(self):
"""Test copie profonde."""
original = StateTensor(
state_id=1,
timestamp=datetime.now().isoformat(),
firstness=np.random.randn(EMBEDDING_DIM),
)
copied = original.copy()
# Modifier l'original ne doit pas affecter la copie
original.firstness[0] = 999.0
assert copied.firstness[0] != 999.0
def test_set_dimension(self):
"""Test modification d'une dimension."""
tensor = StateTensor(
state_id=0,
timestamp=datetime.now().isoformat(),
)
new_vec = np.random.randn(EMBEDDING_DIM)
tensor.set_dimension(TensorDimension.VALEURS, new_vec)
# Doit être normalisé
assert np.isclose(np.linalg.norm(tensor.valeurs), 1.0)
def test_get_dimension(self):
"""Test récupération d'une dimension."""
tensor = StateTensor(
state_id=0,
timestamp=datetime.now().isoformat(),
)
vec = tensor.get_dimension(TensorDimension.FIRSTNESS)
assert vec.shape == (EMBEDDING_DIM,)
def test_to_dict(self):
"""Test conversion en dictionnaire."""
tensor = StateTensor(
state_id=5,
timestamp="2026-02-01T12:00:00",
trigger_type="user",
trigger_content="Hello",
)
d = tensor.to_dict()
assert d["state_id"] == 5
assert d["trigger_type"] == "user"
assert "firstness" not in d # Vecteurs pas dans properties
def test_get_vectors_dict(self):
"""Test récupération des vecteurs pour Weaviate."""
tensor = StateTensor(
state_id=0,
timestamp=datetime.now().isoformat(),
)
vectors = tensor.get_vectors_dict()
assert len(vectors) == 8
assert "firstness" in vectors
assert "valeurs" in vectors
assert len(vectors["firstness"]) == EMBEDDING_DIM
class TestStateTensorAggregation:
"""Tests des opérations d'agrégation."""
def test_weighted_mean_two_tensors(self):
"""Test moyenne pondérée de 2 tenseurs."""
t1 = StateTensor(
state_id=1,
timestamp=datetime.now().isoformat(),
)
t2 = StateTensor(
state_id=2,
timestamp=datetime.now().isoformat(),
)
# Initialiser avec des vecteurs aléatoires normalisés
for dim_name in DIMENSION_NAMES:
v1 = np.random.randn(EMBEDDING_DIM)
v1 = v1 / np.linalg.norm(v1)
setattr(t1, dim_name, v1)
v2 = np.random.randn(EMBEDDING_DIM)
v2 = v2 / np.linalg.norm(v2)
setattr(t2, dim_name, v2)
# Moyenne 50/50
result = StateTensor.weighted_mean([t1, t2], [0.5, 0.5])
# Résultat doit être normalisé
for dim_name in DIMENSION_NAMES:
vec = getattr(result, dim_name)
assert np.isclose(np.linalg.norm(vec), 1.0, atol=1e-5)
def test_blend(self):
"""Test blend 70/30."""
t1 = StateTensor(state_id=1, timestamp=datetime.now().isoformat())
t2 = StateTensor(state_id=2, timestamp=datetime.now().isoformat())
# Initialiser
for dim_name in DIMENSION_NAMES:
v1 = np.random.randn(EMBEDDING_DIM)
v1 = v1 / np.linalg.norm(v1)
setattr(t1, dim_name, v1)
v2 = np.random.randn(EMBEDDING_DIM)
v2 = v2 / np.linalg.norm(v2)
setattr(t2, dim_name, v2)
result = StateTensor.blend(t1, t2, alpha=0.7)
assert result is not None
assert result.state_id == -1 # Non défini
def test_from_matrix(self):
"""Test création depuis matrice."""
matrix = np.random.randn(8, EMBEDDING_DIM)
tensor = StateTensor.from_matrix(
matrix=matrix,
state_id=10,
timestamp="2026-02-01T12:00:00"
)
assert tensor.state_id == 10
assert np.allclose(tensor.firstness, matrix[0])
assert np.allclose(tensor.valeurs, matrix[7])
def test_from_matrix_wrong_shape(self):
"""Test erreur si matrice mauvaise forme."""
matrix = np.random.randn(4, EMBEDDING_DIM) # 4 au lieu de 8
with pytest.raises(ValueError):
StateTensor.from_matrix(matrix, state_id=0, timestamp="")
class TestStateTensorDistance:
"""Tests de calcul de distance entre tenseurs."""
def test_distance_to_self_is_zero(self):
"""Distance à soi-même = 0."""
tensor = StateTensor(state_id=0, timestamp=datetime.now().isoformat())
for dim_name in DIMENSION_NAMES:
v = np.random.randn(EMBEDDING_DIM)
v = v / np.linalg.norm(v)
setattr(tensor, dim_name, v)
flat = tensor.to_flat()
distance = np.linalg.norm(flat - flat)
assert distance == 0.0
def test_normalized_distance(self):
"""Test distance normalisée entre 2 tenseurs."""
t1 = StateTensor(state_id=1, timestamp=datetime.now().isoformat())
t2 = StateTensor(state_id=2, timestamp=datetime.now().isoformat())
for dim_name in DIMENSION_NAMES:
v1 = np.random.randn(EMBEDDING_DIM)
v1 = v1 / np.linalg.norm(v1)
setattr(t1, dim_name, v1)
v2 = np.random.randn(EMBEDDING_DIM)
v2 = v2 / np.linalg.norm(v2)
setattr(t2, dim_name, v2)
diff = t1.to_flat() - t2.to_flat()
distance = np.linalg.norm(diff) / np.linalg.norm(t2.to_flat())
# Distance normalisée doit être > 0 et finie
assert distance > 0
assert np.isfinite(distance)
class TestStateTensorSerialization:
"""Tests de sérialisation."""
def test_from_dict_roundtrip(self):
"""Test aller-retour dict."""
original = StateTensor(
state_id=42,
timestamp="2026-02-01T12:00:00",
previous_state_id=41,
trigger_type="user",
trigger_content="Test message",
embedding_model="BAAI/bge-m3",
)
# Simuler les vecteurs
vectors = {}
for dim_name in DIMENSION_NAMES:
v = np.random.randn(EMBEDDING_DIM)
v = v / np.linalg.norm(v)
setattr(original, dim_name, v)
vectors[dim_name] = v.tolist()
# Convertir et recréer
props = original.to_dict()
reconstructed = StateTensor.from_dict(props, vectors)
assert reconstructed.state_id == 42
assert reconstructed.trigger_type == "user"
assert np.allclose(reconstructed.firstness, original.firstness)
if __name__ == "__main__":
pytest.main([__file__, "-v"])