Skip to content

Commit dff9c38

Browse files
Copilotfermga
andcommitted
Add cascade metrics and enhance THOL telemetry
Co-authored-by: fermga <203334638+fermga@users.noreply.github.com>
1 parent 8761490 commit dff9c38

File tree

3 files changed

+638
-11
lines changed

3 files changed

+638
-11
lines changed

src/tnfr/operators/metabolism.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
"capture_network_signals",
4747
"metabolize_signals_into_subepi",
4848
"propagate_subepi_to_network",
49+
"compute_cascade_depth",
50+
"compute_propagation_radius",
51+
"compute_subepi_collective_coherence",
52+
"compute_metabolic_activity_index",
4953
]
5054

5155

@@ -329,3 +333,185 @@ def propagate_subepi_to_network(
329333
G.nodes[neighbor]["epi_history"] = history[-10:] # Keep last 10
330334

331335
return propagations
336+
337+
338+
def compute_cascade_depth(G: TNFRGraph, node: NodeId) -> int:
339+
"""Compute maximum hierarchical depth of bifurcation cascade.
340+
341+
Recursively measures how many levels of nested sub-EPIs exist,
342+
where each sub-EPI can itself bifurcate into deeper levels.
343+
344+
Parameters
345+
----------
346+
G : TNFRGraph
347+
Graph containing bifurcation history
348+
node : NodeId
349+
Root node of cascade analysis
350+
351+
Returns
352+
-------
353+
int
354+
Maximum cascade depth (0 if no bifurcation occurred)
355+
356+
Examples
357+
--------
358+
>>> # Single-level bifurcation
359+
>>> compute_cascade_depth(G, node)
360+
1
361+
362+
>>> # Multi-level cascade (sub-EPIs bifurcated further)
363+
>>> compute_cascade_depth(G_complex, node)
364+
3
365+
366+
Notes
367+
-----
368+
TNFR Principle: Cascade depth measures the hierarchical complexity
369+
of emergent self-organization. Depth = 1 indicates direct bifurcation;
370+
depth > 1 indicates recursive, multi-scale emergence.
371+
"""
372+
sub_epis = G.nodes[node].get("sub_epis", [])
373+
if not sub_epis:
374+
return 0
375+
376+
# For now: depth = 1 (direct children)
377+
# TODO: If sub-EPIs become independent nodes, recurse
378+
max_depth = 1
379+
380+
for sub in sub_epis:
381+
# If sub-EPI spawned its own children (future enhancement)
382+
nested_depth = sub.get("cascade_depth", 0)
383+
max_depth = max(max_depth, 1 + nested_depth)
384+
385+
return max_depth
386+
387+
388+
def compute_propagation_radius(G: TNFRGraph) -> int:
389+
"""Count total unique nodes affected by THOL cascades.
390+
391+
Parameters
392+
----------
393+
G : TNFRGraph
394+
Graph with THOL propagation history
395+
396+
Returns
397+
-------
398+
int
399+
Number of nodes reached by at least one propagation event
400+
401+
Notes
402+
-----
403+
TNFR Principle: Propagation radius measures the spatial extent
404+
of cascade effects across the network. High radius indicates
405+
network-wide self-organization.
406+
407+
Examples
408+
--------
409+
>>> # Local cascade (few nodes)
410+
>>> compute_propagation_radius(G_local)
411+
3
412+
413+
>>> # Network-wide cascade
414+
>>> compute_propagation_radius(G_wide)
415+
15
416+
"""
417+
propagations = G.graph.get("thol_propagations", [])
418+
affected_nodes = set()
419+
420+
for prop in propagations:
421+
affected_nodes.add(prop["source_node"])
422+
for target, _ in prop["propagations"]:
423+
affected_nodes.add(target)
424+
425+
return len(affected_nodes)
426+
427+
428+
def compute_subepi_collective_coherence(G: TNFRGraph, node: NodeId) -> float:
429+
"""Calculate coherence of sub-EPI ensemble.
430+
431+
Measures how structurally aligned the emergent sub-EPIs are.
432+
Low variance = high coherence = stable emergence.
433+
434+
Parameters
435+
----------
436+
G : TNFRGraph
437+
Graph containing the node
438+
node : NodeId
439+
Node with sub-EPIs to analyze
440+
441+
Returns
442+
-------
443+
float
444+
Coherence metric [0, 1] where 1 = perfect alignment
445+
446+
Notes
447+
-----
448+
Uses variance-based coherence:
449+
C_sub = 1 / (1 + var(sub_epi_magnitudes))
450+
451+
TNFR Principle: Coherent bifurcation produces sub-EPIs with similar
452+
structural magnitudes, indicating controlled emergence vs chaotic
453+
fragmentation.
454+
455+
Examples
456+
--------
457+
>>> # Coherent bifurcation (similar sub-EPIs)
458+
>>> compute_subepi_collective_coherence(G, node)
459+
0.85
460+
461+
>>> # Chaotic fragmentation (varied sub-EPIs)
462+
>>> compute_subepi_collective_coherence(G_chaotic, node)
463+
0.23
464+
"""
465+
np = get_numpy()
466+
467+
sub_epis = G.nodes[node].get("sub_epis", [])
468+
if len(sub_epis) < 2:
469+
return 0.0 # Need ≥2 sub-EPIs to measure coherence
470+
471+
epi_values = [sub["epi"] for sub in sub_epis]
472+
variance = float(np.var(epi_values))
473+
474+
# Coherence: inverse relationship with variance
475+
coherence = 1.0 / (1.0 + variance)
476+
return coherence
477+
478+
479+
def compute_metabolic_activity_index(G: TNFRGraph, node: NodeId) -> float:
480+
"""Measure proportion of sub-EPIs generated through network metabolism.
481+
482+
Parameters
483+
----------
484+
G : TNFRGraph
485+
Graph containing the node
486+
node : NodeId
487+
Node to analyze
488+
489+
Returns
490+
-------
491+
float
492+
Ratio [0, 1] of metabolized sub-EPIs to total sub-EPIs
493+
1.0 = all sub-EPIs included network context
494+
0.0 = all sub-EPIs were purely internal bifurcations
495+
496+
Notes
497+
-----
498+
TNFR Principle: Metabolic activity measures how much network context
499+
influenced bifurcation. High index indicates external pressure drove
500+
emergence; low index indicates internal acceleration dominated.
501+
502+
Examples
503+
--------
504+
>>> # Network-driven bifurcation
505+
>>> compute_metabolic_activity_index(G_coupled, node)
506+
0.90
507+
508+
>>> # Internal-only bifurcation
509+
>>> compute_metabolic_activity_index(G_isolated, node)
510+
0.0
511+
"""
512+
sub_epis = G.nodes[node].get("sub_epis", [])
513+
if not sub_epis:
514+
return 0.0
515+
516+
metabolized_count = sum(1 for sub in sub_epis if sub.get("metabolized", False))
517+
return metabolized_count / len(sub_epis)

src/tnfr/operators/metrics.py

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,10 @@ def contraction_metrics(
972972
def self_organization_metrics(
973973
G: TNFRGraph, node: NodeId, epi_before: float, vf_before: float
974974
) -> dict[str, Any]:
975-
"""THOL - Self-organization metrics: nested EPI generation, cascade formation.
975+
"""THOL - Enhanced metrics with cascade dynamics and collective coherence.
976+
977+
Collects comprehensive THOL metrics including bifurcation, cascade propagation,
978+
collective coherence of sub-EPIs, and metabolic activity indicators.
976979
977980
Parameters
978981
----------
@@ -988,28 +991,86 @@ def self_organization_metrics(
988991
Returns
989992
-------
990993
dict
991-
Self-organization-specific metrics including cascade indicators
994+
Self-organization-specific metrics including:
995+
996+
**Base operator metrics:**
997+
998+
- operator: "Self-organization"
999+
- glyph: "THOL"
1000+
- delta_epi: Change in EPI
1001+
- delta_vf: Change in νf
1002+
- epi_final: Final EPI value
1003+
- vf_final: Final νf value
1004+
- d2epi: Structural acceleration
1005+
- dnfr_final: Final ΔNFR
1006+
1007+
**Bifurcation metrics:**
1008+
1009+
- bifurcation_occurred: Boolean indicator
1010+
- nested_epi_count: Number of sub-EPIs created
1011+
- d2epi_magnitude: Absolute acceleration
1012+
1013+
**Cascade dynamics (NEW):**
1014+
1015+
- cascade_depth: Maximum hierarchical bifurcation depth
1016+
- propagation_radius: Total unique nodes affected
1017+
- cascade_detected: Boolean cascade indicator
1018+
- affected_node_count: Nodes reached by cascade
1019+
- total_propagations: Total propagation events
1020+
1021+
**Collective coherence (NEW):**
1022+
1023+
- subepi_coherence: Coherence of sub-EPI ensemble [0,1]
1024+
- metabolic_activity_index: Network context usage [0,1]
1025+
1026+
**Network emergence indicator (NEW):**
1027+
1028+
- network_emergence: Combined indicator (cascade + high coherence)
1029+
1030+
Notes
1031+
-----
1032+
TNFR Principle: Complete traceability of self-organization dynamics.
1033+
These metrics enable reconstruction of entire cascade evolution,
1034+
validation of controlled emergence, and identification of collective
1035+
network phenomena.
1036+
1037+
See Also
1038+
--------
1039+
operators.metabolism.compute_cascade_depth : Cascade depth computation
1040+
operators.metabolism.compute_subepi_collective_coherence : Coherence metric
1041+
operators.metabolism.compute_metabolic_activity_index : Metabolic tracking
1042+
operators.cascade.detect_cascade : Cascade detection
9921043
"""
9931044
from .cascade import detect_cascade, measure_cascade_radius
1045+
from .metabolism import (
1046+
compute_cascade_depth,
1047+
compute_propagation_radius,
1048+
compute_subepi_collective_coherence,
1049+
compute_metabolic_activity_index,
1050+
)
9941051

9951052
epi_after = _get_node_attr(G, node, ALIAS_EPI)
9961053
vf_after = _get_node_attr(G, node, ALIAS_VF)
9971054
d2epi = _get_node_attr(G, node, ALIAS_D2EPI)
9981055
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
9991056

1000-
# Track nested EPI count if graph maintains it
1001-
nested_epi_count = len(G.graph.get("sub_epi", []))
1057+
# Track nested EPI count from node attribute
1058+
nested_epi_count = len(G.nodes[node].get("sub_epis", []))
10021059

1003-
# NEW: Network propagation and cascade metrics
1060+
# Cascade and propagation analysis
10041061
cascade_analysis = detect_cascade(G)
10051062
cascade_radius = (
10061063
measure_cascade_radius(G, node) if cascade_analysis["is_cascade"] else 0
10071064
)
10081065

1009-
propagations = G.graph.get("thol_propagations", [])
1010-
propagated = len(propagations) > 0
1066+
# NEW: Enhanced cascade and emergence metrics
1067+
cascade_depth = compute_cascade_depth(G, node)
1068+
propagation_radius = compute_propagation_radius(G)
1069+
subepi_coherence = compute_subepi_collective_coherence(G, node)
1070+
metabolic_activity = compute_metabolic_activity_index(G, node)
10111071

10121072
return {
1073+
# Base operator metrics
10131074
"operator": "Self-organization",
10141075
"glyph": "THOL",
10151076
"delta_epi": epi_after - epi_before,
@@ -1018,14 +1079,28 @@ def self_organization_metrics(
10181079
"vf_final": vf_after,
10191080
"d2epi": d2epi,
10201081
"dnfr_final": dnfr,
1082+
1083+
# Bifurcation metrics
1084+
"bifurcation_occurred": nested_epi_count > 0,
10211085
"nested_epi_count": nested_epi_count,
1022-
"cascade_active": abs(d2epi) > 0.1, # Configurable threshold
1023-
# NEW: Network emergence metrics
1024-
"propagated": propagated,
1086+
"d2epi_magnitude": abs(d2epi),
1087+
1088+
# NEW: Cascade dynamics
1089+
"cascade_depth": cascade_depth,
1090+
"propagation_radius": propagation_radius,
10251091
"cascade_detected": cascade_analysis["is_cascade"],
1026-
"cascade_radius": cascade_radius,
10271092
"affected_node_count": len(cascade_analysis["affected_nodes"]),
10281093
"total_propagations": cascade_analysis["total_propagations"],
1094+
1095+
# NEW: Collective coherence
1096+
"subepi_coherence": subepi_coherence,
1097+
"metabolic_activity_index": metabolic_activity,
1098+
1099+
# NEW: Network emergence indicator
1100+
"network_emergence": (
1101+
cascade_analysis["is_cascade"]
1102+
and subepi_coherence > 0.5
1103+
),
10291104
}
10301105

10311106

0 commit comments

Comments
 (0)