core/tests/test_energy.py
Shay 15ed2cee89
Tighten hot-path backend consistency
- route SessionContext anchor CGA through algebra.backend
- move aspect-weight carry into FieldEnergyOperator.compute
- remove duplicated propagate_step threshold patch and per-step imports
- add carry_aspect_weight tests for parity, fallback, and propagation preservation
- preserve normalization, propagation, vault, Rust dispatch, and energy cadence semantics
2026-05-15 08:14:38 -07:00

408 lines
15 KiB
Python
Raw Permalink 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.

"""
ADR-0006 — Field Energy Operator tests.
Covers:
- EnergyClass enum properties (vault_candidate, governance_critical)
- aspect_weight() lookup table (Hebrew and Greek aspect forms)
- FieldEnergyOperator.compute() — all four input axes
- Class boundary thresholds (E0E4)
- Anchor-adjacent E4 escalation
- EnergyProfile.requires_architect_review
- propagate_step() energy recomputation
- Aspect weight preservation across propagation steps
"""
import numpy as np
import pytest
from core.physics.energy import (
EnergyClass,
EnergyProfile,
FieldEnergyOperator,
aspect_weight,
)
from field.state import FieldState
from field.propagate import propagate_step
from algebra.versor import unitize_versor
from algebra.rotor import make_rotor_from_angle
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _clean_versor() -> np.ndarray:
"""Return a Cl(4,1) unit versor suitable for FieldState.F."""
v = np.zeros(32, dtype=np.float64)
v[0] = 1.0
return unitize_versor(v)
def _identity_rotor() -> np.ndarray:
v = np.zeros(32, dtype=np.float64)
v[0] = 1.0
return v
_op = FieldEnergyOperator()
# ---------------------------------------------------------------------------
# EnergyClass properties
# ---------------------------------------------------------------------------
class TestEnergyClassProperties:
def test_e0_is_vault_candidate(self):
assert EnergyClass.E0.vault_candidate is True
def test_e1_is_vault_candidate(self):
assert EnergyClass.E1.vault_candidate is True
def test_e2_is_not_vault_candidate(self):
assert EnergyClass.E2.vault_candidate is False
def test_e3_is_not_vault_candidate(self):
assert EnergyClass.E3.vault_candidate is False
def test_e4_is_governance_critical(self):
assert EnergyClass.E4.governance_critical is True
def test_e3_is_not_governance_critical(self):
assert EnergyClass.E3.governance_critical is False
# ---------------------------------------------------------------------------
# aspect_weight lookup
# ---------------------------------------------------------------------------
class TestAspectWeight:
def test_none_features_returns_zero(self):
assert aspect_weight(None) == 0.0
def test_empty_features_returns_zero(self):
assert aspect_weight({}) == 0.0
def test_qatal_is_low(self):
w = aspect_weight({"aspect": "qatal"})
assert w == pytest.approx(0.15)
def test_aorist_is_low(self):
w = aspect_weight({"tense": "aorist"})
assert w == pytest.approx(0.15)
def test_imperative_is_highest(self):
w = aspect_weight({"mood": "imperative"})
assert w == pytest.approx(0.90)
def test_yiqtol_is_high(self):
w = aspect_weight({"aspect": "yiqtol"})
assert w == pytest.approx(0.65)
def test_wayyiqtol_is_mid(self):
w = aspect_weight({"aspect": "wayyiqtol"})
assert w == pytest.approx(0.45)
def test_unknown_aspect_returns_zero(self):
assert aspect_weight({"aspect": "unknown_form"}) == 0.0
def test_case_insensitive(self):
assert aspect_weight({"aspect": "IMPERATIVE"}) == pytest.approx(0.90)
def test_max_of_multiple_features(self):
# qatal + imperative: max should be imperative
w = aspect_weight({"aspect": "qatal", "mood": "imperative"})
assert w == pytest.approx(0.90)
# ---------------------------------------------------------------------------
# FieldEnergyOperator — class boundary thresholds
# ---------------------------------------------------------------------------
class TestFieldEnergyOperatorThresholds:
"""Drive raw score into each class by controlling inputs."""
def test_all_zero_inputs_gives_e0(self):
ep = _op.compute()
assert ep.energy_class is EnergyClass.E0
assert ep.raw < 0.16
def test_low_convergence_no_activation_gives_e1(self):
# convergence_density=2, no recency, no residual, no aspect
# convergence contribution: 0.35 * log1p(2)/log1p(8) ≈ 0.35 * 0.404 ≈ 0.141
ep = _op.compute(convergence_density=2)
assert ep.energy_class in {EnergyClass.E0, EnergyClass.E1}
def test_moderate_inputs_gives_e2(self):
# convergence=4 -> ~0.30 contrib; activation=4/8*1=0.5 -> 0.125 contrib
ep = _op.compute(
convergence_density=4,
activation_count=4,
current_cycle=5,
last_activation_cycle=4,
)
assert ep.energy_class is EnergyClass.E2
def test_high_convergence_and_activation_gives_e3(self):
ep = _op.compute(
convergence_density=8,
activation_count=8,
current_cycle=1,
last_activation_cycle=0,
coherence_residual=0.5,
)
assert ep.energy_class is EnergyClass.E3
def test_imperative_aspect_and_full_convergence_gives_e4(self):
ep = _op.compute(
convergence_density=8,
activation_count=8,
current_cycle=1,
last_activation_cycle=0,
coherence_residual=1.0,
morphology_features={"mood": "imperative"},
)
assert ep.energy_class is EnergyClass.E4
def test_e4_raw_boundary(self):
# raw >= 0.82 without anchor_adjacent should be E4
# Use max inputs to guarantee raw >= 0.82
ep = _op.compute(
convergence_density=8,
activation_count=8,
current_cycle=0,
last_activation_cycle=0,
coherence_residual=1.0,
morphology_features={"mood": "imperative"},
)
assert ep.energy_class is EnergyClass.E4
assert ep.raw >= 0.82
# ---------------------------------------------------------------------------
# Anchor-adjacent escalation
# ---------------------------------------------------------------------------
class TestAnchorAdjacentEscalation:
def test_anchor_adjacent_escalates_to_e4_at_lower_raw(self):
# Without anchor: raw ~0.72 might be E3
ep_no_anchor = _op.compute(
convergence_density=8,
activation_count=6,
current_cycle=1,
last_activation_cycle=0,
coherence_residual=0.3,
anchor_adjacent=False,
)
ep_anchor = _op.compute(
convergence_density=8,
activation_count=6,
current_cycle=1,
last_activation_cycle=0,
coherence_residual=0.3,
anchor_adjacent=True,
)
# anchor_adjacent path escalates at raw >= 0.72 instead of >= 0.82
if ep_anchor.raw >= 0.72:
assert ep_anchor.energy_class is EnergyClass.E4
# Without anchor and same raw, must be lower class
if ep_no_anchor.raw < 0.82:
assert ep_no_anchor.energy_class is not EnergyClass.E4
def test_anchor_adjacent_stored_on_profile(self):
ep = _op.compute(anchor_adjacent=True)
assert ep.anchor_adjacent is True
# ---------------------------------------------------------------------------
# EnergyProfile.requires_architect_review
# ---------------------------------------------------------------------------
class TestRequiresArchitectReview:
def test_e4_always_requires_review(self):
ep = _op.compute(
convergence_density=8,
activation_count=8,
current_cycle=0,
last_activation_cycle=0,
coherence_residual=1.0,
morphology_features={"mood": "imperative"},
)
assert ep.energy_class is EnergyClass.E4
assert ep.requires_architect_review is True
def test_e3_anchor_adjacent_requires_review(self):
# Force E3 but with anchor_adjacent=True
# E3: raw in [0.62, 0.82). Build that range.
ep = _op.compute(
convergence_density=8,
activation_count=8,
current_cycle=1,
last_activation_cycle=0,
coherence_residual=0.2,
anchor_adjacent=True,
)
# If raw landed in E3 range and anchor_adjacent, review required
if ep.energy_class is EnergyClass.E3:
assert ep.requires_architect_review is True
def test_e2_does_not_require_review(self):
ep = _op.compute(
convergence_density=4,
activation_count=4,
current_cycle=5,
last_activation_cycle=4,
)
if ep.energy_class is EnergyClass.E2:
assert ep.requires_architect_review is False
# ---------------------------------------------------------------------------
# propagate_step energy recomputation
# ---------------------------------------------------------------------------
class TestPropagateStepEnergyRecomputation:
def _make_state_with_energy(self, energy: EnergyProfile | None = None) -> FieldState:
F = _clean_versor()
return FieldState(F=F, node=0, step=0, energy=energy)
def _rotor(self) -> np.ndarray:
return _identity_rotor()
def test_no_energy_propagates_none(self):
state = self._make_state_with_energy(None)
new_state = propagate_step(state, self._rotor())
assert new_state.energy is None
def test_step_increments(self):
ep = _op.compute(convergence_density=2, activation_count=2, current_cycle=0)
state = self._make_state_with_energy(ep)
new_state = propagate_step(state, self._rotor())
assert new_state.step == 1
def test_energy_is_recomputed_not_carried_verbatim(self):
"""After propagation the EnergyProfile object must be a new instance."""
ep = _op.compute(convergence_density=4, activation_count=3, current_cycle=0)
state = self._make_state_with_energy(ep)
new_state = propagate_step(state, self._rotor())
assert new_state.energy is not ep
def test_activation_count_increments(self):
ep = _op.compute(convergence_density=4, activation_count=3, current_cycle=0)
state = self._make_state_with_energy(ep)
new_state = propagate_step(state, self._rotor())
assert new_state.energy.activation_count == ep.activation_count + 1
def test_convergence_density_preserved(self):
ep = _op.compute(convergence_density=6, activation_count=2, current_cycle=0)
state = self._make_state_with_energy(ep)
new_state = propagate_step(state, self._rotor())
assert new_state.energy.convergence_density == 6
def test_anchor_adjacent_preserved(self):
ep = _op.compute(convergence_density=3, anchor_adjacent=True)
state = self._make_state_with_energy(ep)
new_state = propagate_step(state, self._rotor())
assert new_state.energy.anchor_adjacent is True
def test_aspect_weight_preserved_across_step(self):
"""Aspect weight baked at injection must survive propagation."""
ep = _op.compute(
convergence_density=4,
activation_count=2,
current_cycle=0,
morphology_features={"mood": "imperative"},
)
assert ep.aspect_weight == pytest.approx(0.90)
state = self._make_state_with_energy(ep)
new_state = propagate_step(state, self._rotor())
assert new_state.energy.aspect_weight == pytest.approx(0.90)
def test_coherence_residual_reset_to_zero_on_propagation(self):
"""Propagation is not a corrective pass; residual must be zero."""
ep = _op.compute(
convergence_density=4,
activation_count=2,
coherence_residual=0.8,
)
state = self._make_state_with_energy(ep)
new_state = propagate_step(state, self._rotor())
assert new_state.energy.coherence_residual == pytest.approx(0.0)
def test_multiple_steps_monotonically_age(self):
"""Repeated propagation cools energy as recency decays."""
ep = _op.compute(
convergence_density=4,
activation_count=4,
current_cycle=0,
last_activation_cycle=0,
)
state = self._make_state_with_energy(ep)
# 20 steps of propagation — recency term exp(-age/12) decays
for _ in range(20):
state = propagate_step(state, _identity_rotor())
# After 20 cold steps, energy class should not be E4
assert state.energy.energy_class is not EnergyClass.E4
# ---------------------------------------------------------------------------
# carry_aspect_weight consolidation
# ---------------------------------------------------------------------------
class TestCarryAspectWeight:
def test_carry_aspect_matches_morphology_derived(self):
"""carry_aspect_weight produces identical raw/class as morphology-derived aspect."""
base_kw = dict(
convergence_density=4,
activation_count=4,
current_cycle=1,
last_activation_cycle=0,
coherence_residual=0.0,
anchor_adjacent=False,
)
from_morph = _op.compute(**base_kw, morphology_features={"mood": "imperative"})
from_carry = _op.compute(**base_kw, carry_aspect_weight=0.90)
assert from_carry.raw == pytest.approx(from_morph.raw)
assert from_carry.energy_class is from_morph.energy_class
assert from_carry.aspect_weight == pytest.approx(from_morph.aspect_weight)
def test_carry_zero_falls_back_to_morphology(self):
ep = _op.compute(
morphology_features={"aspect": "yiqtol"},
carry_aspect_weight=0.0,
)
assert ep.aspect_weight == pytest.approx(0.65)
def test_propagate_step_preserves_baked_aspect_weight(self):
"""Aspect weight injected at the gate survives propagation via carry."""
ep = _op.compute(
convergence_density=4,
activation_count=2,
current_cycle=0,
morphology_features={"mood": "imperative"},
)
state = FieldState(F=_clean_versor(), node=0, step=0, energy=ep)
new_state = propagate_step(state, _identity_rotor())
assert new_state.energy.aspect_weight == pytest.approx(0.90)
assert new_state.energy.raw > 0.0
# ---------------------------------------------------------------------------
# EnergyProfile field storage round-trip on FieldState
# ---------------------------------------------------------------------------
class TestEnergyProfileRoundTrip:
def test_field_state_carries_energy_profile(self):
ep = _op.compute(convergence_density=3, activation_count=2)
F = _clean_versor()
state = FieldState(F=F, node=0, step=0, energy=ep)
assert state.energy is ep
assert state.energy.energy_class in list(EnergyClass)
def test_field_state_advance_preserves_energy(self):
ep = _op.compute(convergence_density=3)
F = _clean_versor()
state = FieldState(F=F, node=0, step=0, energy=ep)
new_F = _clean_versor()
advanced = state.advance(new_F, new_node=1)
assert advanced.energy is ep
assert advanced.step == 1