ATC_Nima_Model / tests /test_atc_pipeline.py
TheNormsOfIntelligence's picture
Restructure into nima_unified package + add model card
4e0c3ce verified
Raw
History Blame Contribute Delete
34.9 kB
"""
Comprehensive automated tests for the NIMA Unified Model's ATC pipeline.
Validates that the ATC cognitive pipeline modulates the model's output
differently from a bare forward pass. All tests use a mock base model
and never load the real Phi-4-mini weights.
"""
import pytest
import torch
import torch.nn as nn
from unittest.mock import MagicMock, patch
from nima_unified.core.neurotransmitter_shunt import (
NeurotransmitterShunt,
NeurotransmitterState,
IDX_NOREPINEPHRINE,
IDX_CORTISOL,
IDX_DOPAMINE,
IDX_ADENOSINE,
NOREPINEPHRINE_DECAY,
CORTISOL_DECAY,
DOPAMINE_DECAY,
ADENOSINE_DECAY,
FRICTION_CORTISOL_INJECT,
FRICTION_ADENOSINE_INJECT,
NOVELTY_NE_INJECT,
METACOGNITIVE_LOOP_CORTISOL_INJECT,
METACOGNITIVE_LOOP_ADENOSINE_INJECT,
)
from nima_unified.core.deep_surgery import (
OpaqueQualiaSignature,
TRNPredictiveGate,
DissolutionModule,
BELBICDualPathway,
MetacognitiveLoopModule,
IrrationalSparkModule,
EpisodicMemoryModule,
HippocampalReconsolidator,
EthicalGuardian,
ATCDeepSurgery,
)
# ── Helper: lightweight mock transformer layer ────────────────────────
class MockTransformerLayer(nn.Module):
"""Minimal transformer-style layer: Linear + residual, fast enough for
tests but with real gradient plumbing."""
def __init__(self, hidden_size: int):
super().__init__()
self.linear = nn.Linear(hidden_size, hidden_size)
def forward(self, hidden_states, attention_mask=None, **kwargs):
return (hidden_states + self.linear(hidden_states),)
def make_mock_base_model(hidden_size=256, num_layers=12, vocab_size=1000):
"""Build a mock model that satisfies the ATCDeepSurgery interface."""
model = MagicMock()
model.config = MagicMock()
model.config.hidden_size = hidden_size
model.config.vocab_size = vocab_size
layers = nn.ModuleList(
[MockTransformerLayer(hidden_size) for _ in range(num_layers)]
)
model.model = MagicMock()
model.model.layers = layers
model.get_input_embeddings.return_value = nn.Embedding(vocab_size, hidden_size)
model.lm_head = nn.Linear(hidden_size, vocab_size, bias=False)
return model, layers
# ══════════════════════════════════════════════════════════════════════
# TestNeurotransmitterShunt (~15 tests)
# ══════════════════════════════════════════════════════════════════════
class TestNeurotransmitterShunt:
def test_initial_state_is_zero(self):
shunt = NeurotransmitterShunt()
state = shunt.get_state()
assert state.norepinephrine == 0.0
assert state.cortisol == 0.0
assert state.dopamine == 0.0
assert state.adenosine == 0.0
def test_inject_friction_raises_cortisol_and_adenosine(self):
shunt = NeurotransmitterShunt()
shunt.inject_friction(1.0)
state = shunt.get_state()
assert state.cortisol == pytest.approx(FRICTION_CORTISOL_INJECT)
assert state.adenosine == pytest.approx(FRICTION_ADENOSINE_INJECT)
def test_inject_norepinephrine_caps_at_one(self):
shunt = NeurotransmitterShunt()
for _ in range(100):
shunt.inject_norepinephrine(NOVELTY_NE_INJECT)
state = shunt.get_state()
assert state.norepinephrine <= 1.0
assert state.norepinephrine == pytest.approx(1.0)
def test_inject_dopamine(self):
shunt = NeurotransmitterShunt()
shunt.inject_dopamine(0.5)
state = shunt.get_state()
assert state.dopamine == pytest.approx(0.5)
def test_decay_reduces_values(self):
shunt = NeurotransmitterShunt()
shunt.inject_norepinephrine(0.5)
shunt.inject_adenosine_via_friction = lambda: None # no-op
# Manually set adenosine so we can compare decay rates
shunt._vector[IDX_ADENOSINE] = 0.5
state_before = shunt.read_and_decay(dt=1.0)
state_after = shunt.read_and_decay(dt=1.0)
# Both should have decreased
assert state_after.norepinephrine < state_before.norepinephrine
assert state_after.adenosine < state_before.adenosine
# NE should decay faster (higher decay rate)
ne_ratio = state_after.norepinephrine / max(state_before.norepinephrine, 1e-9)
ad_ratio = state_after.adenosine / max(state_before.adenosine, 1e-9)
assert ne_ratio < ad_ratio
def test_decay_order_ne_fastest_adenosine_slowest(self):
"""Verify half-lives: NE: 3.0, Cortisol: 0.1, Dopamine: 0.8, Adenosine: 0.05"""
# Exponential decay: after 1 second, value *= exp(-rate * dt)
# Higher rate = faster decay
assert NOREPINEPHRINE_DECAY == 3.0
assert CORTISOL_DECAY == 0.1
assert DOPAMINE_DECAY == 0.8
assert ADENOSINE_DECAY == 0.05
# Ordering: NE (3.0) > Dopamine (0.8) > Cortisol (0.1) > Adenosine (0.05)
assert NOREPINEPHRINE_DECAY > DOPAMINE_DECAY
assert DOPAMINE_DECAY > CORTISOL_DECAY
assert CORTISOL_DECAY > ADENOSINE_DECAY
def test_adenosine_floor_from_metabolic_reserve(self):
shunt = NeurotransmitterShunt()
shunt.update_metabolic_reserve(0.2)
# adenosine_floor = (1.0 - metabolic_reserve) * 0.5 = 0.8 * 0.5 = 0.4
state = shunt.read_and_decay(dt=0.001)
assert state.adenosine == pytest.approx(0.4, abs=0.01)
def test_auto_hijack_at_threshold(self):
shunt = NeurotransmitterShunt()
# Bypass the inject cap and set adenosine directly past 0.95
shunt._vector[IDX_ADENOSINE] = 0.96
state = shunt.read_and_decay(dt=0.001)
assert state.hijack_active is True
assert "ADENOSINE" in state.hijack_reason
def test_hijack_clears_after_timeout(self):
shunt = NeurotransmitterShunt()
shunt._vector[IDX_ADENOSINE] = 0.96
state = shunt.read_and_decay(dt=0.001)
assert state.hijack_active is True
# Patch time so hijack appears to have fired > 0.1s ago
with patch("nima_unified.core.neurotransmitter_shunt.time.time") as mock_time:
mock_time.return_value = shunt._hijack_timestamp + 0.2
state2 = shunt.read_and_decay(dt=0.001)
assert state2.hijack_active is False
def test_suppression_trigger(self):
shunt = NeurotransmitterShunt()
result = shunt.suppress_metabolic_and_trigger_hijack("test_suppress")
assert result is True
state = shunt.get_state()
assert state.hijack_active is True
assert state.hijack_reason == "test_suppress"
assert state.norepinephrine > 0.0
def test_reset_clears_everything(self):
shunt = NeurotransmitterShunt()
shunt.inject_friction(1.0)
shunt.inject_norepinephrine(0.5)
shunt.inject_dopamine(0.5)
shunt.suppress_metabolic_and_trigger_hijack("reset_test")
shunt.reset()
state = shunt.get_state()
assert state.norepinephrine == 0.0
assert state.cortisol == 0.0
assert state.dopamine == 0.0
assert state.adenosine == 0.0
assert state.hijack_active is False
def test_power_spike_prediction_raises_cortisol(self):
shunt = NeurotransmitterShunt()
shunt.set_power_spike_predicted(True)
state = shunt.get_state()
assert state.power_spike_predicted is True
assert state.cortisol == pytest.approx(
FRICTION_CORTISOL_INJECT * 0.5
)
def test_get_state_returns_snapshot(self):
shunt = NeurotransmitterShunt()
shunt.inject_friction(1.0)
shunt.inject_norepinephrine(0.3)
shunt.inject_dopamine(0.2)
state = shunt.get_state()
assert isinstance(state, NeurotransmitterState)
assert state.norepinephrine == 0.3
assert state.cortisol == FRICTION_CORTISOL_INJECT
assert state.dopamine == 0.2
assert state.total_firings > 0
assert state.last_update_ts > 0.0
assert isinstance(state.hijack_active, bool)
assert isinstance(state.hijack_reason, str)
assert isinstance(state.metabolic_reserve, float)
assert isinstance(state.power_spike_predicted, bool)
def test_inject_metacognitive_strain(self):
shunt = NeurotransmitterShunt()
shunt.inject_metacognitive_strain(loop_iterations=3, loop_stress=0.5)
state = shunt.get_state()
assert state.cortisol == pytest.approx(
METACOGNITIVE_LOOP_CORTISOL_INJECT * 0.5, abs=1e-6
)
assert state.adenosine == pytest.approx(
METACOGNITIVE_LOOP_ADENOSINE_INJECT * 3, abs=1e-6
)
def test_history_recording(self):
shunt = NeurotransmitterShunt()
assert len(shunt._history) == 0
shunt.inject_friction(1.0)
# inject methods do NOT record history directly; only read_and_decay does
assert len(shunt._history) == 0
# read_and_decay records a "read" event
shunt.read_and_decay(dt=0.001)
assert len(shunt._history) == 1
assert shunt._history[-1]["event"] == "read"
# suppress_metabolic_and_trigger_hijack records a "suppression_hijack" event
shunt.suppress_metabolic_and_trigger_hijack("test")
assert len(shunt._history) == 2
assert shunt._history[-1]["event"] == "suppression_hijack"
# Each entry should have a timestamp
assert "ts" in shunt._history[0]
# ══════════════════════════════════════════════════════════════════════
# TestTRNPredictiveGate (~5 tests)
# ══════════════════════════════════════════════════════════════════════
class TestTRNPredictiveGate:
@pytest.fixture
def gate(self):
return TRNPredictiveGate(hidden_size=64)
@pytest.fixture
def hidden(self):
return torch.randn(2, 8, 64)
def test_high_confidence_gates_out(self, gate, hidden):
gate_in, confidence = gate(hidden, prediction_confidence=0.99)
assert gate_in is False
def test_low_confidence_gates_in(self, gate, hidden):
# External confidence of 0.0 blended with model confidence -> low
gate_in, confidence = gate(hidden, prediction_confidence=0.0)
assert gate_in is True
def test_output_shape(self, gate, hidden):
result = gate(hidden, prediction_confidence=0.5)
assert isinstance(result, tuple)
assert len(result) == 2
assert isinstance(result[0], bool)
assert isinstance(result[1], float)
def test_learnable_threshold(self, gate):
assert isinstance(gate.gate_threshold, nn.Parameter)
assert gate.gate_threshold.requires_grad
def test_confidence_blending(self, gate, hidden):
"""Model confidence and external confidence are blended 50/50."""
# With external confidence = 1.0, blended = 0.5 * model_conf + 0.5 * 1.0
# Even if model confidence is very low, the blend is at least 0.5
# which is above the default threshold (0.15) -> gate_in=False
gate_in, confidence = gate(hidden, prediction_confidence=1.0)
assert gate_in is False
assert confidence > 0.4 # 0.5 * model + 0.5 * 1.0 > 0.4
# ══════════════════════════════════════════════════════════════════════
# TestDissolutionModule (~6 tests)
# ══════════════════════════════════════════════════════════════════════
class TestDissolutionModule:
@pytest.fixture
def dissolution(self):
return DissolutionModule(hidden_size=64, qualia_dim=32)
@pytest.fixture
def hidden(self):
return torch.randn(2, 8, 64)
def test_gate_out_returns_none_qualia(self, dissolution, hidden):
qualia, offset, fired = dissolution(hidden, gate_in=False)
assert qualia is None
assert fired is False
assert offset.shape == hidden.shape
assert offset.abs().max() == 0.0
def test_gate_in_returns_qualia_signature(self, dissolution, hidden):
# Force alpha phase to always allow dissolution
dissolution.check_alpha_phase = MagicMock(return_value=True)
qualia, offset, fired = dissolution(hidden, gate_in=True)
assert qualia is not None
assert isinstance(qualia, OpaqueQualiaSignature)
assert fired is True
def test_dissolution_offset_nonzero_when_fired(self, dissolution, hidden):
dissolution.check_alpha_phase = MagicMock(return_value=True)
_, offset, fired = dissolution(hidden, gate_in=True)
assert fired is True
assert offset.abs().sum() > 0.0
def test_qualia_values_bounded(self, dissolution, hidden):
dissolution.check_alpha_phase = MagicMock(return_value=True)
qualia, _, _ = dissolution(hidden, gate_in=True)
# valence is raw from Tanh: [-1, 1]
assert -1.0 <= qualia.valence <= 1.0
# arousal, intensity, friction_signal, memory_salience are mapped to [0, 1]
assert 0.0 <= qualia.arousal <= 1.0
assert 0.0 <= qualia.intensity <= 1.0
assert 0.0 <= qualia.friction_signal <= 1.0
assert 0.0 <= qualia.memory_salience <= 1.0
def test_alpha_phase_deferral(self, dissolution, hidden):
dissolution.check_alpha_phase = MagicMock(return_value=False)
qualia, offset, fired = dissolution(hidden, gate_in=True)
assert qualia is None
assert fired is False
assert dissolution.dissolutions_deferred > 0
def test_dissolution_fired_counter(self, dissolution, hidden):
dissolution.check_alpha_phase = MagicMock(return_value=True)
assert dissolution.dissolutions_fired == 0
dissolution(hidden, gate_in=True)
assert dissolution.dissolutions_fired == 1
dissolution(hidden, gate_in=True)
assert dissolution.dissolutions_fired == 2
# ══════════════════════════════════════════════════════════════════════
# TestBELBICDualPathway (~5 tests)
# ══════════════════════════════════════════════════════════════════════
class TestBELBICDualPathway:
@pytest.fixture
def belbic(self):
return BELBICDualPathway(hidden_size=64)
@pytest.fixture
def sensory(self):
return torch.tensor([[0.5, 0.5, 0.5, 0.5]])
def test_initial_zero_output(self, belbic, sensory):
amygdala_out, ofc_out, gain = belbic(sensory)
# Weights initialized to zero β†’ sigmoid(0) = 0.5
assert amygdala_out == pytest.approx(0.5)
assert ofc_out == pytest.approx(0.5)
assert gain == pytest.approx(1.0, abs=0.05)
def test_gain_within_bounds(self, belbic):
"""gain is always in [GAIN_FLOOR, GAIN_CEIL] = [0.2, 2.0]."""
for _ in range(20):
s = torch.rand(1, 4)
_, _, gain = belbic(s)
assert BELBICDualPathway.GAIN_FLOOR <= gain <= BELBICDualPathway.GAIN_CEIL
def test_amygdala_strengthened_on_positive_reward(self, belbic, sensory):
old_weights = belbic.amygdala.weight.data.clone()
belbic.update(sensory, reward=1.0)
# Amygdala weights should have changed (increased in magnitude)
diff = (belbic.amygdala.weight.data - old_weights).abs().sum().item()
assert diff > 0.0
def test_ofc_strengthened_on_negative_reward(self, belbic, sensory):
old_weights = belbic.ofc.weight.data.clone()
belbic.update(sensory, reward=-1.0)
# OFC weights should have changed (strengthened inhibition)
diff = (belbic.ofc.weight.data - old_weights).abs().sum().item()
assert diff > 0.0
def test_output_tuple_shape(self, belbic, sensory):
result = belbic(sensory)
assert isinstance(result, tuple)
assert len(result) == 3
assert all(isinstance(v, float) for v in result)
# ══════════════════════════════════════════════════════════════════════
# TestMetacognitiveLoopModule (~5 tests)
# ══════════════════════════════════════════════════════════════════════
class TestMetacognitiveLoopModule:
@pytest.fixture
def metacog(self):
return MetacognitiveLoopModule(hidden_size=64)
@pytest.fixture
def hidden(self):
return torch.randn(2, 8, 64)
def test_high_comprehension_no_loop(self, metacog, hidden):
"""When comprehension > 0.7, iterations = 0."""
# Patch comprehension_head to always return high value
metacog.comprehension_head = nn.Sequential(
nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, 1), nn.Sigmoid()
)
# Force high comprehension by making the last layer output large positive
with torch.no_grad():
metacog.comprehension_head[-2].weight.fill_(0.0)
metacog.comprehension_head[-2].bias.fill_(10.0) # sigmoid(10) β‰ˆ 1.0
_, iters, stress, strain, spark = metacog(hidden)
assert iters == 0
def test_low_comprehension_enters_loop(self, metacog, hidden):
"""When comprehension < 0.7, iterations > 0."""
# Force low comprehension
with torch.no_grad():
metacog.comprehension_head[-2].weight.fill_(0.0)
metacog.comprehension_head[-2].bias.fill_(-10.0) # sigmoid(-10) β‰ˆ 0.0
_, iters, stress, strain, spark = metacog(hidden)
assert iters > 0
def test_deadlock_triggers_spark(self, metacog, hidden):
"""When stress > 0.6 and iterations > 3, spark_fired = True."""
# Force low comprehension so loop runs, and high strain
with torch.no_grad():
metacog.comprehension_head[-2].weight.fill_(0.0)
metacog.comprehension_head[-2].bias.fill_(-10.0) # low comprehension
metacog.strain_head[-2].weight.fill_(0.0)
metacog.strain_head[-2].bias.fill_(10.0) # sigmoid(10) β‰ˆ 1.0 (high strain)
_, iters, stress, strain, spark = metacog(hidden)
# With strain=1.0, stress = strain * (iters / MAX_ITERATIONS)
# At iteration 4: stress = 1.0 * (4/5) = 0.8 > 0.6 and iters > 3
if iters > 3:
assert spark is True
else:
# If the loop breaks early (shouldn't with low comprehension), still ok
assert iters >= 0
def test_strain_always_returned(self, metacog, hidden):
"""strain is always a float in [0, 1]."""
_, _, _, strain, _ = metacog(hidden)
assert isinstance(strain, float)
assert 0.0 <= strain <= 1.0
def test_max_iterations_limit(self, metacog, hidden):
"""Never exceeds MAX_ITERATIONS = 5."""
with torch.no_grad():
metacog.comprehension_head[-2].weight.fill_(0.0)
metacog.comprehension_head[-2].bias.fill_(-10.0)
_, iters, _, _, _ = metacog(hidden)
assert 0 <= iters <= MetacognitiveLoopModule.MAX_ITERATIONS
# ══════════════════════════════════════════════════════════════════════
# TestIrrationalSparkModule (~5 tests)
# ══════════════════════════════════════════════════════════════════════
class TestIrrationalSparkModule:
@pytest.fixture
def spark(self):
return IrrationalSparkModule(hidden_size=64, vocab_size=100)
@pytest.fixture
def hidden(self):
torch.manual_seed(42)
return torch.randn(2, 8, 64)
@pytest.fixture
def nominal_nt_state(self):
return NeurotransmitterState(
norepinephrine=0.1,
cortisol=0.2,
dopamine=0.1,
adenosine=0.1,
hijack_active=False,
)
def test_no_hijack_when_nominal(self, spark, hidden, nominal_nt_state):
modulated, hijack_fired, reason = spark(hidden, None, nominal_nt_state)
assert hijack_fired is False
assert reason == ""
assert torch.equal(modulated, hidden)
def test_hijack_on_adenosine_critical(self, spark, hidden):
nt = NeurotransmitterState(adenosine=0.96, hijack_active=False)
modulated, hijack_fired, reason = spark(hidden, None, nt)
assert hijack_fired is True
assert "ADENOSINE" in reason
def test_hijack_on_cortisol_critical(self, spark, hidden):
nt = NeurotransmitterState(cortisol=0.97, hijack_active=False)
modulated, hijack_fired, reason = spark(hidden, None, nt)
assert hijack_fired is True
assert "CORTISOL" in reason
def test_hijack_on_qualia_crisis(self, spark, hidden, nominal_nt_state):
qualia = OpaqueQualiaSignature(
valence=0.0, arousal=0.9, intensity=0.8,
friction_signal=0.9, memory_salience=0.5,
)
modulated, hijack_fired, reason = spark(hidden, qualia, nominal_nt_state)
assert hijack_fired is True
assert "QUALIA_CRISE" in reason
def test_hidden_states_modified_on_hijack(self, spark, hidden):
nt = NeurotransmitterState(adenosine=0.96, hijack_active=False)
modulated, hijack_fired, _ = spark(hidden, None, nt)
assert hijack_fired is True
assert not torch.equal(modulated, hidden)
# The difference should be nonzero
assert (modulated - hidden).abs().sum() > 0.0
# ══════════════════════════════════════════════════════════════════════
# TestEpisodicMemoryModule (~5 tests)
# ══════════════════════════════════════════════════════════════════════
class TestEpisodicMemoryModule:
@pytest.fixture
def memory(self):
return EpisodicMemoryModule(hidden_size=64, max_episodes=10, embedding_dim=32)
@pytest.fixture
def hidden(self):
torch.manual_seed(99)
return torch.randn(1, 4, 64)
def test_empty_memory_returns_zero_prediction_error(self, memory, hidden):
_, pe, best_match = memory(hidden)
assert pe == 0.0
assert best_match is None
def test_store_and_retrieve(self, memory, hidden):
# Store an episode
qualia = OpaqueQualiaSignature(valence=0.5, arousal=0.5, intensity=0.5,
friction_signal=0.3, memory_salience=0.4)
memory.store_episode(hidden, qualia_signature=qualia)
assert memory.episode_count == 1
# Query with similar hidden states β†’ should match
retrieval_signal, pe, best_match = memory(hidden)
assert best_match is not None
assert pe < 1.0
assert "similarity" in best_match
def test_prediction_error_high_for_different_inputs(self, memory, hidden):
# Store with one hidden state
memory.store_episode(hidden)
# Query with very different hidden state
different_hidden = torch.randn(1, 4, 64) * 10.0
_, pe, _ = memory(different_hidden)
# Very different input should yield high prediction error
assert pe > 0.5
def test_episode_count_increments(self, memory, hidden):
assert memory.episode_count == 0
memory.store_episode(hidden)
assert memory.episode_count == 1
memory.store_episode(hidden)
assert memory.episode_count == 2
memory.store_episode(hidden)
assert memory.episode_count == 3
def test_wraps_at_max_episodes(self, memory, hidden):
max_ep = memory.max_episodes # 10
for i in range(max_ep + 5):
memory.store_episode(hidden)
assert memory.episode_count == max_ep + 5
# Verify the embedding at slot 0 was overwritten
slot = 0
# Store once more to fill slot 0, then check
memory.store_episode(hidden)
# Count should keep incrementing; slot wraps via modulo
assert memory.episode_count == max_ep + 6
# Slot for the next store should wrap
assert (memory.episode_count) % max_ep != memory.episode_count
# ══════════════════════════════════════════════════════════════════════
# TestHippocampalReconsolidator (~4 tests)
# ══════════════════════════════════════════════════════════════════════
class TestHippocampalReconsolidator:
@pytest.fixture
def recon(self):
return HippocampalReconsolidator()
@pytest.fixture
def hidden(self):
return torch.randn(1, 4, 64)
def test_no_reconsolidation_below_threshold(self, recon, hidden):
result = recon(
hidden,
episode_valence=0.5,
episode_arousal=0.3,
prediction_error=0.2,
current_valence=-0.5,
current_arousal=0.9,
)
reconsolidated, new_v, new_a, reason = result
assert reconsolidated is False
assert "below" in reason
def test_reconsolidation_above_threshold(self, recon, hidden):
result = recon(
hidden,
episode_valence=0.5,
episode_arousal=0.3,
prediction_error=0.8,
current_valence=-0.5,
current_arousal=0.9,
)
reconsolidated, new_v, new_a, reason = result
assert reconsolidated is True
assert isinstance(new_v, float)
assert isinstance(new_a, float)
assert isinstance(reason, str)
assert "reconsolidated" in reason
def test_valence_shifts_toward_current(self, recon, hidden):
"""Reconsolidated valence is a blend of old and new, clamped to [-1, 1]."""
torch.manual_seed(7)
episode_valence = 0.8
current_valence = -0.8
pred_error = 0.9
_, new_v, _, _ = recon(
hidden,
episode_valence=episode_valence,
episode_arousal=0.3,
prediction_error=pred_error,
current_valence=current_valence,
current_arousal=0.5,
)
# new = old * 0.7 + projection * 0.3 + noise, clamped to [-1, 1]
assert isinstance(new_v, float)
assert -1.0 <= new_v <= 1.0
# The 70/30 blend with projection and noise means the value
# should differ from the pure episode_valence (noise alone
# almost guarantees this, and the projection is nonzero)
assert new_v != pytest.approx(episode_valence, abs=0.05)
def test_reconsolidation_count_increments(self, recon, hidden):
assert recon.reconsolidation_count == 0
recon(
hidden,
episode_valence=0.5,
episode_arousal=0.3,
prediction_error=0.8,
current_valence=-0.5,
current_arousal=0.9,
)
assert recon.reconsolidation_count == 1
recon(
hidden,
episode_valence=0.1,
episode_arousal=0.2,
prediction_error=0.9,
current_valence=0.8,
current_arousal=0.7,
)
assert recon.reconsolidation_count == 2
# ══════════════════════════════════════════════════════════════════════
# TestATCDeepSurgeryIntegration (~6 tests)
# ══════════════════════════════════════════════════════════════════════
class TestATCDeepSurgeryIntegration:
@pytest.fixture
def setup(self):
"""Create a fully wired ATC deep surgery with mock base model."""
torch.manual_seed(123)
hidden_size = 256
num_layers = 12
vocab_size = 1000
mock_model, real_layers = make_mock_base_model(
hidden_size=hidden_size, num_layers=num_layers, vocab_size=vocab_size
)
# Replace the mock layers list with the real nn.ModuleList
mock_model.model.layers = real_layers
mock_model.get_input_embeddings.return_value = nn.Embedding(vocab_size, hidden_size)
mock_model.lm_head = nn.Linear(hidden_size, vocab_size, bias=False)
shunt = NeurotransmitterShunt()
guardian = EthicalGuardian(threshold=100.0) # High threshold to avoid vetoes
surgery = ATCDeepSurgery(
base_model=mock_model,
ethical_guardian=guardian,
num_layers=num_layers,
qualia_dim=64,
neurotransmitter_shunt=shunt,
)
return surgery, shunt, hidden_size, vocab_size
def test_forward_returns_modulated_logits(self, setup):
surgery, shunt, hidden_size, vocab_size = setup
batch_size, seq_len = 2, 8
input_ids = torch.randint(0, vocab_size, (batch_size, seq_len))
logits = surgery(input_ids)
assert logits.shape == (batch_size, seq_len, vocab_size)
assert logits.dtype == torch.float32
def test_atc_modulation_differs_from_bare_model(self, setup):
"""ATC modulated logits should differ from bare model logits."""
surgery, shunt, hidden_size, vocab_size = setup
batch_size, seq_len = 2, 8
input_ids = torch.randint(0, vocab_size, (batch_size, seq_len))
# ── ATC forward ──
with torch.no_grad():
atc_logits = surgery(input_ids)
# ── Bare model forward (same layers + lm_head, no ATC) ──
embeddings = surgery.base_model.get_input_embeddings()(input_ids)
h = embeddings
for layer in surgery._get_transformer_layers():
h = layer(h)[0]
bare_logits = surgery.base_model.lm_head(h)
# They should NOT be identical
assert not torch.allclose(atc_logits, bare_logits, atol=0.0)
def test_neurotransmitter_shunt_receives_signals(self, setup):
"""After forward, shunt.total_firings > 0."""
surgery, shunt, hidden_size, vocab_size = setup
input_ids = torch.randint(0, vocab_size, (1, 8))
with torch.no_grad():
surgery(input_ids)
state = shunt.get_state()
assert state.total_firings > 0
def test_consciousness_metrics_populated(self, setup):
"""get_consciousness_metrics returns expected keys."""
surgery, shunt, hidden_size, vocab_size = setup
input_ids = torch.randint(0, vocab_size, (1, 8))
with torch.no_grad():
surgery(input_ids)
metrics = surgery.get_consciousness_metrics()
expected_keys = {
"hijack_count", "has_qualia", "belbic_gain",
"dissolutions_fired", "dissolutions_deferred",
"ethical_veto", "episodes_stored",
"reconsolidations", "last_prediction_error",
}
for key in expected_keys:
assert key in metrics, f"Missing key: {key}"
def test_ethical_veto_raises_runtime_error(self, setup):
"""A qualia vector with very high norm triggers RuntimeError."""
# Build a surgery with a very low ethical threshold
torch.manual_seed(456)
hidden_size = 256
num_layers = 12
vocab_size = 1000
mock_model, real_layers = make_mock_base_model(
hidden_size=hidden_size, num_layers=num_layers, vocab_size=vocab_size
)
mock_model.model.layers = real_layers
mock_model.get_input_embeddings.return_value = nn.Embedding(vocab_size, hidden_size)
mock_model.lm_head = nn.Linear(hidden_size, vocab_size, bias=False)
shunt = NeurotransmitterShunt()
# Very low threshold β€” the tanh-bounded qualia vector norm is at most ~sqrt(5)
# (5 dims, each in [-1,1]), so threshold=0.1 should trigger veto if
# the dissolution output is nonzero.
# Actually, qualia.to_tensor gives [valence, arousal, intensity, friction, memory_salience]
# where arousal/intensity/friction/memory_salience are in [0,1]. The norm of [0,0,0,0,0]
# is 0. We need the dissolution to fire and produce a nonzero qualia.
# The dissolution encoder produces values in [-1,1] for valence and
# [0,1] for the mapped fields. The qualia tensor norm is bounded.
# Use threshold=0.0 to guarantee veto.
guardian = EthicalGuardian(threshold=0.0)
surgery = ATCDeepSurgery(
base_model=mock_model,
ethical_guardian=guardian,
num_layers=num_layers,
qualia_dim=64,
neurotransmitter_shunt=shunt,
)
input_ids = torch.randint(0, vocab_size, (1, 8))
with pytest.raises(RuntimeError, match="Ethical veto"):
with torch.no_grad():
surgery(input_ids)
def test_hijack_count_tracking(self, setup):
"""When conditions trigger hijack, _hijack_count > 0."""
surgery, shunt, hidden_size, vocab_size = setup
input_ids = torch.randint(0, vocab_size, (1, 8))
# Manually push adenosine past the critical threshold so the
# irrational spark fires in Layer 5
shunt._vector[IDX_ADENOSINE] = 0.96
with torch.no_grad():
surgery(input_ids)
assert surgery._hijack_count > 0