Emergence metrics for T'HOL structural metabolism.
Provides quantitative measures of complexity emergence, bifurcation dynamics, and metabolic efficiency in self-organizing systems.
"""Emergence metrics for T'HOL structural metabolism.
Provides quantitative measures of complexity emergence, bifurcation dynamics,
and metabolic efficiency in self-organizing systems.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..types import NodeId, TNFRGraph
from ..alias import get_attr
from ..constants.aliases import ALIAS_EPI
__all__ = [
"compute_structural_complexity",
"compute_bifurcation_rate",
"compute_metabolic_efficiency",
"compute_emergence_index",
]
# Emergence index calculation constant
_EMERGENCE_INDEX_EPSILON = 1e-6 # Small value to avoid zero in geometric mean
def compute_structural_complexity(G: TNFRGraph, node: NodeId) -> int:
"""Measure structural complexity by counting nested sub-EPIs.
Structural complexity reflects the number of bifurcations that have
occurred, indicating the degree of self-organized internal structure.
Parameters
----------
G : TNFRGraph
Graph containing the node
node : NodeId
Node identifier
Returns
-------
int
Number of sub-EPIs generated through T'HOL bifurcations
Notes
-----
Higher complexity indicates more sophisticated internal organization
but may also indicate higher maintenance costs (higher νf required).
Examples
--------
>>> from tnfr.structural import create_nfr
>>> from tnfr.operators.definitions import SelfOrganization
>>> from tnfr.metrics.emergence import compute_structural_complexity
>>> G, node = create_nfr("system", epi=0.5, vf=1.0)
>>> # Initialize history for bifurcation
>>> G.nodes[node]["epi_history"] = [0.3, 0.4, 0.6] # Accelerating
>>> SelfOrganization()(G, node, tau=0.05) # Low threshold
>>> complexity = compute_structural_complexity(G, node)
>>> complexity # doctest: +SKIP
1
"""
sub_epis = G.nodes[node].get("sub_epis", [])
return len(sub_epis)
def compute_bifurcation_rate(G: TNFRGraph, node: NodeId, window: int = 10) -> float:
"""Calculate frequency of bifurcations in recent history.
Bifurcation rate indicates how actively the node is generating new
structural complexity through T'HOL operations.
Parameters
----------
G : TNFRGraph
Graph containing the node
node : NodeId
Node identifier
window : int
Time window for rate calculation (in operator steps, default 10)
Returns
-------
float
Bifurcations per step in the window (0.0 to 1.0 typical)
Notes
-----
High bifurcation rate (> 0.5) may indicate:
- Active adaptation to changing environment
- High structural instability
- Rich exploratory dynamics
Low rate (< 0.1) may indicate:
- Stable structural regime
- Low adaptive pressure
- Insufficient ΔNFR for bifurcation
Examples
--------
>>> from tnfr.structural import create_nfr
>>> from tnfr.metrics.emergence import compute_bifurcation_rate
>>> G, node = create_nfr("evolving", epi=0.6, vf=1.0)
>>> # Simulate several bifurcations
>>> G.nodes[node]["sub_epis"] = [
... {"timestamp": 5}, {"timestamp": 8}, {"timestamp": 12}
... ]
>>> rate = compute_bifurcation_rate(G, node, window=10)
>>> rate # 2 bifurcations in last 10 steps
0.2
"""
sub_epis = G.nodes[node].get("sub_epis", [])
if not sub_epis:
return 0.0
# Get current timestamp from glyph history
current_time = len(G.nodes[node].get("glyph_history", []))
# Count bifurcations in window
recent_bifurcations = [
s for s in sub_epis if s.get("timestamp", 0) >= (current_time - window)
]
return len(recent_bifurcations) / float(window)
def compute_metabolic_efficiency(G: TNFRGraph, node: NodeId) -> float:
"""Calculate EPI gain per T'HOL application (metabolic efficiency).
Metabolic efficiency measures how effectively T'HOL converts
reorganization events into stable structural complexity (EPI growth).
Parameters
----------
G : TNFRGraph
Graph containing the node
node : NodeId
Node identifier
Returns
-------
float
Average EPI increase per T'HOL application
Returns 0.0 if no T'HOL applications recorded
Notes
-----
High efficiency (> 0.1) indicates:
- Effective self-organization
- Strong coherence maintenance
- Productive metabolic cycles
Low efficiency (< 0.01) indicates:
- Ineffective reorganization
- High structural friction
- Possible need for different operator sequences
Examples
--------
>>> from tnfr.structural import create_nfr
>>> from tnfr.metrics.emergence import compute_metabolic_efficiency
>>> G, node = create_nfr("productive", epi=0.5, vf=1.0)
>>> # Record initial EPI
>>> G.nodes[node]["epi_initial"] = 0.3
>>> # Simulate T'HOL applications
>>> G.nodes[node]["glyph_history"] = ["THOL", "THOL", "IL"]
>>> # Current EPI increased to 0.5
>>> efficiency = compute_metabolic_efficiency(G, node)
>>> efficiency # (0.5 - 0.3) / 2 = 0.1
0.1
"""
from ..types import Glyph
# Count T'HOL applications
glyph_history = G.nodes[node].get("glyph_history", [])
thol_count = sum(1 for g in glyph_history if g == "THOL" or g == Glyph.THOL.value)
if thol_count == 0:
return 0.0
# Calculate EPI delta
current_epi = float(get_attr(G.nodes[node], ALIAS_EPI, 0.0))
initial_epi = float(G.nodes[node].get("epi_initial", current_epi))
epi_gain = current_epi - initial_epi
return epi_gain / float(thol_count)
def compute_emergence_index(G: TNFRGraph, node: NodeId) -> float:
"""Composite metric combining complexity, rate, and efficiency.
Emergence index provides a holistic measure of T'HOL metabolic health,
combining structural complexity, bifurcation dynamics, and efficiency.
Parameters
----------
G : TNFRGraph
Graph containing the node
node : NodeId
Node identifier
Returns
-------
float
Emergence index (0.0 to ~1.0 typical, higher indicates more emergent)
Computed as: sqrt(complexity * rate * efficiency)
Notes
-----
This index balances three factors:
- Complexity: how much structure has emerged
- Rate: how actively new structure forms
- Efficiency: how productive each reorganization is
High index (> 0.5) indicates healthy emergent dynamics.
Low index (< 0.1) suggests reorganization is stalled or inefficient.
Examples
--------
>>> from tnfr.structural import create_nfr
>>> from tnfr.metrics.emergence import compute_emergence_index
>>> G, node = create_nfr("emergent", epi=0.7, vf=1.0)
>>> # Setup for high emergence
>>> G.nodes[node]["epi_initial"] = 0.3
>>> G.nodes[node]["glyph_history"] = ["THOL", "THOL", "IL"]
>>> G.nodes[node]["sub_epis"] = [{"timestamp": 1}, {"timestamp": 2}]
>>> index = compute_emergence_index(G, node)
>>> index # doctest: +SKIP
0.63...
"""
complexity = float(compute_structural_complexity(G, node))
rate = compute_bifurcation_rate(G, node)
efficiency = compute_metabolic_efficiency(G, node)
# Geometric mean to avoid dominance by any single factor
# Add epsilon to avoid zero multiplication when no bifurcations occurred
index = (
(complexity + _EMERGENCE_INDEX_EPSILON)
* (rate + _EMERGENCE_INDEX_EPSILON)
* (efficiency + _EMERGENCE_INDEX_EPSILON)
) ** (1.0 / 3.0)
return index