Skip to content

Commit c07d2e8

Browse files
Copilotfermga
andcommitted
feat: Add hierarchical depth telemetry for nested THOL bifurcations
- Add bifurcation_level and hierarchy_path fields to sub_epi records - Implement compute_hierarchical_depth() for recursive depth measurement - Add depth validation with THOL_MAX_BIFURCATION_DEPTH parameter - Create hierarchy visualization utility (print_bifurcation_hierarchy) - Add comprehensive tests for 1, 2, and 3-level nested bifurcations - Fix missing angle_diff import in metabolism.py Co-authored-by: fermga <203334638+fermga@users.noreply.github.com>
1 parent 821e3c1 commit c07d2e8

File tree

5 files changed

+838
-3
lines changed

5 files changed

+838
-3
lines changed

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

src/tnfr/operators/metabolism.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@
4141
from ..alias import get_attr
4242
from ..constants.aliases import ALIAS_EPI, ALIAS_THETA
4343
from ..utils import get_numpy
44+
from ..utils.numeric import angle_diff
4445

4546
__all__ = [
4647
"capture_network_signals",
4748
"metabolize_signals_into_subepi",
4849
"propagate_subepi_to_network",
4950
"compute_cascade_depth",
51+
"compute_hierarchical_depth",
5052
"compute_propagation_radius",
5153
"compute_subepi_collective_coherence",
5254
"compute_metabolic_activity_index",
@@ -414,6 +416,76 @@ def compute_cascade_depth(G: TNFRGraph, node: NodeId) -> int:
414416
return max_depth
415417

416418

419+
def compute_hierarchical_depth(G: TNFRGraph, node: NodeId) -> int:
420+
"""Compute maximum bifurcation depth from node using recursive traversal.
421+
422+
Traverses sub-EPI hierarchy recursively to find the maximum bifurcation_level
423+
across all nested branches. This provides accurate hierarchical telemetry for
424+
nested THOL bifurcations, supporting operational fractality analysis.
425+
426+
Parameters
427+
----------
428+
G : TNFRGraph
429+
Network graph
430+
node : NodeId
431+
Root node to measure depth from
432+
433+
Returns
434+
-------
435+
int
436+
Maximum bifurcation depth (0 = no bifurcations, 1 = single-level, etc.)
437+
438+
Notes
439+
-----
440+
TNFR Principle: Hierarchical depth reflects operational fractality
441+
(Invariant #7) - the ability of sub-EPIs to bifurcate recursively,
442+
creating multi-scale emergent structures.
443+
444+
This function recursively traverses all branches to find the deepest
445+
bifurcation_level, providing precise depth tracking for:
446+
- Debugging complex nested structures
447+
- Validating depth limits
448+
- Analyzing bifurcation patterns
449+
- Performance monitoring
450+
451+
Examples
452+
--------
453+
>>> # Node with no bifurcations
454+
>>> compute_hierarchical_depth(G, node)
455+
0
456+
457+
>>> # Node with single-level bifurcation
458+
>>> compute_hierarchical_depth(G, node_with_subs)
459+
1
460+
461+
>>> # Node with 2-level nested bifurcation
462+
>>> compute_hierarchical_depth(G, node_nested)
463+
2
464+
"""
465+
sub_epis = G.nodes[node].get("sub_epis", [])
466+
467+
if not sub_epis:
468+
return 0
469+
470+
# Recursively find the maximum bifurcation_level across all branches
471+
max_level = 0
472+
for sub_epi in sub_epis:
473+
# Get this sub-EPI's bifurcation level
474+
level = sub_epi.get("bifurcation_level", 1)
475+
max_level = max(max_level, level)
476+
477+
# Recurse into sub-node if it exists to find deeper levels
478+
sub_node_id = sub_epi.get("node_id")
479+
if sub_node_id and sub_node_id in G.nodes:
480+
# Recursively check this sub-node's depth
481+
sub_depth = compute_hierarchical_depth(G, sub_node_id)
482+
# If sub-node has bifurcations, its depth represents deeper levels
483+
if sub_depth > 0:
484+
max_level = max(max_level, sub_depth)
485+
486+
return max_level
487+
488+
417489
def compute_propagation_radius(G: TNFRGraph) -> int:
418490
"""Count total unique nodes affected by THOL cascades.
419491

src/tnfr/visualization/__init__.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
- Health metrics dashboards with radar charts and gauges
66
- Pattern analysis with component highlighting
77
- Frequency timelines showing structural evolution
8-
- Cascade propagation and temporal dynamics (NEW)
8+
- Cascade propagation and temporal dynamics
9+
- Hierarchical bifurcation structures (NEW)
910
10-
Requires matplotlib for plotting. Install with::
11+
Requires matplotlib for plotting (optional). Install with::
1112
1213
pip install tnfr[viz]
1314
15+
Hierarchy visualization (ASCII) has no external dependencies.
16+
1417
Examples
1518
--------
1619
>>> from tnfr.visualization import SequenceVisualizer
@@ -23,12 +26,22 @@
2326
>>> fig, ax = visualizer.plot_sequence_flow(sequence, result.health_metrics)
2427
>>> fig.savefig("sequence_flow.png")
2528
26-
>>> # Cascade visualization (NEW)
29+
>>> # Cascade visualization
2730
>>> from tnfr.visualization import plot_cascade_propagation, plot_cascade_timeline
2831
>>> fig = plot_cascade_propagation(G)
2932
>>> fig.savefig("cascade_propagation.png")
33+
34+
>>> # Hierarchy visualization (no matplotlib required)
35+
>>> from tnfr.visualization import print_bifurcation_hierarchy
36+
>>> print_bifurcation_hierarchy(G, node)
3037
"""
3138

39+
# Always available (no dependencies)
40+
from .hierarchy import (
41+
print_bifurcation_hierarchy,
42+
get_hierarchy_info,
43+
)
44+
3245
_import_error: ImportError | None = None
3346

3447
try:
@@ -44,6 +57,8 @@
4457
"plot_cascade_propagation",
4558
"plot_cascade_timeline",
4659
"plot_cascade_metrics_summary",
60+
"print_bifurcation_hierarchy",
61+
"get_hierarchy_info",
4762
]
4863
except ImportError as _import_err:
4964
_import_error = _import_err
@@ -78,4 +93,6 @@ def _missing_viz_dependency(*args: _Any, **kwargs: _Any) -> None:
7893
"plot_cascade_propagation",
7994
"plot_cascade_timeline",
8095
"plot_cascade_metrics_summary",
96+
"print_bifurcation_hierarchy",
97+
"get_hierarchy_info",
8198
]

0 commit comments

Comments
 (0)