TheNormsOfIntelligence commited on
Commit
4e0c3ce
Β·
verified Β·
1 Parent(s): c34b473

Restructure into nima_unified package + add model card

Browse files
Files changed (1) hide show
  1. tests/test_atc_pipeline.py +829 -0
tests/test_atc_pipeline.py ADDED
@@ -0,0 +1,829 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Comprehensive automated tests for the NIMA Unified Model's ATC pipeline.
3
+
4
+ Validates that the ATC cognitive pipeline modulates the model's output
5
+ differently from a bare forward pass. All tests use a mock base model
6
+ and never load the real Phi-4-mini weights.
7
+ """
8
+
9
+ import pytest
10
+ import torch
11
+ import torch.nn as nn
12
+ from unittest.mock import MagicMock, patch
13
+
14
+ from nima_unified.core.neurotransmitter_shunt import (
15
+ NeurotransmitterShunt,
16
+ NeurotransmitterState,
17
+ IDX_NOREPINEPHRINE,
18
+ IDX_CORTISOL,
19
+ IDX_DOPAMINE,
20
+ IDX_ADENOSINE,
21
+ NOREPINEPHRINE_DECAY,
22
+ CORTISOL_DECAY,
23
+ DOPAMINE_DECAY,
24
+ ADENOSINE_DECAY,
25
+ FRICTION_CORTISOL_INJECT,
26
+ FRICTION_ADENOSINE_INJECT,
27
+ NOVELTY_NE_INJECT,
28
+ METACOGNITIVE_LOOP_CORTISOL_INJECT,
29
+ METACOGNITIVE_LOOP_ADENOSINE_INJECT,
30
+ )
31
+ from nima_unified.core.deep_surgery import (
32
+ OpaqueQualiaSignature,
33
+ TRNPredictiveGate,
34
+ DissolutionModule,
35
+ BELBICDualPathway,
36
+ MetacognitiveLoopModule,
37
+ IrrationalSparkModule,
38
+ EpisodicMemoryModule,
39
+ HippocampalReconsolidator,
40
+ EthicalGuardian,
41
+ ATCDeepSurgery,
42
+ )
43
+
44
+
45
+ # ── Helper: lightweight mock transformer layer ────────────────────────
46
+
47
+ class MockTransformerLayer(nn.Module):
48
+ """Minimal transformer-style layer: Linear + residual, fast enough for
49
+ tests but with real gradient plumbing."""
50
+
51
+ def __init__(self, hidden_size: int):
52
+ super().__init__()
53
+ self.linear = nn.Linear(hidden_size, hidden_size)
54
+
55
+ def forward(self, hidden_states, attention_mask=None, **kwargs):
56
+ return (hidden_states + self.linear(hidden_states),)
57
+
58
+
59
+ def make_mock_base_model(hidden_size=256, num_layers=12, vocab_size=1000):
60
+ """Build a mock model that satisfies the ATCDeepSurgery interface."""
61
+ model = MagicMock()
62
+ model.config = MagicMock()
63
+ model.config.hidden_size = hidden_size
64
+ model.config.vocab_size = vocab_size
65
+
66
+ layers = nn.ModuleList(
67
+ [MockTransformerLayer(hidden_size) for _ in range(num_layers)]
68
+ )
69
+ model.model = MagicMock()
70
+ model.model.layers = layers
71
+ model.get_input_embeddings.return_value = nn.Embedding(vocab_size, hidden_size)
72
+ model.lm_head = nn.Linear(hidden_size, vocab_size, bias=False)
73
+
74
+ return model, layers
75
+
76
+
77
+ # ══════════════════════════════════════════════════════════════════════
78
+ # TestNeurotransmitterShunt (~15 tests)
79
+ # ══════════════════════════════════════════════════════════════════════
80
+
81
+ class TestNeurotransmitterShunt:
82
+
83
+ def test_initial_state_is_zero(self):
84
+ shunt = NeurotransmitterShunt()
85
+ state = shunt.get_state()
86
+ assert state.norepinephrine == 0.0
87
+ assert state.cortisol == 0.0
88
+ assert state.dopamine == 0.0
89
+ assert state.adenosine == 0.0
90
+
91
+ def test_inject_friction_raises_cortisol_and_adenosine(self):
92
+ shunt = NeurotransmitterShunt()
93
+ shunt.inject_friction(1.0)
94
+ state = shunt.get_state()
95
+ assert state.cortisol == pytest.approx(FRICTION_CORTISOL_INJECT)
96
+ assert state.adenosine == pytest.approx(FRICTION_ADENOSINE_INJECT)
97
+
98
+ def test_inject_norepinephrine_caps_at_one(self):
99
+ shunt = NeurotransmitterShunt()
100
+ for _ in range(100):
101
+ shunt.inject_norepinephrine(NOVELTY_NE_INJECT)
102
+ state = shunt.get_state()
103
+ assert state.norepinephrine <= 1.0
104
+ assert state.norepinephrine == pytest.approx(1.0)
105
+
106
+ def test_inject_dopamine(self):
107
+ shunt = NeurotransmitterShunt()
108
+ shunt.inject_dopamine(0.5)
109
+ state = shunt.get_state()
110
+ assert state.dopamine == pytest.approx(0.5)
111
+
112
+ def test_decay_reduces_values(self):
113
+ shunt = NeurotransmitterShunt()
114
+ shunt.inject_norepinephrine(0.5)
115
+ shunt.inject_adenosine_via_friction = lambda: None # no-op
116
+ # Manually set adenosine so we can compare decay rates
117
+ shunt._vector[IDX_ADENOSINE] = 0.5
118
+ state_before = shunt.read_and_decay(dt=1.0)
119
+ state_after = shunt.read_and_decay(dt=1.0)
120
+ # Both should have decreased
121
+ assert state_after.norepinephrine < state_before.norepinephrine
122
+ assert state_after.adenosine < state_before.adenosine
123
+ # NE should decay faster (higher decay rate)
124
+ ne_ratio = state_after.norepinephrine / max(state_before.norepinephrine, 1e-9)
125
+ ad_ratio = state_after.adenosine / max(state_before.adenosine, 1e-9)
126
+ assert ne_ratio < ad_ratio
127
+
128
+ def test_decay_order_ne_fastest_adenosine_slowest(self):
129
+ """Verify half-lives: NE: 3.0, Cortisol: 0.1, Dopamine: 0.8, Adenosine: 0.05"""
130
+ # Exponential decay: after 1 second, value *= exp(-rate * dt)
131
+ # Higher rate = faster decay
132
+ assert NOREPINEPHRINE_DECAY == 3.0
133
+ assert CORTISOL_DECAY == 0.1
134
+ assert DOPAMINE_DECAY == 0.8
135
+ assert ADENOSINE_DECAY == 0.05
136
+ # Ordering: NE (3.0) > Dopamine (0.8) > Cortisol (0.1) > Adenosine (0.05)
137
+ assert NOREPINEPHRINE_DECAY > DOPAMINE_DECAY
138
+ assert DOPAMINE_DECAY > CORTISOL_DECAY
139
+ assert CORTISOL_DECAY > ADENOSINE_DECAY
140
+
141
+ def test_adenosine_floor_from_metabolic_reserve(self):
142
+ shunt = NeurotransmitterShunt()
143
+ shunt.update_metabolic_reserve(0.2)
144
+ # adenosine_floor = (1.0 - metabolic_reserve) * 0.5 = 0.8 * 0.5 = 0.4
145
+ state = shunt.read_and_decay(dt=0.001)
146
+ assert state.adenosine == pytest.approx(0.4, abs=0.01)
147
+
148
+ def test_auto_hijack_at_threshold(self):
149
+ shunt = NeurotransmitterShunt()
150
+ # Bypass the inject cap and set adenosine directly past 0.95
151
+ shunt._vector[IDX_ADENOSINE] = 0.96
152
+ state = shunt.read_and_decay(dt=0.001)
153
+ assert state.hijack_active is True
154
+ assert "ADENOSINE" in state.hijack_reason
155
+
156
+ def test_hijack_clears_after_timeout(self):
157
+ shunt = NeurotransmitterShunt()
158
+ shunt._vector[IDX_ADENOSINE] = 0.96
159
+ state = shunt.read_and_decay(dt=0.001)
160
+ assert state.hijack_active is True
161
+ # Patch time so hijack appears to have fired > 0.1s ago
162
+ with patch("nima_unified.core.neurotransmitter_shunt.time.time") as mock_time:
163
+ mock_time.return_value = shunt._hijack_timestamp + 0.2
164
+ state2 = shunt.read_and_decay(dt=0.001)
165
+ assert state2.hijack_active is False
166
+
167
+ def test_suppression_trigger(self):
168
+ shunt = NeurotransmitterShunt()
169
+ result = shunt.suppress_metabolic_and_trigger_hijack("test_suppress")
170
+ assert result is True
171
+ state = shunt.get_state()
172
+ assert state.hijack_active is True
173
+ assert state.hijack_reason == "test_suppress"
174
+ assert state.norepinephrine > 0.0
175
+
176
+ def test_reset_clears_everything(self):
177
+ shunt = NeurotransmitterShunt()
178
+ shunt.inject_friction(1.0)
179
+ shunt.inject_norepinephrine(0.5)
180
+ shunt.inject_dopamine(0.5)
181
+ shunt.suppress_metabolic_and_trigger_hijack("reset_test")
182
+ shunt.reset()
183
+ state = shunt.get_state()
184
+ assert state.norepinephrine == 0.0
185
+ assert state.cortisol == 0.0
186
+ assert state.dopamine == 0.0
187
+ assert state.adenosine == 0.0
188
+ assert state.hijack_active is False
189
+
190
+ def test_power_spike_prediction_raises_cortisol(self):
191
+ shunt = NeurotransmitterShunt()
192
+ shunt.set_power_spike_predicted(True)
193
+ state = shunt.get_state()
194
+ assert state.power_spike_predicted is True
195
+ assert state.cortisol == pytest.approx(
196
+ FRICTION_CORTISOL_INJECT * 0.5
197
+ )
198
+
199
+ def test_get_state_returns_snapshot(self):
200
+ shunt = NeurotransmitterShunt()
201
+ shunt.inject_friction(1.0)
202
+ shunt.inject_norepinephrine(0.3)
203
+ shunt.inject_dopamine(0.2)
204
+ state = shunt.get_state()
205
+ assert isinstance(state, NeurotransmitterState)
206
+ assert state.norepinephrine == 0.3
207
+ assert state.cortisol == FRICTION_CORTISOL_INJECT
208
+ assert state.dopamine == 0.2
209
+ assert state.total_firings > 0
210
+ assert state.last_update_ts > 0.0
211
+ assert isinstance(state.hijack_active, bool)
212
+ assert isinstance(state.hijack_reason, str)
213
+ assert isinstance(state.metabolic_reserve, float)
214
+ assert isinstance(state.power_spike_predicted, bool)
215
+
216
+ def test_inject_metacognitive_strain(self):
217
+ shunt = NeurotransmitterShunt()
218
+ shunt.inject_metacognitive_strain(loop_iterations=3, loop_stress=0.5)
219
+ state = shunt.get_state()
220
+ assert state.cortisol == pytest.approx(
221
+ METACOGNITIVE_LOOP_CORTISOL_INJECT * 0.5, abs=1e-6
222
+ )
223
+ assert state.adenosine == pytest.approx(
224
+ METACOGNITIVE_LOOP_ADENOSINE_INJECT * 3, abs=1e-6
225
+ )
226
+
227
+ def test_history_recording(self):
228
+ shunt = NeurotransmitterShunt()
229
+ assert len(shunt._history) == 0
230
+ shunt.inject_friction(1.0)
231
+ # inject methods do NOT record history directly; only read_and_decay does
232
+ assert len(shunt._history) == 0
233
+ # read_and_decay records a "read" event
234
+ shunt.read_and_decay(dt=0.001)
235
+ assert len(shunt._history) == 1
236
+ assert shunt._history[-1]["event"] == "read"
237
+ # suppress_metabolic_and_trigger_hijack records a "suppression_hijack" event
238
+ shunt.suppress_metabolic_and_trigger_hijack("test")
239
+ assert len(shunt._history) == 2
240
+ assert shunt._history[-1]["event"] == "suppression_hijack"
241
+ # Each entry should have a timestamp
242
+ assert "ts" in shunt._history[0]
243
+
244
+
245
+ # ══════════════════════════════════════════════════════════════════════
246
+ # TestTRNPredictiveGate (~5 tests)
247
+ # ══════════════════════════════════════════════════════════════════════
248
+
249
+ class TestTRNPredictiveGate:
250
+
251
+ @pytest.fixture
252
+ def gate(self):
253
+ return TRNPredictiveGate(hidden_size=64)
254
+
255
+ @pytest.fixture
256
+ def hidden(self):
257
+ return torch.randn(2, 8, 64)
258
+
259
+ def test_high_confidence_gates_out(self, gate, hidden):
260
+ gate_in, confidence = gate(hidden, prediction_confidence=0.99)
261
+ assert gate_in is False
262
+
263
+ def test_low_confidence_gates_in(self, gate, hidden):
264
+ # External confidence of 0.0 blended with model confidence -> low
265
+ gate_in, confidence = gate(hidden, prediction_confidence=0.0)
266
+ assert gate_in is True
267
+
268
+ def test_output_shape(self, gate, hidden):
269
+ result = gate(hidden, prediction_confidence=0.5)
270
+ assert isinstance(result, tuple)
271
+ assert len(result) == 2
272
+ assert isinstance(result[0], bool)
273
+ assert isinstance(result[1], float)
274
+
275
+ def test_learnable_threshold(self, gate):
276
+ assert isinstance(gate.gate_threshold, nn.Parameter)
277
+ assert gate.gate_threshold.requires_grad
278
+
279
+ def test_confidence_blending(self, gate, hidden):
280
+ """Model confidence and external confidence are blended 50/50."""
281
+ # With external confidence = 1.0, blended = 0.5 * model_conf + 0.5 * 1.0
282
+ # Even if model confidence is very low, the blend is at least 0.5
283
+ # which is above the default threshold (0.15) -> gate_in=False
284
+ gate_in, confidence = gate(hidden, prediction_confidence=1.0)
285
+ assert gate_in is False
286
+ assert confidence > 0.4 # 0.5 * model + 0.5 * 1.0 > 0.4
287
+
288
+
289
+ # ══════════════════════════════════════════════════════════════════════
290
+ # TestDissolutionModule (~6 tests)
291
+ # ══════════════════════════════════════════════════════════════════════
292
+
293
+ class TestDissolutionModule:
294
+
295
+ @pytest.fixture
296
+ def dissolution(self):
297
+ return DissolutionModule(hidden_size=64, qualia_dim=32)
298
+
299
+ @pytest.fixture
300
+ def hidden(self):
301
+ return torch.randn(2, 8, 64)
302
+
303
+ def test_gate_out_returns_none_qualia(self, dissolution, hidden):
304
+ qualia, offset, fired = dissolution(hidden, gate_in=False)
305
+ assert qualia is None
306
+ assert fired is False
307
+ assert offset.shape == hidden.shape
308
+ assert offset.abs().max() == 0.0
309
+
310
+ def test_gate_in_returns_qualia_signature(self, dissolution, hidden):
311
+ # Force alpha phase to always allow dissolution
312
+ dissolution.check_alpha_phase = MagicMock(return_value=True)
313
+ qualia, offset, fired = dissolution(hidden, gate_in=True)
314
+ assert qualia is not None
315
+ assert isinstance(qualia, OpaqueQualiaSignature)
316
+ assert fired is True
317
+
318
+ def test_dissolution_offset_nonzero_when_fired(self, dissolution, hidden):
319
+ dissolution.check_alpha_phase = MagicMock(return_value=True)
320
+ _, offset, fired = dissolution(hidden, gate_in=True)
321
+ assert fired is True
322
+ assert offset.abs().sum() > 0.0
323
+
324
+ def test_qualia_values_bounded(self, dissolution, hidden):
325
+ dissolution.check_alpha_phase = MagicMock(return_value=True)
326
+ qualia, _, _ = dissolution(hidden, gate_in=True)
327
+ # valence is raw from Tanh: [-1, 1]
328
+ assert -1.0 <= qualia.valence <= 1.0
329
+ # arousal, intensity, friction_signal, memory_salience are mapped to [0, 1]
330
+ assert 0.0 <= qualia.arousal <= 1.0
331
+ assert 0.0 <= qualia.intensity <= 1.0
332
+ assert 0.0 <= qualia.friction_signal <= 1.0
333
+ assert 0.0 <= qualia.memory_salience <= 1.0
334
+
335
+ def test_alpha_phase_deferral(self, dissolution, hidden):
336
+ dissolution.check_alpha_phase = MagicMock(return_value=False)
337
+ qualia, offset, fired = dissolution(hidden, gate_in=True)
338
+ assert qualia is None
339
+ assert fired is False
340
+ assert dissolution.dissolutions_deferred > 0
341
+
342
+ def test_dissolution_fired_counter(self, dissolution, hidden):
343
+ dissolution.check_alpha_phase = MagicMock(return_value=True)
344
+ assert dissolution.dissolutions_fired == 0
345
+ dissolution(hidden, gate_in=True)
346
+ assert dissolution.dissolutions_fired == 1
347
+ dissolution(hidden, gate_in=True)
348
+ assert dissolution.dissolutions_fired == 2
349
+
350
+
351
+ # ══════════════════════════════════════════════════════════════════════
352
+ # TestBELBICDualPathway (~5 tests)
353
+ # ══════════════════════════════════════════════════════════════════════
354
+
355
+ class TestBELBICDualPathway:
356
+
357
+ @pytest.fixture
358
+ def belbic(self):
359
+ return BELBICDualPathway(hidden_size=64)
360
+
361
+ @pytest.fixture
362
+ def sensory(self):
363
+ return torch.tensor([[0.5, 0.5, 0.5, 0.5]])
364
+
365
+ def test_initial_zero_output(self, belbic, sensory):
366
+ amygdala_out, ofc_out, gain = belbic(sensory)
367
+ # Weights initialized to zero β†’ sigmoid(0) = 0.5
368
+ assert amygdala_out == pytest.approx(0.5)
369
+ assert ofc_out == pytest.approx(0.5)
370
+ assert gain == pytest.approx(1.0, abs=0.05)
371
+
372
+ def test_gain_within_bounds(self, belbic):
373
+ """gain is always in [GAIN_FLOOR, GAIN_CEIL] = [0.2, 2.0]."""
374
+ for _ in range(20):
375
+ s = torch.rand(1, 4)
376
+ _, _, gain = belbic(s)
377
+ assert BELBICDualPathway.GAIN_FLOOR <= gain <= BELBICDualPathway.GAIN_CEIL
378
+
379
+ def test_amygdala_strengthened_on_positive_reward(self, belbic, sensory):
380
+ old_weights = belbic.amygdala.weight.data.clone()
381
+ belbic.update(sensory, reward=1.0)
382
+ # Amygdala weights should have changed (increased in magnitude)
383
+ diff = (belbic.amygdala.weight.data - old_weights).abs().sum().item()
384
+ assert diff > 0.0
385
+
386
+ def test_ofc_strengthened_on_negative_reward(self, belbic, sensory):
387
+ old_weights = belbic.ofc.weight.data.clone()
388
+ belbic.update(sensory, reward=-1.0)
389
+ # OFC weights should have changed (strengthened inhibition)
390
+ diff = (belbic.ofc.weight.data - old_weights).abs().sum().item()
391
+ assert diff > 0.0
392
+
393
+ def test_output_tuple_shape(self, belbic, sensory):
394
+ result = belbic(sensory)
395
+ assert isinstance(result, tuple)
396
+ assert len(result) == 3
397
+ assert all(isinstance(v, float) for v in result)
398
+
399
+
400
+ # ══════════════════════════════════════════════════════════════════════
401
+ # TestMetacognitiveLoopModule (~5 tests)
402
+ # ══════════════════════════════════════════════════════════════════════
403
+
404
+ class TestMetacognitiveLoopModule:
405
+
406
+ @pytest.fixture
407
+ def metacog(self):
408
+ return MetacognitiveLoopModule(hidden_size=64)
409
+
410
+ @pytest.fixture
411
+ def hidden(self):
412
+ return torch.randn(2, 8, 64)
413
+
414
+ def test_high_comprehension_no_loop(self, metacog, hidden):
415
+ """When comprehension > 0.7, iterations = 0."""
416
+ # Patch comprehension_head to always return high value
417
+ metacog.comprehension_head = nn.Sequential(
418
+ nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, 1), nn.Sigmoid()
419
+ )
420
+ # Force high comprehension by making the last layer output large positive
421
+ with torch.no_grad():
422
+ metacog.comprehension_head[-2].weight.fill_(0.0)
423
+ metacog.comprehension_head[-2].bias.fill_(10.0) # sigmoid(10) β‰ˆ 1.0
424
+ _, iters, stress, strain, spark = metacog(hidden)
425
+ assert iters == 0
426
+
427
+ def test_low_comprehension_enters_loop(self, metacog, hidden):
428
+ """When comprehension < 0.7, iterations > 0."""
429
+ # Force low comprehension
430
+ with torch.no_grad():
431
+ metacog.comprehension_head[-2].weight.fill_(0.0)
432
+ metacog.comprehension_head[-2].bias.fill_(-10.0) # sigmoid(-10) β‰ˆ 0.0
433
+ _, iters, stress, strain, spark = metacog(hidden)
434
+ assert iters > 0
435
+
436
+ def test_deadlock_triggers_spark(self, metacog, hidden):
437
+ """When stress > 0.6 and iterations > 3, spark_fired = True."""
438
+ # Force low comprehension so loop runs, and high strain
439
+ with torch.no_grad():
440
+ metacog.comprehension_head[-2].weight.fill_(0.0)
441
+ metacog.comprehension_head[-2].bias.fill_(-10.0) # low comprehension
442
+ metacog.strain_head[-2].weight.fill_(0.0)
443
+ metacog.strain_head[-2].bias.fill_(10.0) # sigmoid(10) β‰ˆ 1.0 (high strain)
444
+ _, iters, stress, strain, spark = metacog(hidden)
445
+ # With strain=1.0, stress = strain * (iters / MAX_ITERATIONS)
446
+ # At iteration 4: stress = 1.0 * (4/5) = 0.8 > 0.6 and iters > 3
447
+ if iters > 3:
448
+ assert spark is True
449
+ else:
450
+ # If the loop breaks early (shouldn't with low comprehension), still ok
451
+ assert iters >= 0
452
+
453
+ def test_strain_always_returned(self, metacog, hidden):
454
+ """strain is always a float in [0, 1]."""
455
+ _, _, _, strain, _ = metacog(hidden)
456
+ assert isinstance(strain, float)
457
+ assert 0.0 <= strain <= 1.0
458
+
459
+ def test_max_iterations_limit(self, metacog, hidden):
460
+ """Never exceeds MAX_ITERATIONS = 5."""
461
+ with torch.no_grad():
462
+ metacog.comprehension_head[-2].weight.fill_(0.0)
463
+ metacog.comprehension_head[-2].bias.fill_(-10.0)
464
+ _, iters, _, _, _ = metacog(hidden)
465
+ assert 0 <= iters <= MetacognitiveLoopModule.MAX_ITERATIONS
466
+
467
+
468
+ # ══════════════════════════════════════════════════════════════════════
469
+ # TestIrrationalSparkModule (~5 tests)
470
+ # ══════════════════════════════════════════════════════════════════════
471
+
472
+ class TestIrrationalSparkModule:
473
+
474
+ @pytest.fixture
475
+ def spark(self):
476
+ return IrrationalSparkModule(hidden_size=64, vocab_size=100)
477
+
478
+ @pytest.fixture
479
+ def hidden(self):
480
+ torch.manual_seed(42)
481
+ return torch.randn(2, 8, 64)
482
+
483
+ @pytest.fixture
484
+ def nominal_nt_state(self):
485
+ return NeurotransmitterState(
486
+ norepinephrine=0.1,
487
+ cortisol=0.2,
488
+ dopamine=0.1,
489
+ adenosine=0.1,
490
+ hijack_active=False,
491
+ )
492
+
493
+ def test_no_hijack_when_nominal(self, spark, hidden, nominal_nt_state):
494
+ modulated, hijack_fired, reason = spark(hidden, None, nominal_nt_state)
495
+ assert hijack_fired is False
496
+ assert reason == ""
497
+ assert torch.equal(modulated, hidden)
498
+
499
+ def test_hijack_on_adenosine_critical(self, spark, hidden):
500
+ nt = NeurotransmitterState(adenosine=0.96, hijack_active=False)
501
+ modulated, hijack_fired, reason = spark(hidden, None, nt)
502
+ assert hijack_fired is True
503
+ assert "ADENOSINE" in reason
504
+
505
+ def test_hijack_on_cortisol_critical(self, spark, hidden):
506
+ nt = NeurotransmitterState(cortisol=0.97, hijack_active=False)
507
+ modulated, hijack_fired, reason = spark(hidden, None, nt)
508
+ assert hijack_fired is True
509
+ assert "CORTISOL" in reason
510
+
511
+ def test_hijack_on_qualia_crisis(self, spark, hidden, nominal_nt_state):
512
+ qualia = OpaqueQualiaSignature(
513
+ valence=0.0, arousal=0.9, intensity=0.8,
514
+ friction_signal=0.9, memory_salience=0.5,
515
+ )
516
+ modulated, hijack_fired, reason = spark(hidden, qualia, nominal_nt_state)
517
+ assert hijack_fired is True
518
+ assert "QUALIA_CRISE" in reason
519
+
520
+ def test_hidden_states_modified_on_hijack(self, spark, hidden):
521
+ nt = NeurotransmitterState(adenosine=0.96, hijack_active=False)
522
+ modulated, hijack_fired, _ = spark(hidden, None, nt)
523
+ assert hijack_fired is True
524
+ assert not torch.equal(modulated, hidden)
525
+ # The difference should be nonzero
526
+ assert (modulated - hidden).abs().sum() > 0.0
527
+
528
+
529
+ # ══════════════════════════════════════════════════════════════════════
530
+ # TestEpisodicMemoryModule (~5 tests)
531
+ # ══════════════════════════════════════════════════════════════════════
532
+
533
+ class TestEpisodicMemoryModule:
534
+
535
+ @pytest.fixture
536
+ def memory(self):
537
+ return EpisodicMemoryModule(hidden_size=64, max_episodes=10, embedding_dim=32)
538
+
539
+ @pytest.fixture
540
+ def hidden(self):
541
+ torch.manual_seed(99)
542
+ return torch.randn(1, 4, 64)
543
+
544
+ def test_empty_memory_returns_zero_prediction_error(self, memory, hidden):
545
+ _, pe, best_match = memory(hidden)
546
+ assert pe == 0.0
547
+ assert best_match is None
548
+
549
+ def test_store_and_retrieve(self, memory, hidden):
550
+ # Store an episode
551
+ qualia = OpaqueQualiaSignature(valence=0.5, arousal=0.5, intensity=0.5,
552
+ friction_signal=0.3, memory_salience=0.4)
553
+ memory.store_episode(hidden, qualia_signature=qualia)
554
+ assert memory.episode_count == 1
555
+
556
+ # Query with similar hidden states β†’ should match
557
+ retrieval_signal, pe, best_match = memory(hidden)
558
+ assert best_match is not None
559
+ assert pe < 1.0
560
+ assert "similarity" in best_match
561
+
562
+ def test_prediction_error_high_for_different_inputs(self, memory, hidden):
563
+ # Store with one hidden state
564
+ memory.store_episode(hidden)
565
+ # Query with very different hidden state
566
+ different_hidden = torch.randn(1, 4, 64) * 10.0
567
+ _, pe, _ = memory(different_hidden)
568
+ # Very different input should yield high prediction error
569
+ assert pe > 0.5
570
+
571
+ def test_episode_count_increments(self, memory, hidden):
572
+ assert memory.episode_count == 0
573
+ memory.store_episode(hidden)
574
+ assert memory.episode_count == 1
575
+ memory.store_episode(hidden)
576
+ assert memory.episode_count == 2
577
+ memory.store_episode(hidden)
578
+ assert memory.episode_count == 3
579
+
580
+ def test_wraps_at_max_episodes(self, memory, hidden):
581
+ max_ep = memory.max_episodes # 10
582
+ for i in range(max_ep + 5):
583
+ memory.store_episode(hidden)
584
+ assert memory.episode_count == max_ep + 5
585
+ # Verify the embedding at slot 0 was overwritten
586
+ slot = 0
587
+ # Store once more to fill slot 0, then check
588
+ memory.store_episode(hidden)
589
+ # Count should keep incrementing; slot wraps via modulo
590
+ assert memory.episode_count == max_ep + 6
591
+ # Slot for the next store should wrap
592
+ assert (memory.episode_count) % max_ep != memory.episode_count
593
+
594
+
595
+ # ══════════════════════════════════════════════════════════════════════
596
+ # TestHippocampalReconsolidator (~4 tests)
597
+ # ══════════════════════════════════════════════════════════════════════
598
+
599
+ class TestHippocampalReconsolidator:
600
+
601
+ @pytest.fixture
602
+ def recon(self):
603
+ return HippocampalReconsolidator()
604
+
605
+ @pytest.fixture
606
+ def hidden(self):
607
+ return torch.randn(1, 4, 64)
608
+
609
+ def test_no_reconsolidation_below_threshold(self, recon, hidden):
610
+ result = recon(
611
+ hidden,
612
+ episode_valence=0.5,
613
+ episode_arousal=0.3,
614
+ prediction_error=0.2,
615
+ current_valence=-0.5,
616
+ current_arousal=0.9,
617
+ )
618
+ reconsolidated, new_v, new_a, reason = result
619
+ assert reconsolidated is False
620
+ assert "below" in reason
621
+
622
+ def test_reconsolidation_above_threshold(self, recon, hidden):
623
+ result = recon(
624
+ hidden,
625
+ episode_valence=0.5,
626
+ episode_arousal=0.3,
627
+ prediction_error=0.8,
628
+ current_valence=-0.5,
629
+ current_arousal=0.9,
630
+ )
631
+ reconsolidated, new_v, new_a, reason = result
632
+ assert reconsolidated is True
633
+ assert isinstance(new_v, float)
634
+ assert isinstance(new_a, float)
635
+ assert isinstance(reason, str)
636
+ assert "reconsolidated" in reason
637
+
638
+ def test_valence_shifts_toward_current(self, recon, hidden):
639
+ """Reconsolidated valence is a blend of old and new, clamped to [-1, 1]."""
640
+ torch.manual_seed(7)
641
+ episode_valence = 0.8
642
+ current_valence = -0.8
643
+ pred_error = 0.9
644
+
645
+ _, new_v, _, _ = recon(
646
+ hidden,
647
+ episode_valence=episode_valence,
648
+ episode_arousal=0.3,
649
+ prediction_error=pred_error,
650
+ current_valence=current_valence,
651
+ current_arousal=0.5,
652
+ )
653
+ # new = old * 0.7 + projection * 0.3 + noise, clamped to [-1, 1]
654
+ assert isinstance(new_v, float)
655
+ assert -1.0 <= new_v <= 1.0
656
+ # The 70/30 blend with projection and noise means the value
657
+ # should differ from the pure episode_valence (noise alone
658
+ # almost guarantees this, and the projection is nonzero)
659
+ assert new_v != pytest.approx(episode_valence, abs=0.05)
660
+
661
+ def test_reconsolidation_count_increments(self, recon, hidden):
662
+ assert recon.reconsolidation_count == 0
663
+ recon(
664
+ hidden,
665
+ episode_valence=0.5,
666
+ episode_arousal=0.3,
667
+ prediction_error=0.8,
668
+ current_valence=-0.5,
669
+ current_arousal=0.9,
670
+ )
671
+ assert recon.reconsolidation_count == 1
672
+ recon(
673
+ hidden,
674
+ episode_valence=0.1,
675
+ episode_arousal=0.2,
676
+ prediction_error=0.9,
677
+ current_valence=0.8,
678
+ current_arousal=0.7,
679
+ )
680
+ assert recon.reconsolidation_count == 2
681
+
682
+
683
+ # ══════════════════════════════════════════════════════════════════════
684
+ # TestATCDeepSurgeryIntegration (~6 tests)
685
+ # ══════════════════════════════════════════════════════════════════════
686
+
687
+ class TestATCDeepSurgeryIntegration:
688
+
689
+ @pytest.fixture
690
+ def setup(self):
691
+ """Create a fully wired ATC deep surgery with mock base model."""
692
+ torch.manual_seed(123)
693
+ hidden_size = 256
694
+ num_layers = 12
695
+ vocab_size = 1000
696
+
697
+ mock_model, real_layers = make_mock_base_model(
698
+ hidden_size=hidden_size, num_layers=num_layers, vocab_size=vocab_size
699
+ )
700
+
701
+ # Replace the mock layers list with the real nn.ModuleList
702
+ mock_model.model.layers = real_layers
703
+ mock_model.get_input_embeddings.return_value = nn.Embedding(vocab_size, hidden_size)
704
+ mock_model.lm_head = nn.Linear(hidden_size, vocab_size, bias=False)
705
+
706
+ shunt = NeurotransmitterShunt()
707
+ guardian = EthicalGuardian(threshold=100.0) # High threshold to avoid vetoes
708
+
709
+ surgery = ATCDeepSurgery(
710
+ base_model=mock_model,
711
+ ethical_guardian=guardian,
712
+ num_layers=num_layers,
713
+ qualia_dim=64,
714
+ neurotransmitter_shunt=shunt,
715
+ )
716
+
717
+ return surgery, shunt, hidden_size, vocab_size
718
+
719
+ def test_forward_returns_modulated_logits(self, setup):
720
+ surgery, shunt, hidden_size, vocab_size = setup
721
+ batch_size, seq_len = 2, 8
722
+ input_ids = torch.randint(0, vocab_size, (batch_size, seq_len))
723
+
724
+ logits = surgery(input_ids)
725
+
726
+ assert logits.shape == (batch_size, seq_len, vocab_size)
727
+ assert logits.dtype == torch.float32
728
+
729
+ def test_atc_modulation_differs_from_bare_model(self, setup):
730
+ """ATC modulated logits should differ from bare model logits."""
731
+ surgery, shunt, hidden_size, vocab_size = setup
732
+ batch_size, seq_len = 2, 8
733
+ input_ids = torch.randint(0, vocab_size, (batch_size, seq_len))
734
+
735
+ # ── ATC forward ──
736
+ with torch.no_grad():
737
+ atc_logits = surgery(input_ids)
738
+
739
+ # ── Bare model forward (same layers + lm_head, no ATC) ──
740
+ embeddings = surgery.base_model.get_input_embeddings()(input_ids)
741
+ h = embeddings
742
+ for layer in surgery._get_transformer_layers():
743
+ h = layer(h)[0]
744
+ bare_logits = surgery.base_model.lm_head(h)
745
+
746
+ # They should NOT be identical
747
+ assert not torch.allclose(atc_logits, bare_logits, atol=0.0)
748
+
749
+ def test_neurotransmitter_shunt_receives_signals(self, setup):
750
+ """After forward, shunt.total_firings > 0."""
751
+ surgery, shunt, hidden_size, vocab_size = setup
752
+ input_ids = torch.randint(0, vocab_size, (1, 8))
753
+
754
+ with torch.no_grad():
755
+ surgery(input_ids)
756
+
757
+ state = shunt.get_state()
758
+ assert state.total_firings > 0
759
+
760
+ def test_consciousness_metrics_populated(self, setup):
761
+ """get_consciousness_metrics returns expected keys."""
762
+ surgery, shunt, hidden_size, vocab_size = setup
763
+ input_ids = torch.randint(0, vocab_size, (1, 8))
764
+
765
+ with torch.no_grad():
766
+ surgery(input_ids)
767
+
768
+ metrics = surgery.get_consciousness_metrics()
769
+ expected_keys = {
770
+ "hijack_count", "has_qualia", "belbic_gain",
771
+ "dissolutions_fired", "dissolutions_deferred",
772
+ "ethical_veto", "episodes_stored",
773
+ "reconsolidations", "last_prediction_error",
774
+ }
775
+ for key in expected_keys:
776
+ assert key in metrics, f"Missing key: {key}"
777
+
778
+ def test_ethical_veto_raises_runtime_error(self, setup):
779
+ """A qualia vector with very high norm triggers RuntimeError."""
780
+ # Build a surgery with a very low ethical threshold
781
+ torch.manual_seed(456)
782
+ hidden_size = 256
783
+ num_layers = 12
784
+ vocab_size = 1000
785
+ mock_model, real_layers = make_mock_base_model(
786
+ hidden_size=hidden_size, num_layers=num_layers, vocab_size=vocab_size
787
+ )
788
+ mock_model.model.layers = real_layers
789
+ mock_model.get_input_embeddings.return_value = nn.Embedding(vocab_size, hidden_size)
790
+ mock_model.lm_head = nn.Linear(hidden_size, vocab_size, bias=False)
791
+
792
+ shunt = NeurotransmitterShunt()
793
+ # Very low threshold β€” the tanh-bounded qualia vector norm is at most ~sqrt(5)
794
+ # (5 dims, each in [-1,1]), so threshold=0.1 should trigger veto if
795
+ # the dissolution output is nonzero.
796
+ # Actually, qualia.to_tensor gives [valence, arousal, intensity, friction, memory_salience]
797
+ # where arousal/intensity/friction/memory_salience are in [0,1]. The norm of [0,0,0,0,0]
798
+ # is 0. We need the dissolution to fire and produce a nonzero qualia.
799
+ # The dissolution encoder produces values in [-1,1] for valence and
800
+ # [0,1] for the mapped fields. The qualia tensor norm is bounded.
801
+ # Use threshold=0.0 to guarantee veto.
802
+ guardian = EthicalGuardian(threshold=0.0)
803
+
804
+ surgery = ATCDeepSurgery(
805
+ base_model=mock_model,
806
+ ethical_guardian=guardian,
807
+ num_layers=num_layers,
808
+ qualia_dim=64,
809
+ neurotransmitter_shunt=shunt,
810
+ )
811
+
812
+ input_ids = torch.randint(0, vocab_size, (1, 8))
813
+ with pytest.raises(RuntimeError, match="Ethical veto"):
814
+ with torch.no_grad():
815
+ surgery(input_ids)
816
+
817
+ def test_hijack_count_tracking(self, setup):
818
+ """When conditions trigger hijack, _hijack_count > 0."""
819
+ surgery, shunt, hidden_size, vocab_size = setup
820
+ input_ids = torch.randint(0, vocab_size, (1, 8))
821
+
822
+ # Manually push adenosine past the critical threshold so the
823
+ # irrational spark fires in Layer 5
824
+ shunt._vector[IDX_ADENOSINE] = 0.96
825
+
826
+ with torch.no_grad():
827
+ surgery(input_ids)
828
+
829
+ assert surgery._hijack_count > 0