Skip to content

Commit 42abca8

Browse files
authored
Merge pull request #2884 from fermga/copilot/add-complete-testing-suite-zhir
Add comprehensive test suite for ZHIR (Mutation) operator canonical contracts
2 parents 9c56455 + 873894d commit 42abca8

File tree

6 files changed

+1974
-0
lines changed

6 files changed

+1974
-0
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
"""Tests for ZHIR (Mutation) network impact and neighbor effects.
2+
3+
This module tests how ZHIR affects neighboring nodes in the network,
4+
capturing the structural physics of phase transformation propagation.
5+
6+
Test Coverage:
7+
1. Impact on directly connected neighbors
8+
2. Phase coherence with neighbors
9+
3. Network-wide effects
10+
4. Isolated node behavior
11+
12+
References:
13+
- AGENTS.md §11 (Mutation operator)
14+
- test_mutation_metrics_comprehensive.py (network_impact metrics)
15+
"""
16+
17+
import pytest
18+
import math
19+
from tnfr.structural import create_nfr, run_sequence
20+
from tnfr.operators.definitions import Mutation, Coherence, Dissonance
21+
22+
23+
class TestZHIRNetworkImpact:
24+
"""Test ZHIR impact on network neighbors."""
25+
26+
def test_zhir_affects_neighbors(self):
27+
"""ZHIR should have measurable impact on connected neighbors."""
28+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
29+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
30+
31+
# Add neighbors with proper TNFR attributes
32+
neighbors = []
33+
for i in range(3):
34+
neighbor_id = f"neighbor_{i}"
35+
G.add_node(
36+
neighbor_id,
37+
EPI=0.5,
38+
epi=0.5,
39+
theta=0.5 + i * 0.1,
40+
**{"νf": 1.0}, # Greek letter for canonical
41+
vf=1.0,
42+
dnfr=0.0,
43+
delta_nfr=0.0,
44+
theta_history=[0.5, 0.5 + i * 0.1],
45+
epi_history=[0.4, 0.5],
46+
)
47+
G.add_edge(node, neighbor_id)
48+
neighbors.append(neighbor_id)
49+
50+
G.graph["COLLECT_OPERATOR_METRICS"] = True
51+
52+
# Store neighbor states before mutation
53+
neighbors_theta_before = {n: G.nodes[n]["theta"] for n in neighbors}
54+
55+
# Apply mutation
56+
Mutation()(G, node)
57+
58+
# Check metrics captured network impact
59+
metrics = G.graph["operator_metrics"][-1]
60+
61+
assert "neighbor_count" in metrics
62+
assert metrics["neighbor_count"] == 3
63+
64+
assert "network_impact_radius" in metrics
65+
# Impact radius should be non-zero with neighbors
66+
# (actual value depends on implementation)
67+
68+
assert "phase_coherence_neighbors" in metrics
69+
# Should have computed phase coherence with neighbors
70+
71+
def test_zhir_phase_coherence_with_neighbors(self):
72+
"""ZHIR should consider phase coherence with neighbors."""
73+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
74+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
75+
76+
# Add neighbor with similar phase (coherent)
77+
G.add_node(
78+
"coherent_neighbor",
79+
epi=0.5,
80+
vf=1.0,
81+
theta=0.52, # Very close phase
82+
delta_nfr=0.0,
83+
theta_history=[0.5, 0.52],
84+
)
85+
G.add_edge(node, "coherent_neighbor")
86+
87+
# Add neighbor with opposite phase (incoherent)
88+
G.add_node(
89+
"incoherent_neighbor",
90+
epi=0.5,
91+
vf=1.0,
92+
theta=0.5 + math.pi, # Opposite phase
93+
delta_nfr=0.0,
94+
theta_history=[0.5 + math.pi, 0.5 + math.pi],
95+
)
96+
G.add_edge(node, "incoherent_neighbor")
97+
98+
G.graph["COLLECT_OPERATOR_METRICS"] = True
99+
100+
# Apply mutation
101+
Mutation()(G, node)
102+
103+
metrics = G.graph["operator_metrics"][-1]
104+
105+
# Should have measured phase coherence
106+
assert "phase_coherence_neighbors" in metrics
107+
assert "impacted_neighbors" in metrics
108+
109+
def test_zhir_isolated_node_zero_impact(self):
110+
"""ZHIR on isolated node should have zero network impact."""
111+
G, node = create_nfr("test", epi=0.5, vf=1.0)
112+
# No neighbors
113+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
114+
G.graph["COLLECT_OPERATOR_METRICS"] = True
115+
116+
Mutation()(G, node)
117+
118+
metrics = G.graph["operator_metrics"][-1]
119+
120+
# Isolated node should have zero neighbors
121+
assert metrics["neighbor_count"] == 0
122+
assert metrics["impacted_neighbors"] == 0
123+
assert metrics["network_impact_radius"] == 0.0
124+
assert metrics["phase_coherence_neighbors"] == 0.0
125+
126+
def test_zhir_network_impact_radius_calculation(self):
127+
"""Network impact radius should be calculated correctly."""
128+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
129+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
130+
131+
# Add neighbors at different "distances" (via phase difference)
132+
# Close phase = high impact
133+
G.add_node("close", epi=0.5, vf=1.0, theta=0.51, delta_nfr=0.0)
134+
G.add_edge(node, "close")
135+
136+
# Far phase = low impact
137+
G.add_node("far", epi=0.5, vf=1.0, theta=0.5 + 1.5, delta_nfr=0.0)
138+
G.add_edge(node, "far")
139+
140+
G.graph["COLLECT_OPERATOR_METRICS"] = True
141+
142+
Mutation()(G, node)
143+
144+
metrics = G.graph["operator_metrics"][-1]
145+
146+
# Should have computed impact radius
147+
assert "network_impact_radius" in metrics
148+
assert 0.0 <= metrics["network_impact_radius"] <= 1.0
149+
150+
def test_zhir_in_dense_network(self):
151+
"""ZHIR in dense network should track all neighbors."""
152+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
153+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
154+
155+
# Add many neighbors (dense network)
156+
for i in range(10):
157+
neighbor_id = f"n{i}"
158+
G.add_node(
159+
neighbor_id,
160+
epi=0.5,
161+
vf=1.0,
162+
theta=0.5 + i * 0.1,
163+
delta_nfr=0.0,
164+
)
165+
G.add_edge(node, neighbor_id)
166+
167+
G.graph["COLLECT_OPERATOR_METRICS"] = True
168+
169+
Mutation()(G, node)
170+
171+
metrics = G.graph["operator_metrics"][-1]
172+
173+
# Should track all neighbors
174+
assert metrics["neighbor_count"] == 10
175+
176+
def test_zhir_with_bidirectional_edges(self):
177+
"""ZHIR should handle bidirectional connections correctly."""
178+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
179+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
180+
181+
# Add bidirectional neighbor
182+
G.add_node("neighbor", epi=0.5, vf=1.0, theta=0.52, delta_nfr=0.0)
183+
G.add_edge(node, "neighbor")
184+
G.add_edge("neighbor", node) # Bidirectional
185+
186+
G.graph["COLLECT_OPERATOR_METRICS"] = True
187+
188+
# Should not raise error
189+
Mutation()(G, node)
190+
191+
metrics = G.graph["operator_metrics"][-1]
192+
193+
# Should count neighbor once (not twice for bidirectional)
194+
assert metrics["neighbor_count"] >= 1
195+
196+
197+
class TestZHIRNeighborPhaseCompatibility:
198+
"""Test phase compatibility checking with neighbors."""
199+
200+
def test_zhir_with_compatible_neighbors(self):
201+
"""ZHIR with phase-compatible neighbors should work smoothly."""
202+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
203+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
204+
205+
# Add neighbors with compatible phases (within π/2)
206+
for i in range(3):
207+
G.add_node(
208+
f"n{i}",
209+
epi=0.5,
210+
vf=1.0,
211+
theta=0.5 + i * 0.3, # Within compatible range
212+
delta_nfr=0.0,
213+
)
214+
G.add_edge(node, f"n{i}")
215+
216+
G.graph["COLLECT_OPERATOR_METRICS"] = True
217+
218+
# Should work without issues
219+
Mutation()(G, node)
220+
221+
metrics = G.graph["operator_metrics"][-1]
222+
223+
# Phase coherence should be relatively high
224+
if "phase_coherence_neighbors" in metrics:
225+
# Should be positive (compatible phases)
226+
assert metrics["phase_coherence_neighbors"] >= 0
227+
228+
def test_zhir_with_incompatible_neighbors(self):
229+
"""ZHIR with phase-incompatible neighbors should still work."""
230+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
231+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
232+
233+
# Add neighbors with incompatible phases (antiphase)
234+
for i in range(3):
235+
neighbor_id = f"n{i}"
236+
G.add_node(
237+
neighbor_id,
238+
EPI=0.5,
239+
**{"νf": 1.0},
240+
theta=0.5 + math.pi + i * 0.1, # Opposite phase
241+
delta_nfr=0.0,
242+
)
243+
G.add_edge(node, neighbor_id)
244+
245+
# Should not raise error (ZHIR is internal transformation)
246+
Mutation()(G, node)
247+
248+
# Node should still be viable
249+
assert G.nodes[node]["νf"] > 0
250+
251+
252+
class TestZHIRNetworkPropagation:
253+
"""Test mutation effects propagation through network."""
254+
255+
def test_zhir_sequence_with_resonance_propagates(self):
256+
"""ZHIR → RA should propagate transformed state."""
257+
from tnfr.operators.definitions import Resonance
258+
259+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
260+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
261+
262+
# Add neighbor with compatible phase and proper initialization
263+
neighbor_id = "neighbor"
264+
G.add_node(neighbor_id, EPI=0.5, **{"νf": 1.0}, theta=0.52, delta_nfr=0.0)
265+
G.add_edge(node, neighbor_id)
266+
267+
theta_before = G.nodes[node]["theta"]
268+
269+
# Apply mutation then resonance
270+
run_sequence(G, node, [
271+
Coherence(),
272+
Dissonance(),
273+
Mutation(), # Transform phase
274+
Resonance(), # Propagate to neighbors
275+
])
276+
277+
theta_after = G.nodes[node]["theta"]
278+
279+
# Phase should have changed
280+
assert theta_after != theta_before
281+
282+
def test_zhir_does_not_directly_modify_neighbors(self):
283+
"""ZHIR should not directly modify neighbor states (internal transformation)."""
284+
G, node = create_nfr("test", epi=0.5, vf=1.0, theta=0.5)
285+
G.nodes[node]["epi_history"] = [0.3, 0.4, 0.5]
286+
287+
# Add neighbor with proper initialization
288+
neighbor_id = "neighbor"
289+
G.add_node(neighbor_id, EPI=0.5, **{"νf": 1.0}, theta=0.52, delta_nfr=0.0)
290+
G.add_edge(node, neighbor_id)
291+
292+
# Store neighbor state
293+
neighbor_theta_before = G.nodes[neighbor_id]["theta"]
294+
neighbor_epi_before = G.nodes[neighbor_id]["EPI"]
295+
296+
# Apply mutation to main node
297+
Mutation()(G, node)
298+
299+
# Neighbor should not be directly modified
300+
assert G.nodes[neighbor_id]["theta"] == neighbor_theta_before
301+
assert G.nodes[neighbor_id]["EPI"] == neighbor_epi_before
302+
303+
304+
if __name__ == "__main__":
305+
pytest.main([__file__, "-v"])

0 commit comments

Comments
 (0)