|
6 | 6 | import pytest |
7 | 7 |
|
8 | 8 | import tnfr.operators as operators |
9 | | -from tnfr.constants import DNFR_PRIMARY, THETA_PRIMARY, inject_defaults |
| 9 | +from tnfr.constants import DNFR_PRIMARY, EPI_PRIMARY, THETA_PRIMARY, inject_defaults |
10 | 10 | from tnfr.dynamics import set_delta_nfr_hook |
11 | 11 | from tnfr.node import NodeNX |
12 | 12 | from tnfr.operators import ( |
|
18 | 18 | reset_jitter_manager, |
19 | 19 | _um_candidate_iter, |
20 | 20 | ) |
| 21 | +from tnfr.operators.definitions import Coupling |
21 | 22 | from tnfr.validation import SequenceValidationResult |
22 | 23 | from tnfr.structural import Dissonance, create_nfr, run_sequence |
23 | 24 | from tnfr.types import Glyph |
@@ -361,3 +362,71 @@ def test_um_coupling_wraps_phases_near_pi_boundary(graph_canon): |
361 | 362 | new_theta = G.nodes[0]["theta"] |
362 | 363 | assert new_theta == pytest.approx(expected_theta) |
363 | 364 | assert -math.pi <= new_theta < math.pi |
| 365 | + |
| 366 | + |
| 367 | +def test_um_preserves_epi_identity(): |
| 368 | + """Verify that UM does not directly modify EPI. |
| 369 | + |
| 370 | + One of the fundamental principles of the UM (Coupling) operator is that it |
| 371 | + preserves the identity EPI of nodes during coupling. The coupling process |
| 372 | + synchronizes θ (phase) and potentially νf (structural frequency), but NEVER |
| 373 | + modifies EPI directly. This test validates this critical invariant. |
| 374 | + """ |
| 375 | + # Create two nodes with different EPI and phase values |
| 376 | + G, node1 = create_nfr("node1", epi=0.5, theta=0.0) |
| 377 | + G, node2 = create_nfr("node2", epi=0.7, theta=math.pi/4, graph=G) |
| 378 | + G.add_edge(node1, node2) |
| 379 | + |
| 380 | + # Record EPI values before applying UM |
| 381 | + epi_before_node1 = G.nodes[node1][EPI_PRIMARY] |
| 382 | + epi_before_node2 = G.nodes[node2][EPI_PRIMARY] |
| 383 | + |
| 384 | + # Apply UM (Coupling) operator to node1 |
| 385 | + Coupling()(G, node1) |
| 386 | + |
| 387 | + # Get EPI values after applying UM |
| 388 | + epi_after_node1 = G.nodes[node1][EPI_PRIMARY] |
| 389 | + epi_after_node2 = G.nodes[node2][EPI_PRIMARY] |
| 390 | + |
| 391 | + # EPI should be unchanged for both the node and its neighbor |
| 392 | + # Use strict tolerance (1e-9) to ensure no modification occurred |
| 393 | + assert abs(epi_after_node1 - epi_before_node1) < 1e-9, \ |
| 394 | + f"UM modified node1 EPI: {epi_before_node1} → {epi_after_node1}" |
| 395 | + assert abs(epi_after_node2 - epi_before_node2) < 1e-9, \ |
| 396 | + f"UM modified node2 EPI: {epi_before_node2} → {epi_after_node2}" |
| 397 | + |
| 398 | + # Verify the exact values remained constant |
| 399 | + assert epi_after_node1 == 0.5, "node1 EPI should remain 0.5" |
| 400 | + assert epi_after_node2 == 0.7, "node2 EPI should remain 0.7" |
| 401 | + |
| 402 | + |
| 403 | +def test_um_preserves_epi_identity_with_multiple_neighbors(): |
| 404 | + """Verify EPI preservation when coupling a node with multiple neighbors. |
| 405 | + |
| 406 | + Tests that UM maintains EPI identity even in complex network configurations |
| 407 | + where a node couples with multiple neighbors simultaneously. |
| 408 | + """ |
| 409 | + # Create a central node with multiple neighbors |
| 410 | + G, center = create_nfr("center", epi=0.6, theta=0.0) |
| 411 | + neighbors = [] |
| 412 | + for i in range(4): |
| 413 | + G, neighbor = create_nfr(f"neighbor_{i}", epi=0.5 + i*0.1, theta=i*math.pi/4, graph=G) |
| 414 | + G.add_edge(center, neighbor) |
| 415 | + neighbors.append(neighbor) |
| 416 | + |
| 417 | + # Record all EPI values before coupling |
| 418 | + epi_before = {center: G.nodes[center][EPI_PRIMARY]} |
| 419 | + for n in neighbors: |
| 420 | + epi_before[n] = G.nodes[n][EPI_PRIMARY] |
| 421 | + |
| 422 | + # Apply UM to the central node |
| 423 | + Coupling()(G, center) |
| 424 | + |
| 425 | + # Verify EPI unchanged for center node and all neighbors |
| 426 | + assert abs(G.nodes[center][EPI_PRIMARY] - epi_before[center]) < 1e-9, \ |
| 427 | + f"UM modified center EPI: {epi_before[center]} → {G.nodes[center][EPI_PRIMARY]}" |
| 428 | + |
| 429 | + for n in neighbors: |
| 430 | + epi_after = G.nodes[n][EPI_PRIMARY] |
| 431 | + assert abs(epi_after - epi_before[n]) < 1e-9, \ |
| 432 | + f"UM modified {n} EPI: {epi_before[n]} → {epi_after}" |
0 commit comments