Skip to content

Commit e29d38b

Browse files
Copilotfermga
andcommitted
[SHA Tests] Complete regression test suite for Silence operator
Co-authored-by: fermga <203334638+fermga@users.noreply.github.com>
1 parent 47f5379 commit e29d38b

File tree

1 file changed

+368
-0
lines changed

1 file changed

+368
-0
lines changed
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
"""Comprehensive regression test suite for SHA (Silence) operator.
2+
3+
This module implements a complete regression test suite for the Silence (SHA)
4+
operator following TNFR structural theory as specified in TNFR.pdf §2.3.10.
5+
6+
Test Coverage:
7+
- A. Structural Effects: νf reduction, EPI preservation, ΔNFR freezing
8+
- B. Preconditions: Validation of minimum νf and existing EPI requirements
9+
- C. Canonical Sequences: IL→SHA, SHA→AL, SHA→NAV, OZ→SHA
10+
- D. Metrics: SHA-specific metrics validation
11+
- E. Integration: Multi-node effects, complex sequences
12+
- F. Nodal Equation: Validation of ∂EPI/∂t = νf · ΔNFR(t)
13+
- G. Full Lifecycle: Complete activation-silence-reactivation cycle
14+
15+
Theoretical Foundation:
16+
- SHA reduces νf → 0 (structural pause)
17+
- EPI remains invariant (preservation)
18+
- ΔNFR maintained but frozen (no reorganization pressure)
19+
- Latency state tracking for memory consolidation
20+
"""
21+
22+
from __future__ import annotations
23+
24+
import pytest
25+
import warnings
26+
27+
from tnfr.constants import DNFR_PRIMARY, EPI_PRIMARY, VF_PRIMARY
28+
from tnfr.structural import create_nfr, run_sequence
29+
from tnfr.operators.definitions import (
30+
Silence,
31+
Emission,
32+
Reception,
33+
Coherence,
34+
Dissonance,
35+
Resonance,
36+
Coupling,
37+
Transition,
38+
)
39+
from tnfr.operators.preconditions import OperatorPreconditionError
40+
from tnfr.alias import set_attr
41+
from tnfr.constants.aliases import ALIAS_EPI, ALIAS_VF
42+
43+
44+
class TestSHAStructuralEffects:
45+
"""Test A: Structural effects of SHA operator."""
46+
47+
def test_sha_reduces_vf_to_minimum(self):
48+
"""Test 1: SHA must reduce νf to value close to zero.
49+
50+
Validates nodal equation: If νf ≈ 0, then ∂EPI/∂t ≈ 0 (independent of ΔNFR).
51+
Sequence must start with generator (AL) per R1 grammar rule.
52+
"""
53+
G, node = create_nfr("active", epi=0.60, vf=1.50)
54+
initial_vf = G.nodes[node][VF_PRIMARY]
55+
56+
# Apply sequence: AL (generator start) → SHA
57+
run_sequence(G, node, [Emission(), Silence()])
58+
59+
final_vf = G.nodes[node][VF_PRIMARY]
60+
min_threshold = G.graph.get("SHA_MIN_VF", 0.01)
61+
62+
# Validations
63+
assert final_vf < initial_vf, "SHA must reduce νf"
64+
assert final_vf <= min_threshold * 2, f"νf must be close to minimum: {final_vf}"
65+
assert final_vf >= 0.0, "νf cannot be negative"
66+
67+
def test_sha_preserves_epi_exactly(self):
68+
"""Test 2: SHA must maintain EPI invariant with minimal tolerance."""
69+
G, node = create_nfr("memory", epi=0.73, vf=1.20)
70+
initial_epi = G.nodes[node][EPI_PRIMARY]
71+
72+
# Apply SHA
73+
run_sequence(G, node, [Silence()])
74+
75+
final_epi = G.nodes[node][EPI_PRIMARY]
76+
tolerance = 1e-3 # Allow small numerical tolerance
77+
78+
assert abs(final_epi - initial_epi) < tolerance, (
79+
f"EPI must be preserved: ΔEPI = {abs(final_epi - initial_epi)}"
80+
)
81+
82+
def test_sha_freezes_dnfr(self):
83+
"""Test 3: SHA does not modify ΔNFR - state is frozen.
84+
85+
ΔNFR can remain high but with νf ≈ 0, it does not affect EPI.
86+
"""
87+
G, node = create_nfr("frozen", epi=0.50, vf=1.00)
88+
G.nodes[node][DNFR_PRIMARY] = 0.15 # High reorganization pressure
89+
initial_dnfr = G.nodes[node][DNFR_PRIMARY]
90+
91+
# Apply SHA
92+
run_sequence(G, node, [Silence()])
93+
94+
final_dnfr = G.nodes[node][DNFR_PRIMARY]
95+
96+
# ΔNFR can remain high, but SHA should not actively change it
97+
# The key is that with νf ≈ 0, ΔNFR does not affect EPI
98+
assert abs(final_dnfr - initial_dnfr) < 0.05, (
99+
"SHA should not actively modify ΔNFR"
100+
)
101+
102+
103+
class TestSHAPreconditions:
104+
"""Test B: Precondition validation for SHA operator."""
105+
106+
def test_sha_precondition_vf_minimum(self):
107+
"""Test 4: SHA must fail if νf already at minimum."""
108+
G, node = create_nfr("already_silent", epi=0.40, vf=0.005)
109+
G.graph["VALIDATE_OPERATOR_PRECONDITIONS"] = True
110+
111+
with pytest.raises(OperatorPreconditionError, match="already minimal"):
112+
run_sequence(G, node, [Silence()])
113+
114+
def test_sha_requires_existing_epi(self):
115+
"""Test 5: SHA should warn if EPI ≈ 0 (no structure to preserve)."""
116+
G, node = create_nfr("empty", epi=0.0, vf=1.0)
117+
G.graph["VALIDATE_OPERATOR_PRECONDITIONS"] = True
118+
119+
# SHA on empty structure should issue warning
120+
# This is more of a semantic warning than hard failure
121+
with pytest.warns(UserWarning, match="no structure|empty|zero"):
122+
run_sequence(G, node, [Silence()])
123+
124+
125+
class TestSHACanonicalSequences:
126+
"""Test C: Canonical operator sequences involving SHA."""
127+
128+
def test_sha_after_coherence_preserves_stability(self):
129+
"""Test 6: IL → SHA (stabilize then preserve) - canonical memory pattern."""
130+
G, node = create_nfr("learning", epi=0.45, vf=1.10)
131+
G.nodes[node][DNFR_PRIMARY] = 0.20 # High initial pressure
132+
133+
# IL reduces ΔNFR, stabilizes
134+
run_sequence(G, node, [Coherence()])
135+
post_il_dnfr = G.nodes[node][DNFR_PRIMARY]
136+
post_il_epi = G.nodes[node][EPI_PRIMARY]
137+
138+
# SHA preserves the stabilized state
139+
run_sequence(G, node, [Silence()])
140+
141+
assert G.nodes[node][VF_PRIMARY] < 0.1, "SHA reduces νf"
142+
assert abs(G.nodes[node][EPI_PRIMARY] - post_il_epi) < 0.05, "EPI preserved"
143+
144+
def test_sha_to_emission_reactivation(self):
145+
"""Test 7: SHA → NAV → AL (reactivation from silence) - structurally coherent awakening.
146+
147+
TNFR Physics: Cannot jump zero → high (SHA → AL) directly.
148+
Must transition through medium frequency: SHA → NAV → AL (zero → medium → high).
149+
This respects structural continuity and prevents singularities.
150+
"""
151+
G, node = create_nfr("sleeping", epi=0.55, vf=1.00)
152+
153+
# Phase 1: Prepare and enter silence
154+
run_sequence(G, node, [Emission(), Coherence(), Silence()])
155+
assert G.nodes[node][VF_PRIMARY] < 0.1, "Node in silence"
156+
silent_epi = G.nodes[node][EPI_PRIMARY]
157+
158+
# Phase 2: Reactivate through medium frequency (NAV) then high (AL)
159+
run_sequence(G, node, [Transition(), Emission()])
160+
161+
# Validate coherent reactivation
162+
assert G.nodes[node][VF_PRIMARY] > 0.5, "Node reactivated"
163+
assert G.nodes[node][EPI_PRIMARY] >= silent_epi - 0.15, "EPI maintains structural identity"
164+
165+
def test_sha_to_transition_controlled_change(self):
166+
"""Test 8: SHA → NAV (controlled transition from silence)."""
167+
G, node = create_nfr("dormant", epi=0.48, vf=0.95)
168+
169+
# SHA: Preserve structure
170+
run_sequence(G, node, [Silence()])
171+
preserved_epi = G.nodes[node][EPI_PRIMARY]
172+
173+
# NAV: Transition from silence
174+
run_sequence(G, node, [Transition()])
175+
176+
# Validate controlled transition without collapse
177+
assert G.nodes[node][VF_PRIMARY] > 0.1, "Node reactivating"
178+
assert abs(G.nodes[node][EPI_PRIMARY] - preserved_epi) < 0.2, (
179+
"EPI transitions controlledly without collapse"
180+
)
181+
182+
def test_oz_to_sha_containment(self):
183+
"""Test 9: OZ → SHA (dissonance contained) - therapeutic pause.
184+
185+
Clinical use case: Trauma containment, conflict deferred.
186+
"""
187+
G, node = create_nfr("trauma", epi=0.40, vf=1.00)
188+
G.nodes[node][DNFR_PRIMARY] = 0.05
189+
190+
# OZ: Introduce dissonance
191+
run_sequence(G, node, [Dissonance()])
192+
post_oz_dnfr = G.nodes[node][DNFR_PRIMARY]
193+
assert post_oz_dnfr > 0.10, "Dissonance increases ΔNFR"
194+
195+
# SHA: Contain dissonance (protective pause)
196+
run_sequence(G, node, [Silence()])
197+
198+
# Validate containment
199+
assert G.nodes[node][VF_PRIMARY] < 0.1, "Node paused"
200+
# ΔNFR remains high but frozen
201+
assert G.nodes[node][DNFR_PRIMARY] > 0.10, "Dissonance contained (not resolved)"
202+
203+
204+
class TestSHAMetrics:
205+
"""Test D: SHA-specific metrics collection."""
206+
207+
def test_sha_metrics_preservation(self):
208+
"""Test 10: Validate that silence_metrics captures preservation correctly."""
209+
G, node = create_nfr("test", epi=0.60, vf=1.00)
210+
G.graph["COLLECT_OPERATOR_METRICS"] = True
211+
212+
run_sequence(G, node, [Silence()])
213+
214+
# Check if metrics were collected
215+
if "operator_metrics" in G.graph:
216+
metrics = G.graph["operator_metrics"][-1]
217+
218+
assert metrics["operator"] == "Silence", "Operator name recorded"
219+
assert metrics["glyph"] == "SHA", "Glyph recorded"
220+
221+
# Check for SHA-specific metric keys
222+
assert "vf_reduction" in metrics or "vf_final" in metrics, (
223+
"νf reduction metric present"
224+
)
225+
226+
227+
class TestSHAIntegration:
228+
"""Test E: Integration and network effects."""
229+
230+
def test_sha_does_not_affect_neighbors(self):
231+
"""Test 11: SHA is local operation - no direct propagation to neighbors."""
232+
G, n1 = create_nfr("node1", epi=0.50, vf=1.00)
233+
234+
# Add second node manually
235+
_, n2 = create_nfr("node2", epi=0.50, vf=1.00)
236+
# Import n2's attributes into G
237+
G.add_node(n2)
238+
set_attr(G.nodes[n2], ALIAS_EPI, 0.50)
239+
set_attr(G.nodes[n2], ALIAS_VF, 1.00)
240+
G.add_edge(n1, n2) # Connect nodes
241+
242+
initial_n2_vf = G.nodes[n2][VF_PRIMARY]
243+
244+
# SHA on n1
245+
run_sequence(G, n1, [Silence()])
246+
247+
# n2 must remain active
248+
assert G.nodes[n1][VF_PRIMARY] < 0.1, "n1 in silence"
249+
assert G.nodes[n2][VF_PRIMARY] >= initial_n2_vf * 0.9, (
250+
"n2 remains active (SHA is local)"
251+
)
252+
253+
def test_sha_after_complex_sequence(self):
254+
"""Test 12: SHA as closure of complex sequence.
255+
256+
Sequence: AL → IL → RA → UM → SHA
257+
"""
258+
G, node = create_nfr("complex", epi=0.30, vf=0.80)
259+
260+
sequence = [
261+
Emission(), # AL: Activate
262+
Coherence(), # IL: Stabilize
263+
Resonance(), # RA: Propagate
264+
Coupling(), # UM: Couple
265+
Silence() # SHA: Close and preserve
266+
]
267+
268+
initial_epi = G.nodes[node][EPI_PRIMARY]
269+
run_sequence(G, node, sequence)
270+
271+
# Validate final state
272+
assert G.nodes[node][VF_PRIMARY] < 0.1, "Sequence closed with silence"
273+
assert G.nodes[node][EPI_PRIMARY] >= initial_epi, (
274+
"EPI evolved during sequence"
275+
)
276+
277+
278+
class TestSHANodalEquation:
279+
"""Test F: Nodal equation validation for SHA."""
280+
281+
def test_sha_nodal_equation_validation(self):
282+
"""Test 13: Validate SHA respects nodal equation: ∂EPI/∂t = νf · ΔNFR(t).
283+
284+
If νf → 0, then |∂EPI/∂t| → 0
285+
"""
286+
G, node = create_nfr("validate", epi=0.65, vf=1.30)
287+
G.nodes[node][DNFR_PRIMARY] = 0.25 # High pressure
288+
289+
epi_before = G.nodes[node][EPI_PRIMARY]
290+
291+
# SHA should work even with high ΔNFR
292+
run_sequence(G, node, [Silence()])
293+
294+
epi_after = G.nodes[node][EPI_PRIMARY]
295+
vf_after = G.nodes[node][VF_PRIMARY]
296+
297+
# ∂EPI/∂t ≈ νf_after · ΔNFR ≈ 0 (because νf ≈ 0)
298+
delta_epi = abs(epi_after - epi_before)
299+
300+
# With νf ≈ 0, EPI change should be minimal regardless of ΔNFR
301+
assert delta_epi < 0.1, (
302+
f"Nodal equation respected: ΔEPI = {delta_epi} should be small with νf ≈ 0"
303+
)
304+
305+
306+
class TestSHAFullLifecycle:
307+
"""Test G: Complete lifecycle including SHA."""
308+
309+
def test_sha_full_cycle_activation_silence_reactivation(self):
310+
"""Test 14: Complete cycle: AL → IL → SHA → NAV → AL.
311+
312+
Simulates: learning → consolidation → memory → recall → use
313+
"""
314+
G, node = create_nfr("lifecycle", epi=0.25, vf=0.90)
315+
316+
# Phase 1: Activation and stabilization (learning)
317+
run_sequence(G, node, [Emission(), Coherence()])
318+
post_learning_epi = G.nodes[node][EPI_PRIMARY]
319+
assert post_learning_epi > 0.25, "Learning increments EPI"
320+
321+
# Phase 2: Consolidation in silence (memory formation)
322+
run_sequence(G, node, [Silence()])
323+
assert G.nodes[node][VF_PRIMARY] < 0.1, "Memory consolidated"
324+
memory_epi = G.nodes[node][EPI_PRIMARY]
325+
326+
# Phase 3: Transition and reactivation (recall)
327+
run_sequence(G, node, [Transition(), Emission()])
328+
329+
# Validate memory preservation and reactivation
330+
assert abs(G.nodes[node][EPI_PRIMARY] - memory_epi) < 0.2, (
331+
"Structural identity preserved through silence cycle"
332+
)
333+
assert G.nodes[node][VF_PRIMARY] > 0.5, "Node active again"
334+
335+
336+
class TestSHALatencyStateTracking:
337+
"""Additional tests for SHA latency state attributes."""
338+
339+
def test_sha_sets_latency_attributes(self):
340+
"""Validate SHA sets latency state tracking attributes."""
341+
G, node = create_nfr("latency_test", epi=0.50, vf=1.00)
342+
343+
run_sequence(G, node, [Silence()])
344+
345+
# Check latency attributes
346+
assert G.nodes[node].get("latent") == True, "Latent flag set"
347+
assert "latency_start_time" in G.nodes[node], "Start time recorded"
348+
assert "preserved_epi" in G.nodes[node], "EPI preserved"
349+
assert G.nodes[node]["preserved_epi"] == pytest.approx(0.50, abs=0.01), (
350+
"Preserved EPI matches initial"
351+
)
352+
assert "silence_duration" in G.nodes[node], "Duration tracker initialized"
353+
354+
def test_sha_preserved_epi_matches_current(self):
355+
"""Validate preserved_epi attribute matches actual EPI at silence entry."""
356+
G, node = create_nfr("preserve_test", epi=0.73, vf=1.10)
357+
358+
# Apply some operators first
359+
run_sequence(G, node, [Emission(), Coherence()])
360+
epi_before_silence = G.nodes[node][EPI_PRIMARY]
361+
362+
# Apply SHA
363+
run_sequence(G, node, [Silence()])
364+
365+
preserved_epi = G.nodes[node].get("preserved_epi", 0.0)
366+
assert abs(preserved_epi - epi_before_silence) < 0.01, (
367+
"Preserved EPI must match EPI at silence entry"
368+
)

0 commit comments

Comments
 (0)