Skip to content

Commit 6d9955a

Browse files
authored
Merge pull request #2875 from fermga/copilot/refactor-phase-alignment-functions
Unify phase compatibility calculations across UM and THOL operators
2 parents aaa1e49 + 542b073 commit 6d9955a

File tree

6 files changed

+886
-16
lines changed

6 files changed

+886
-16
lines changed

src/tnfr/metrics/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
compute_learning_plasticity,
3333
glyph_history_to_operator_names,
3434
)
35+
from .phase_compatibility import (
36+
compute_network_phase_alignment,
37+
compute_phase_coupling_strength,
38+
is_phase_compatible,
39+
)
3540
from .reporting import (
3641
Tg_by_node,
3742
Tg_global,
@@ -68,4 +73,7 @@
6873
"compute_bifurcation_rate",
6974
"compute_metabolic_efficiency",
7075
"compute_emergence_index",
76+
"compute_phase_coupling_strength",
77+
"is_phase_compatible",
78+
"compute_network_phase_alignment",
7179
)
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
"""Unified phase compatibility calculations for TNFR operators.
2+
3+
This module provides canonical implementations of phase-based coupling strength
4+
calculations used by multiple TNFR operators (UM, RA, THOL). All operators that
5+
perform phase-based coupling or propagation MUST use these functions to ensure
6+
consistency with TNFR physics and Invariant #5.
7+
8+
Physical Foundation
9+
-------------------
10+
11+
**Phase Compatibility in TNFR:**
12+
13+
Coupling between nodes requires phase synchronization. Destructive interference
14+
occurs when phases are misaligned (antiphase), while constructive interference
15+
occurs when phases align. The coupling strength formula reflects this physics:
16+
17+
.. math::
18+
\\text{coupling_strength} = 1.0 - \\frac{|\\Delta\\phi|}{\\pi}
19+
20+
where Δφ is the phase difference in radians.
21+
22+
**Physical Interpretation:**
23+
24+
- Δφ = 0 (perfect alignment) → coupling = 1.0 (maximum constructive interference)
25+
- Δφ = π/2 (orthogonal) → coupling = 0.5 (partial coupling)
26+
- Δφ = π (antiphase) → coupling = 0.0 (destructive interference)
27+
28+
**TNFR Invariant #5:** "No coupling without explicit phase verification"
29+
(see AGENTS.md). All coupling operations must verify phase compatibility
30+
before propagating structural information.
31+
32+
Canonical Usage
33+
---------------
34+
35+
**Operators Using This Module:**
36+
37+
1. **UM (Coupling)**: Phase synchronization and network formation
38+
2. **RA (Resonance)**: Coherence propagation through phase-aligned paths
39+
3. **THOL (Self-organization)**: Sub-EPI propagation to coupled neighbors
40+
41+
**Before Refactoring:**
42+
43+
Each operator implemented its own phase compatibility calculation, leading
44+
to potential inconsistencies and maintenance burden.
45+
46+
**After Refactoring:**
47+
48+
All operators use the canonical functions defined here, ensuring theoretical
49+
consistency and simplifying validation against TNFR physics.
50+
51+
Examples
52+
--------
53+
54+
**Basic coupling strength calculation:**
55+
56+
>>> import math
57+
>>> # Perfect alignment
58+
>>> compute_phase_coupling_strength(0.0, 0.0)
59+
1.0
60+
>>> # Orthogonal phases
61+
>>> compute_phase_coupling_strength(0.0, math.pi/2)
62+
0.5
63+
>>> # Antiphase (destructive)
64+
>>> round(compute_phase_coupling_strength(0.0, math.pi), 10)
65+
0.0
66+
67+
**Phase compatibility check:**
68+
69+
>>> # Check if phases are compatible for coupling
70+
>>> is_phase_compatible(0.0, 0.1, threshold=0.5)
71+
True
72+
>>> is_phase_compatible(0.0, math.pi, threshold=0.5)
73+
False
74+
75+
**Network phase alignment:**
76+
77+
>>> import networkx as nx
78+
>>> from tnfr.constants.aliases import ALIAS_THETA
79+
>>> G = nx.Graph()
80+
>>> G.add_edges_from([(0, 1), (1, 2)])
81+
>>> for i, theta in enumerate([0.0, 0.1, 0.2]):
82+
... G.nodes[i][ALIAS_THETA] = theta
83+
>>> alignment = compute_network_phase_alignment(G, node=1, radius=1)
84+
>>> 0.0 <= alignment <= 1.0
85+
True
86+
87+
See Also
88+
--------
89+
90+
operators.definitions : Operator implementations (UM, RA, THOL)
91+
metrics.phase_coherence : Kuramoto order parameter and phase metrics
92+
AGENTS.md : Invariant #5 - Phase Verification requirement
93+
UNIFIED_GRAMMAR_RULES.md : U3 - RESONANT COUPLING grammar rule
94+
95+
References
96+
----------
97+
98+
.. [1] TNFR.pdf § 2.3: Phase synchronization and coupling
99+
.. [2] AGENTS.md: Invariant #5 - No coupling without phase verification
100+
.. [3] UNIFIED_GRAMMAR_RULES.md: U3 - Resonant Coupling requires |φᵢ - φⱼ| ≤ Δφ_max
101+
"""
102+
103+
from __future__ import annotations
104+
105+
import math
106+
from typing import TYPE_CHECKING, Any
107+
108+
if TYPE_CHECKING:
109+
from ..types import TNFRGraph, NodeId
110+
111+
from ..utils.numeric import angle_diff
112+
113+
__all__ = [
114+
"compute_phase_coupling_strength",
115+
"is_phase_compatible",
116+
"compute_network_phase_alignment",
117+
]
118+
119+
120+
def compute_phase_coupling_strength(
121+
theta_a: float,
122+
theta_b: float,
123+
) -> float:
124+
"""Compute canonical coupling strength from phase difference.
125+
126+
This is the canonical TNFR formula for phase-based coupling strength,
127+
representing the degree of constructive vs. destructive interference
128+
between two oscillating nodes.
129+
130+
Parameters
131+
----------
132+
theta_a : float
133+
Phase of first node in radians [0, 2π)
134+
theta_b : float
135+
Phase of second node in radians [0, 2π)
136+
137+
Returns
138+
-------
139+
float
140+
Coupling strength in [0, 1]:
141+
- 1.0: Perfect phase alignment (Δφ = 0)
142+
- 0.5: Orthogonal phases (Δφ = π/2)
143+
- 0.0: Antiphase (Δφ = π, destructive interference)
144+
145+
Notes
146+
-----
147+
**Formula:**
148+
149+
.. math::
150+
\\text{coupling_strength} = 1.0 - \\frac{|\\text{angle_diff}(\\theta_b, \\theta_a)|}{\\pi}
151+
152+
The formula uses :func:`~tnfr.utils.numeric.angle_diff` to compute the
153+
shortest angular distance between phases, properly handling wrap-around
154+
at 2π boundaries.
155+
156+
**Physics:**
157+
158+
- Based on wave interference physics: aligned phases → constructive interference
159+
- Antiphase (Δφ = π) → destructive interference → zero coupling
160+
- Linear interpolation between extremes reflects gradual transition
161+
162+
**Used By:**
163+
164+
- UM (Coupling): For determining link formation and synchronization strength
165+
- RA (Resonance): For gating coherence propagation to neighbors
166+
- THOL (Self-organization): For sub-EPI propagation through coupled nodes
167+
168+
**Invariant #5:** This function implements the explicit phase verification
169+
required by TNFR Invariant #5 (AGENTS.md). All coupling operations must
170+
verify phase compatibility before propagating structural information.
171+
172+
Examples
173+
--------
174+
>>> import math
175+
>>> # Perfect alignment
176+
>>> compute_phase_coupling_strength(0.0, 0.0)
177+
1.0
178+
>>> # Small misalignment
179+
>>> compute_phase_coupling_strength(0.0, 0.1) # doctest: +ELLIPSIS
180+
0.96...
181+
>>> # Orthogonal phases
182+
>>> compute_phase_coupling_strength(0.0, math.pi/2)
183+
0.5
184+
>>> # Antiphase (destructive)
185+
>>> round(compute_phase_coupling_strength(0.0, math.pi), 10)
186+
0.0
187+
>>> # Wrap-around handling
188+
>>> compute_phase_coupling_strength(0.1, 2*math.pi - 0.1) # doctest: +ELLIPSIS
189+
0.93...
190+
191+
See Also
192+
--------
193+
is_phase_compatible : Boolean compatibility check with threshold
194+
angle_diff : Shortest angular distance between phases
195+
"""
196+
phase_diff = abs(angle_diff(theta_b, theta_a))
197+
return 1.0 - (phase_diff / math.pi)
198+
199+
200+
def is_phase_compatible(
201+
theta_a: float,
202+
theta_b: float,
203+
threshold: float = 0.5,
204+
) -> bool:
205+
"""Check if two phases are compatible for coupling/propagation.
206+
207+
Determines whether two nodes are sufficiently phase-aligned to support
208+
resonant coupling, based on a configurable coupling strength threshold.
209+
210+
Parameters
211+
----------
212+
theta_a : float
213+
Phase of first node in radians [0, 2π)
214+
theta_b : float
215+
Phase of second node in radians [0, 2π)
216+
threshold : float, default=0.5
217+
Minimum coupling strength required for compatibility [0, 1].
218+
Default 0.5 corresponds to maximum phase difference of π/2 (orthogonal).
219+
220+
Returns
221+
-------
222+
bool
223+
True if coupling_strength >= threshold (nodes are compatible)
224+
False if coupling_strength < threshold (nodes are incompatible)
225+
226+
Notes
227+
-----
228+
**Common Thresholds:**
229+
230+
- 0.5 (default): Allows coupling up to π/2 phase difference
231+
- 0.7: More restrictive, requires Δφ < π/2.1 (~95°)
232+
- 0.9: Very restrictive, requires Δφ < π/10 (~18°)
233+
234+
**Usage:**
235+
236+
- **UM (Coupling)**: Gate link formation based on phase compatibility
237+
- **RA (Resonance)**: Filter neighbors for coherence propagation
238+
- **THOL propagation**: Minimum coupling for sub-EPI propagation
239+
240+
**Invariant #5:** This function provides a boolean interface to the
241+
phase verification requirement (AGENTS.md Invariant #5).
242+
243+
Examples
244+
--------
245+
>>> import math
246+
>>> # In-phase: compatible
247+
>>> is_phase_compatible(0.0, 0.1, threshold=0.5)
248+
True
249+
>>> # Orthogonal: at threshold boundary
250+
>>> is_phase_compatible(0.0, math.pi/2, threshold=0.5)
251+
True
252+
>>> # Slightly beyond orthogonal: incompatible
253+
>>> is_phase_compatible(0.0, math.pi/2 + 0.1, threshold=0.5)
254+
False
255+
>>> # Antiphase: incompatible
256+
>>> is_phase_compatible(0.0, math.pi, threshold=0.5)
257+
False
258+
>>> # Higher threshold: more restrictive
259+
>>> is_phase_compatible(0.0, math.pi/4, threshold=0.9)
260+
False
261+
>>> is_phase_compatible(0.0, 0.1, threshold=0.9)
262+
True
263+
264+
See Also
265+
--------
266+
compute_phase_coupling_strength : Continuous coupling strength [0, 1]
267+
"""
268+
coupling = compute_phase_coupling_strength(theta_a, theta_b)
269+
return coupling >= threshold
270+
271+
272+
def compute_network_phase_alignment(
273+
G: TNFRGraph,
274+
node: NodeId,
275+
radius: int = 1,
276+
) -> float:
277+
"""Compute phase alignment in local neighborhood using Kuramoto order parameter.
278+
279+
This is a convenience wrapper around the existing
280+
:func:`~tnfr.metrics.phase_coherence.compute_phase_alignment` function,
281+
provided for API consistency within this module.
282+
283+
Parameters
284+
----------
285+
G : TNFRGraph
286+
TNFR network graph containing nodes with phase (theta) attributes
287+
node : NodeId
288+
Central node for neighborhood analysis
289+
radius : int, default=1
290+
Neighborhood radius in hops from central node
291+
292+
Returns
293+
-------
294+
float
295+
Phase alignment quality in [0, 1]:
296+
- 1.0: Perfect phase synchronization (all nodes aligned)
297+
- 0.0: Complete phase disorder (random phases)
298+
299+
Notes
300+
-----
301+
**Kuramoto Order Parameter:**
302+
303+
Measures collective phase synchrony using:
304+
305+
.. math::
306+
r = |\\frac{1}{N} \\sum_{j=1}^{N} e^{i\\theta_j}|
307+
308+
**Used By:**
309+
310+
- **RA (Resonance)**: Assess network coherence for propagation gating
311+
- **IL (Coherence)**: Validate phase locking effectiveness
312+
313+
**Implementation:**
314+
315+
This function delegates to the existing implementation in
316+
:mod:`tnfr.metrics.phase_coherence` to avoid code duplication
317+
while providing a unified API for phase compatibility calculations.
318+
319+
Examples
320+
--------
321+
>>> import networkx as nx
322+
>>> from tnfr.constants.aliases import ALIAS_THETA
323+
>>> G = nx.Graph()
324+
>>> G.add_edges_from([(0, 1), (1, 2), (2, 3)])
325+
>>> # Highly aligned phases
326+
>>> for i in range(4):
327+
... G.nodes[i][ALIAS_THETA] = i * 0.1
328+
>>> alignment = compute_network_phase_alignment(G, node=1, radius=1)
329+
>>> alignment > 0.9 # High alignment
330+
True
331+
>>> # Random phases
332+
>>> import math
333+
>>> G.nodes[0][ALIAS_THETA] = 0.0
334+
>>> G.nodes[1][ALIAS_THETA] = math.pi/3
335+
>>> G.nodes[2][ALIAS_THETA] = 2*math.pi/3
336+
>>> G.nodes[3][ALIAS_THETA] = math.pi
337+
>>> alignment = compute_network_phase_alignment(G, node=1, radius=1)
338+
>>> 0.0 <= alignment <= 1.0
339+
True
340+
341+
See Also
342+
--------
343+
metrics.phase_coherence.compute_phase_alignment : Underlying implementation
344+
metrics.phase_coherence.compute_global_phase_coherence : Global network metric
345+
"""
346+
# Import existing function to avoid duplication
347+
from .phase_coherence import compute_phase_alignment
348+
349+
return compute_phase_alignment(G, node, radius=radius)

0 commit comments

Comments
 (0)