Skip to content

Commit b15336b

Browse files
Copilotfermga
andcommitted
Add antiphase test and phase compatibility docs for THOL (Invariant #5)
Co-authored-by: fermga <203334638+fermga@users.noreply.github.com>
1 parent e61f58e commit b15336b

File tree

2 files changed

+227
-3
lines changed

2 files changed

+227
-3
lines changed

docs/THOL_ENCAPSULATION_GUIDE.md

Lines changed: 161 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,164 @@ Ensure the added operator makes **semantic sense** for your application:
578578

579579
---
580580

581+
## Phase Compatibility in Propagation
582+
583+
**CRITICAL**: THOL propagation respects **AGENTS.md Invariant #5**: "No coupling is valid without explicit phase verification."
584+
585+
### Physical Basis
586+
587+
Sub-EPI propagation follows resonance physics, not arbitrary connectivity. Antiphase nodes (Δθ ≈ π) create **destructive interference**, preventing coherent propagation regardless of network topology.
588+
589+
### Implementation
590+
591+
From `src/tnfr/operators/metabolism.py::propagate_subepi_to_network()`:
592+
593+
```python
594+
# Compute coupling strength (phase alignment)
595+
phase_diff = abs(angle_diff(neighbor_theta, parent_theta))
596+
coupling_strength = 1.0 - (phase_diff / math.pi)
597+
598+
# Propagate only if sufficiently coupled
599+
if coupling_strength >= min_coupling_strength:
600+
# Attenuate and inject sub-EPI
601+
attenuated_epi = sub_epi_magnitude * attenuation * coupling_strength
602+
# ...
603+
```
604+
605+
**Formula**:
606+
```
607+
coupling_strength = 1.0 - (|Δθ| / π)
608+
```
609+
610+
Where:
611+
- `Δθ` = phase difference between parent and neighbor (radians)
612+
- `coupling_strength`[0, 1]
613+
- Propagation occurs only if `coupling_strength ≥ threshold` (default: 0.5)
614+
615+
### Phase Compatibility Table
616+
617+
| Phase Difference (Δθ) | Coupling Strength | Propagates? (threshold=0.5) | Physics |
618+
|----------------------|-------------------|---------------------------|---------|
619+
| 0 (in-phase) | 1.0 | ✅ Yes | Perfect resonance |
620+
| π/4 (45°) | 0.75 | ✅ Yes | Strong coupling |
621+
| π/2 (90°) | 0.5 | ✅ Yes (at threshold) | Moderate coupling |
622+
| 3π/4 (135°) | 0.25 | ❌ No | Weak coupling |
623+
| π (antiphase) | 0.0 | ❌ No | Destructive interference |
624+
625+
### Canonical Constraints
626+
627+
**From AGENTS.md Invariant #5**:
628+
629+
Sub-EPIs propagate **ONLY** to neighbors with:
630+
1. **Phase compatibility**: `|Δθ| ≤ Δθ_max` (typically π/2)
631+
2. **Coupling threshold**: `coupling_strength ≥ threshold` (default: 0.5)
632+
3. **Explicit verification**: Phase difference computed before propagation
633+
634+
**Why This Matters**:
635+
636+
- **TNFR Physics**: Resonance requires phase alignment, not just network edges
637+
- **Prevents Chaos**: Antiphase propagation would create destructive interference
638+
- **Canonical Compliance**: Same phase verification as UM (Coupling) and RA (Resonance)
639+
640+
### Example: Phase Barrier Blocking Cascade
641+
642+
```python
643+
import math
644+
import networkx as nx
645+
from tnfr.operators.definitions import SelfOrganization
646+
from tnfr.constants import EPI_PRIMARY, THETA_PRIMARY, VF_PRIMARY, DNFR_PRIMARY
647+
648+
# Create network with phase barrier
649+
G = nx.Graph()
650+
651+
# Cluster A: coherent phases
652+
G.add_node(0, **{EPI_PRIMARY: 0.50, THETA_PRIMARY: 0.1, VF_PRIMARY: 1.0, DNFR_PRIMARY: 0.10})
653+
G.add_node(1, **{EPI_PRIMARY: 0.45, THETA_PRIMARY: 0.12, VF_PRIMARY: 1.0, DNFR_PRIMARY: 0.10})
654+
655+
# Node 2: phase barrier (antiphase)
656+
G.add_node(2, **{EPI_PRIMARY: 0.45, THETA_PRIMARY: math.pi, VF_PRIMARY: 1.0, DNFR_PRIMARY: 0.10})
657+
658+
# Cluster B: isolated by barrier
659+
G.add_node(3, **{EPI_PRIMARY: 0.45, THETA_PRIMARY: math.pi + 0.1, VF_PRIMARY: 1.0, DNFR_PRIMARY: 0.10})
660+
661+
# Connect: 0-1-2-3
662+
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
663+
664+
# Enable propagation
665+
G.graph["THOL_PROPAGATION_ENABLED"] = True
666+
G.nodes[0]["epi_history"] = [0.05, 0.33, 0.50] # Bifurcation conditions
667+
668+
# Trigger cascade from node 0
669+
SelfOrganization()(G, 0)
670+
671+
# Result: Propagation reaches node 1, stops at phase barrier (node 2)
672+
# Node 3 is NOT affected due to phase incompatibility
673+
```
674+
675+
### Testing Phase Verification
676+
677+
From `tests/integration/test_thol_propagation.py`:
678+
679+
```python
680+
def test_thol_rejects_antiphase_propagation():
681+
"""THOL must reject propagation to antiphase neighbors (Invariant #5)."""
682+
import math
683+
684+
G = nx.Graph()
685+
G.add_node(0, epi=0.50, vf=1.0, theta=0.0, delta_nfr=0.15)
686+
G.add_node(1, epi=0.50, vf=1.0, theta=math.pi, delta_nfr=0.05) # Antiphase
687+
G.add_edge(0, 1)
688+
689+
G.graph["THOL_PROPAGATION_ENABLED"] = True
690+
G.nodes[0]["epi_history"] = [0.05, 0.33, 0.50]
691+
692+
epi_1_before = G.nodes[1]["epi"]
693+
SelfOrganization()(G, 0)
694+
epi_1_after = G.nodes[1]["epi"]
695+
696+
# Antiphase neighbor should be rejected
697+
assert epi_1_after == epi_1_before, "Invariant #5 violation"
698+
```
699+
700+
### Configuration
701+
702+
Control phase verification via graph parameters:
703+
704+
```python
705+
# Strict phase compatibility (default)
706+
G.graph["THOL_MIN_COUPLING_FOR_PROPAGATION"] = 0.5 # Δθ ≤ π/2
707+
708+
# Relaxed (allow weaker coupling)
709+
G.graph["THOL_MIN_COUPLING_FOR_PROPAGATION"] = 0.3 # Δθ ≤ ~2.2 rad
710+
711+
# Very strict (only near-in-phase)
712+
G.graph["THOL_MIN_COUPLING_FOR_PROPAGATION"] = 0.8 # Δθ ≤ ~0.6 rad
713+
```
714+
715+
**Canonical Default**: 0.5 (π/2 maximum phase difference)
716+
717+
### Cross-Operator Consistency
718+
719+
THOL phase verification is **consistent** with other TNFR operators:
720+
721+
| Operator | Phase Verification | Formula | Use Case |
722+
|----------|-------------------|---------|----------|
723+
| **UM** (Coupling) | Consensus phase | `arctan2(Σsin(θ), Σcos(θ))` | Bidirectional synchronization |
724+
| **RA** (Resonance) | Circular mean | `\|⟨e^(iθ)⟩\|` (Kuramoto) | Network-wide propagation |
725+
| **THOL** (Propagation) | Direct difference | `1.0 - (\|Δθ\| / π)` | Unidirectional sub-EPI transfer |
726+
727+
All three enforce phase compatibility before structural modifications, ensuring resonance physics integrity.
728+
729+
### References
730+
731+
- **AGENTS.md**: Invariant #5 (Phase Verification)
732+
- **UNIFIED_GRAMMAR_RULES.md**: U3 (Resonant Coupling)
733+
- **src/tnfr/operators/metabolism.py**: Lines 284-331 (propagate_subepi_to_network)
734+
- **src/tnfr/utils/numeric.py**: Lines 72-75 (angle_diff utility)
735+
- **tests/integration/test_thol_propagation.py**: Lines 220-290 (test_thol_rejects_antiphase_propagation)
736+
737+
---
738+
581739
## Additional Resources
582740

583741
- **[THOL_CONFIGURATION_REFERENCE.md](THOL_CONFIGURATION_REFERENCE.md)**: Complete THOL parameter reference with canonical constraints
@@ -589,6 +747,6 @@ Ensure the added operator makes **semantic sense** for your application:
589747

590748
---
591749

592-
*Last updated: 2025-11-08*
593-
*Version: 1.0.0 (Initial release with Grammar 2.0)*
594-
*Related PR: #[PR_NUMBER] - Recursive THOL validation with encapsulation*
750+
*Last updated: 2025-11-09*
751+
*Version: 1.1.0 (Added Phase Compatibility section)*
752+
*Related PR: Verify phase validation in THOL sub-EPI propagation (Invariant #5)*

tests/integration/test_thol_propagation.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,72 @@ def test_thol_propagation_can_be_disabled(self):
215215
propagations = G.graph.get("thol_propagations", [])
216216
assert len(propagations) == 0, "No propagations should be recorded"
217217

218+
def test_thol_rejects_antiphase_propagation(self):
219+
"""THOL must reject propagation to neighbors with antiphase (Invariant #5).
220+
221+
This test explicitly validates AGENTS.md Invariant #5:
222+
"No coupling is valid without explicit phase verification."
223+
224+
THOL propagation uses phase-based coupling strength:
225+
coupling_strength = 1.0 - (|Δθ| / π)
226+
227+
For antiphase nodes (Δθ = π), coupling_strength = 0, which is below
228+
the minimum threshold, thus blocking propagation as required by physics.
229+
"""
230+
import math
231+
232+
G = nx.Graph()
233+
# Node 0: phase = 0.0
234+
G.add_node(
235+
0,
236+
**{
237+
EPI_PRIMARY: 0.50,
238+
VF_PRIMARY: 1.0,
239+
THETA_PRIMARY: 0.0,
240+
DNFR_PRIMARY: 0.15,
241+
}
242+
)
243+
# Node 1: phase = π (antiphase - destructive interference)
244+
G.add_node(
245+
1,
246+
**{
247+
EPI_PRIMARY: 0.50,
248+
VF_PRIMARY: 1.0,
249+
THETA_PRIMARY: math.pi,
250+
DNFR_PRIMARY: 0.05,
251+
}
252+
)
253+
G.add_edge(0, 1)
254+
255+
# Enable propagation with standard threshold
256+
G.graph["THOL_PROPAGATION_ENABLED"] = True
257+
G.graph["THOL_MIN_COUPLING_FOR_PROPAGATION"] = 0.5
258+
259+
# Build EPI history for bifurcation
260+
G.nodes[0]["epi_history"] = [0.05, 0.33, 0.50]
261+
262+
epi_1_before = G.nodes[1][EPI_PRIMARY]
263+
264+
# Apply THOL
265+
SelfOrganization()(G, 0)
266+
267+
epi_1_after = G.nodes[1][EPI_PRIMARY]
268+
269+
# Verify: propagation should NOT have occurred to antiphase neighbor
270+
assert (
271+
epi_1_after == epi_1_before
272+
), "Antiphase neighbor must be rejected (Invariant #5)"
273+
274+
# Verify telemetry: no propagation to node 1
275+
propagations = G.graph.get("thol_propagations", [])
276+
if propagations:
277+
for prop in propagations:
278+
if prop["source_node"] == 0:
279+
affected_neighbors = [n for n, _ in prop["propagations"]]
280+
assert (
281+
1 not in affected_neighbors
282+
), "Antiphase neighbor should not appear in propagation list"
283+
218284

219285
class TestTHOLCascades:
220286
"""Test cascade triggering across network chains."""

0 commit comments

Comments
 (0)