Skip to content

Commit 5885d14

Browse files
authored
Merge pull request #2877 from fermga/copilot/add-hierarchical-telemetry
Add hierarchical depth telemetry for nested THOL bifurcations
2 parents dad323f + 1d5cf8e commit 5885d14

File tree

6 files changed

+1046
-3
lines changed

6 files changed

+1046
-3
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
"""Demonstration of hierarchical depth telemetry for nested THOL bifurcations.
2+
3+
This example showcases the new hierarchical tracking features for THOL
4+
(Self-organization) operator, demonstrating operational fractality across
5+
multiple nested levels.
6+
7+
New Features Demonstrated:
8+
1. bifurcation_level tracking in sub_epi records
9+
2. hierarchy_path for full ancestor chain
10+
3. compute_hierarchical_depth() for recursive depth measurement
11+
4. print_bifurcation_hierarchy() for ASCII visualization
12+
5. Depth validation warnings for excessive nesting
13+
"""
14+
15+
from tnfr.structural import create_nfr
16+
from tnfr.operators.definitions import SelfOrganization
17+
from tnfr.operators.metabolism import compute_hierarchical_depth
18+
from tnfr.visualization import print_bifurcation_hierarchy, get_hierarchy_info
19+
20+
21+
def demo_single_level_bifurcation():
22+
"""Demonstrate single-level bifurcation with hierarchy telemetry."""
23+
print("=" * 60)
24+
print("DEMO 1: Single-Level Bifurcation")
25+
print("=" * 60)
26+
27+
# Create node
28+
G, node = create_nfr("root", epi=0.50, vf=1.0, theta=0.1)
29+
30+
# Set up for bifurcation (proper acceleration)
31+
G.nodes[node]["epi_history"] = [0.20, 0.38, 0.50]
32+
33+
# Apply THOL
34+
SelfOrganization()(G, node, tau=0.05)
35+
36+
# Check hierarchy metadata
37+
sub_epi = G.nodes[node]["sub_epis"][0]
38+
print(f"\n📊 Sub-EPI Metadata:")
39+
print(f" - Bifurcation level: {sub_epi['bifurcation_level']}")
40+
print(f" - Hierarchy path: {sub_epi['hierarchy_path']}")
41+
print(f" - Node ID: {sub_epi['node_id']}")
42+
43+
# Compute depth
44+
depth = compute_hierarchical_depth(G, node)
45+
print(f"\n📏 Maximum depth: {depth}")
46+
47+
# Visualize
48+
print(f"\n🌳 Hierarchy Visualization:")
49+
print_bifurcation_hierarchy(G, node)
50+
51+
# Get info
52+
info = get_hierarchy_info(G, node)
53+
print(f"\n📈 Hierarchy Info:")
54+
print(f" - Max depth: {info['max_depth']}")
55+
print(f" - Total descendants: {info['total_descendants']}")
56+
print()
57+
58+
59+
def demo_two_level_nested_bifurcation():
60+
"""Demonstrate two-level nested bifurcation."""
61+
print("=" * 60)
62+
print("DEMO 2: Two-Level Nested Bifurcation")
63+
print("=" * 60)
64+
65+
# Create root node
66+
G, node = create_nfr("root", epi=0.50, vf=1.0, theta=0.1)
67+
68+
# Level 1 bifurcation
69+
print("\n🔹 Creating Level 1 bifurcation...")
70+
G.nodes[node]["epi_history"] = [0.20, 0.38, 0.50]
71+
SelfOrganization()(G, node, tau=0.05)
72+
73+
# Get sub-node
74+
sub_node = G.nodes[node]["sub_epis"][0]["node_id"]
75+
print(f" Sub-node created: {sub_node}")
76+
print(f" Bifurcation level: {G.nodes[sub_node]['_bifurcation_level']}")
77+
78+
# Level 2 bifurcation (nested)
79+
print("\n🔹 Creating Level 2 bifurcation (nested)...")
80+
G.nodes[sub_node]["epi_history"] = [0.05, 0.15, 0.35]
81+
SelfOrganization()(G, sub_node, tau=0.05)
82+
83+
# Get nested sub-node
84+
nested_sub_node = G.nodes[sub_node]["sub_epis"][0]["node_id"]
85+
print(f" Nested sub-node created: {nested_sub_node}")
86+
print(f" Bifurcation level: {G.nodes[nested_sub_node]['_bifurcation_level']}")
87+
88+
# Check depth
89+
depth = compute_hierarchical_depth(G, node)
90+
print(f"\n📏 Maximum depth: {depth}")
91+
92+
# Visualize full hierarchy
93+
print(f"\n🌳 Complete Hierarchy:")
94+
print_bifurcation_hierarchy(G, node)
95+
96+
# Show hierarchy paths
97+
print(f"\n🗺️ Hierarchy Paths:")
98+
for i, se in enumerate(G.nodes[node]["sub_epis"], 1):
99+
print(f" Level 1 Sub-EPI {i}: {se['hierarchy_path']}")
100+
101+
for i, se in enumerate(G.nodes[sub_node]["sub_epis"], 1):
102+
print(f" Level 2 Sub-EPI {i}: {se['hierarchy_path']}")
103+
print()
104+
105+
106+
def demo_three_level_nested_bifurcation():
107+
"""Demonstrate three-level nested bifurcation."""
108+
print("=" * 60)
109+
print("DEMO 3: Three-Level Nested Bifurcation")
110+
print("=" * 60)
111+
112+
# Create root
113+
G, node = create_nfr("alpha", epi=0.50, vf=1.0, theta=0.1)
114+
115+
# Build three levels
116+
print("\n🔹 Building three-level hierarchy...")
117+
118+
# Level 1
119+
G.nodes[node]["epi_history"] = [0.20, 0.38, 0.50]
120+
SelfOrganization()(G, node, tau=0.05)
121+
sub_1 = G.nodes[node]["sub_epis"][0]["node_id"]
122+
print(f" Level 1: {sub_1} (level={G.nodes[sub_1]['_bifurcation_level']})")
123+
124+
# Level 2
125+
G.nodes[sub_1]["epi_history"] = [0.05, 0.15, 0.35]
126+
SelfOrganization()(G, sub_1, tau=0.05)
127+
sub_2 = G.nodes[sub_1]["sub_epis"][0]["node_id"]
128+
print(f" Level 2: {sub_2} (level={G.nodes[sub_2]['_bifurcation_level']})")
129+
130+
# Level 3
131+
G.nodes[sub_2]["epi_history"] = [0.02, 0.08, 0.20]
132+
SelfOrganization()(G, sub_2, tau=0.05)
133+
sub_3 = G.nodes[sub_2]["sub_epis"][0]["node_id"]
134+
print(f" Level 3: {sub_3} (level={G.nodes[sub_3]['_bifurcation_level']})")
135+
136+
# Compute depth
137+
depth = compute_hierarchical_depth(G, node)
138+
print(f"\n📏 Maximum depth: {depth}")
139+
140+
# Visualize
141+
print(f"\n🌳 Three-Level Hierarchy:")
142+
print_bifurcation_hierarchy(G, node)
143+
144+
# Get comprehensive info
145+
info = get_hierarchy_info(G, node)
146+
print(f"\n📊 Comprehensive Info:")
147+
print(f" - Root node: {info['node']}")
148+
print(f" - Root EPI: {info['epi']:.3f}")
149+
print(f" - Max depth: {info['max_depth']}")
150+
print(f" - Total descendants: {info['total_descendants']}")
151+
print()
152+
153+
154+
def demo_depth_validation():
155+
"""Demonstrate depth validation warnings."""
156+
print("=" * 60)
157+
print("DEMO 4: Depth Validation (Warning System)")
158+
print("=" * 60)
159+
160+
# Create node with low max depth
161+
G, node = create_nfr("test", epi=0.50, vf=1.0, theta=0.1)
162+
G.graph["THOL_MAX_BIFURCATION_DEPTH"] = 1 # Low threshold for demo
163+
164+
print("\n⚙️ Configuration:")
165+
print(f" THOL_MAX_BIFURCATION_DEPTH = 1")
166+
167+
# Level 1 - no warning
168+
print("\n🔹 Creating Level 1 (no warning expected)...")
169+
G.nodes[node]["epi_history"] = [0.20, 0.38, 0.50]
170+
SelfOrganization()(G, node, tau=0.05)
171+
print(" ✅ Level 1 created successfully")
172+
173+
# Level 2 - should warn
174+
print("\n🔹 Creating Level 2 (warning expected)...")
175+
sub_node = G.nodes[node]["sub_epis"][0]["node_id"]
176+
G.nodes[sub_node]["epi_history"] = [0.05, 0.15, 0.35]
177+
178+
# This will trigger warning
179+
print(" ⚠️ Attempting bifurcation at max depth...")
180+
SelfOrganization()(G, sub_node, tau=0.05)
181+
182+
# Check warning was recorded
183+
if G.nodes[sub_node].get("_thol_max_depth_warning"):
184+
print(" ✅ Depth warning recorded in node")
185+
186+
events = G.graph.get("thol_depth_warnings", [])
187+
if events:
188+
print(f" ✅ Depth warning recorded in graph (count: {len(events)})")
189+
print(f" Event details: {events[0]}")
190+
191+
print()
192+
193+
194+
if __name__ == "__main__":
195+
print("\n" + "=" * 60)
196+
print("HIERARCHICAL DEPTH TELEMETRY DEMONSTRATION")
197+
print("Nested THOL Bifurcation Tracking")
198+
print("=" * 60 + "\n")
199+
200+
# Run all demos
201+
demo_single_level_bifurcation()
202+
demo_two_level_nested_bifurcation()
203+
demo_three_level_nested_bifurcation()
204+
demo_depth_validation()
205+
206+
print("=" * 60)
207+
print("✨ All demonstrations completed successfully!")
208+
print("=" * 60)

src/tnfr/operators/definitions.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2718,6 +2718,8 @@ def __call__(self, G: TNFRGraph, node: Any, **kw: Any) -> None:
27182718

27192719
# Bifurcate if acceleration exceeds threshold
27202720
if d2_epi > tau:
2721+
# Validate depth before bifurcation
2722+
self._validate_bifurcation_depth(G, node)
27212723
self._spawn_sub_epi(G, node, d2_epi=d2_epi, tau=tau)
27222724

27232725
# CANONICAL VALIDATION: Verify collective coherence of sub-EPIs
@@ -2827,6 +2829,14 @@ def _spawn_sub_epi(
28272829
# Get current timestamp from glyph history length
28282830
timestamp = len(G.nodes[node].get("glyph_history", []))
28292831

2832+
# Determine parent bifurcation level for hierarchical telemetry
2833+
parent_level = G.nodes[node].get("_bifurcation_level", 0)
2834+
child_level = parent_level + 1
2835+
2836+
# Construct hierarchy path for full traceability
2837+
parent_path = G.nodes[node].get("_hierarchy_path", [])
2838+
child_path = parent_path + [node]
2839+
28302840
# ARCHITECTURAL: Create sub-EPI as independent NFR node
28312841
# This enables operational fractality - recursive operators, hierarchical metrics
28322842
sub_node_id = self._create_sub_node(
@@ -2835,6 +2845,8 @@ def _spawn_sub_epi(
28352845
sub_epi=sub_epi_value,
28362846
parent_vf=parent_vf,
28372847
parent_theta=parent_theta,
2848+
child_level=child_level,
2849+
child_path=child_path,
28382850
)
28392851

28402852
# Store sub-EPI metadata for telemetry and backward compatibility
@@ -2847,6 +2859,8 @@ def _spawn_sub_epi(
28472859
"node_id": sub_node_id, # Reference to independent node
28482860
"metabolized": network_signals is not None and metabolic_enabled,
28492861
"network_signals": network_signals,
2862+
"bifurcation_level": child_level, # Hierarchical depth tracking
2863+
"hierarchy_path": child_path, # Full parent chain for traceability
28502864
}
28512865

28522866
# Keep metadata list for telemetry/metrics backward compatibility
@@ -2883,6 +2897,8 @@ def _create_sub_node(
28832897
sub_epi: float,
28842898
parent_vf: float,
28852899
parent_theta: float,
2900+
child_level: int,
2901+
child_path: list,
28862902
) -> str:
28872903
"""Create sub-EPI as independent NFR node for operational fractality.
28882904
@@ -2901,6 +2917,10 @@ def _create_sub_node(
29012917
Parent's structural frequency (inherited with damping)
29022918
parent_theta : float
29032919
Parent's phase (inherited)
2920+
child_level : int
2921+
Bifurcation level for hierarchical tracking
2922+
child_path : list
2923+
Full hierarchy path (ancestor chain)
29042924
29052925
Returns
29062926
-------
@@ -2931,6 +2951,8 @@ def _create_sub_node(
29312951
DNFR_PRIMARY: 0.0,
29322952
"parent_node": parent_node,
29332953
"hierarchy_level": parent_hierarchy_level + 1,
2954+
"_bifurcation_level": child_level, # Hierarchical depth tracking
2955+
"_hierarchy_path": child_path, # Full ancestor chain
29342956
"epi_history": [
29352957
float(sub_epi)
29362958
], # Initialize history for future bifurcation
@@ -2954,6 +2976,58 @@ def _create_sub_node(
29542976

29552977
return sub_node_id
29562978

2979+
def _validate_bifurcation_depth(self, G: TNFRGraph, node: Any) -> None:
2980+
"""Validate bifurcation depth before creating new sub-EPI.
2981+
2982+
Checks if the current bifurcation level is at or exceeds the configured
2983+
maximum depth. Issues a warning if depth limit is reached but still
2984+
allows the bifurcation (for flexibility in research contexts).
2985+
2986+
Parameters
2987+
----------
2988+
G : TNFRGraph
2989+
Graph containing the node
2990+
node : Any
2991+
Node about to undergo bifurcation
2992+
2993+
Notes
2994+
-----
2995+
TNFR Principle: Deep nesting reflects operational fractality (Invariant #7),
2996+
but excessive depth may impact performance and interpretability. This
2997+
validation provides observability without hard constraints.
2998+
2999+
The warning allows tracking when hierarchies become complex, enabling
3000+
researchers to study bifurcation patterns while maintaining system
3001+
performance awareness.
3002+
"""
3003+
import logging
3004+
3005+
# Get current bifurcation level
3006+
current_level = G.nodes[node].get("_bifurcation_level", 0)
3007+
3008+
# Get max depth from graph config (default: 5 levels)
3009+
max_depth = int(G.graph.get("THOL_MAX_BIFURCATION_DEPTH", 5))
3010+
3011+
# Warn if at or exceeding maximum
3012+
if current_level >= max_depth:
3013+
logger = logging.getLogger(__name__)
3014+
logger.warning(
3015+
f"Node {node}: Bifurcation depth ({current_level}) at/exceeds "
3016+
f"maximum ({max_depth}). Deep nesting may impact performance. "
3017+
f"Consider adjusting THOL_MAX_BIFURCATION_DEPTH if intended."
3018+
)
3019+
3020+
# Record warning in node for telemetry
3021+
G.nodes[node]["_thol_max_depth_warning"] = True
3022+
3023+
# Record event for analysis
3024+
events = G.graph.setdefault("thol_depth_warnings", [])
3025+
events.append({
3026+
"node": node,
3027+
"depth": current_level,
3028+
"max_depth": max_depth,
3029+
})
3030+
29573031
def _validate_collective_coherence(self, G: TNFRGraph, node: Any) -> None:
29583032
"""Validate collective coherence of sub-EPI ensemble after bifurcation.
29593033

0 commit comments

Comments
 (0)