Skip to content

Commit 3f23a53

Browse files
Copilotfermga
andcommitted
Implement ZHIR bifurcation detection (Option B conservative approach)
Co-authored-by: fermga <203334638+fermga@users.noreply.github.com>
1 parent 51b8f44 commit 3f23a53

File tree

2 files changed

+555
-0
lines changed

2 files changed

+555
-0
lines changed

src/tnfr/operators/definitions.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3138,6 +3138,21 @@ class Mutation(Operator):
31383138
- ``threshold_met``: Boolean flag indicating threshold status
31393139
- ``threshold_ratio``: Ratio of velocity to threshold
31403140
3141+
**Bifurcation Potential Detection (∂²EPI/∂t² > τ)**:
3142+
3143+
According to AGENTS.md §U4a, ZHIR can trigger bifurcation when structural acceleration
3144+
exceeds threshold τ. The implementation detects bifurcation potential and sets telemetry
3145+
flags for validation of grammar U4a:
3146+
3147+
- **∂²EPI/∂t² > τ**: Sets ``_zhir_bifurcation_potential`` flag, logs detection
3148+
- **∂²EPI/∂t² ≤ τ**: No bifurcation potential detected
3149+
3150+
Configuration: Set ``G.graph["BIFURCATION_THRESHOLD_TAU"]`` (canonical) or
3151+
``G.graph["ZHIR_BIFURCATION_THRESHOLD"]`` (fallback) to customize threshold (default: 0.5).
3152+
3153+
This detection enables validation of U4a requirement: "If {OZ, ZHIR}, include {THOL, IL}"
3154+
for controlled bifurcation handling.
3155+
31413156
Use Cases: Paradigm shifts, strategic pivots, adaptive responses, regime transitions,
31423157
identity transformation while maintaining continuity.
31433158
@@ -3178,6 +3193,130 @@ class Mutation(Operator):
31783193
name: ClassVar[str] = MUTATION
31793194
glyph: ClassVar[Glyph] = Glyph.ZHIR
31803195

3196+
def __call__(self, G: TNFRGraph, node: Any, **kw: Any) -> None:
3197+
"""Apply ZHIR with bifurcation potential detection.
3198+
3199+
Detects when ∂²EPI/∂t² > τ (bifurcation threshold) and sets telemetry flags
3200+
to enable validation of grammar U4a.
3201+
3202+
Parameters
3203+
----------
3204+
G : TNFRGraph
3205+
Graph storing TNFR nodes
3206+
node : Any
3207+
Target node identifier
3208+
**kw : Any
3209+
Additional parameters including:
3210+
- tau: Bifurcation threshold (default from graph config or 0.5)
3211+
- validate_preconditions: Enable precondition checks (default True)
3212+
- collect_metrics: Enable metrics collection (default False)
3213+
"""
3214+
# Compute structural acceleration before base operator
3215+
d2_epi = self._compute_epi_acceleration(G, node)
3216+
3217+
# Get bifurcation threshold (tau) from kwargs or graph config
3218+
tau = kw.get("tau")
3219+
if tau is None:
3220+
# Try canonical threshold first, then operator-specific, then default
3221+
tau = float(
3222+
G.graph.get(
3223+
"BIFURCATION_THRESHOLD_TAU",
3224+
G.graph.get("ZHIR_BIFURCATION_THRESHOLD", 0.5),
3225+
)
3226+
)
3227+
3228+
# Apply base operator (includes glyph application, preconditions, and metrics)
3229+
super().__call__(G, node, **kw)
3230+
3231+
# Detect bifurcation potential if acceleration exceeds threshold
3232+
if d2_epi > tau:
3233+
self._detect_bifurcation_potential(G, node, d2_epi=d2_epi, tau=tau)
3234+
3235+
def _compute_epi_acceleration(self, G: TNFRGraph, node: Any) -> float:
3236+
"""Calculate ∂²EPI/∂t² from node's EPI history.
3237+
3238+
Uses finite difference approximation:
3239+
d²EPI/dt² ≈ (EPI_t - 2*EPI_{t-1} + EPI_{t-2}) / (Δt)²
3240+
For unit time steps: d²EPI/dt² ≈ EPI_t - 2*EPI_{t-1} + EPI_{t-2}
3241+
3242+
Parameters
3243+
----------
3244+
G : TNFRGraph
3245+
Graph containing the node
3246+
node : Any
3247+
Node identifier
3248+
3249+
Returns
3250+
-------
3251+
float
3252+
Magnitude of EPI acceleration (always non-negative)
3253+
"""
3254+
from ..alias import get_attr
3255+
from ..constants.aliases import ALIAS_EPI
3256+
3257+
# Get EPI history (maintained by node for temporal analysis)
3258+
history = G.nodes[node].get("epi_history", [])
3259+
3260+
# Need at least 3 points for second derivative
3261+
if len(history) < 3:
3262+
return 0.0
3263+
3264+
# Finite difference: d²EPI/dt² ≈ (EPI_t - 2*EPI_{t-1} + EPI_{t-2})
3265+
epi_t = float(history[-1])
3266+
epi_t1 = float(history[-2])
3267+
epi_t2 = float(history[-3])
3268+
3269+
d2_epi = epi_t - 2.0 * epi_t1 + epi_t2
3270+
3271+
return abs(d2_epi)
3272+
3273+
def _detect_bifurcation_potential(
3274+
self, G: TNFRGraph, node: Any, d2_epi: float, tau: float
3275+
) -> None:
3276+
"""Detect and record bifurcation potential when ∂²EPI/∂t² > τ.
3277+
3278+
This implements Option B (conservative detection) from the issue specification.
3279+
Sets telemetry flags and logs informative message without creating structural
3280+
variants. Enables validation of grammar U4a requirement.
3281+
3282+
Parameters
3283+
----------
3284+
G : TNFRGraph
3285+
Graph containing the node
3286+
node : Any
3287+
Node identifier
3288+
d2_epi : float
3289+
Current EPI acceleration
3290+
tau : float
3291+
Bifurcation threshold that was exceeded
3292+
"""
3293+
import logging
3294+
3295+
logger = logging.getLogger(__name__)
3296+
3297+
# Set telemetry flags for grammar validation
3298+
G.nodes[node]["_zhir_bifurcation_potential"] = True
3299+
G.nodes[node]["_zhir_d2epi"] = d2_epi
3300+
G.nodes[node]["_zhir_tau"] = tau
3301+
3302+
# Record bifurcation detection event in graph for analysis
3303+
bifurcation_events = G.graph.setdefault("zhir_bifurcation_events", [])
3304+
bifurcation_events.append(
3305+
{
3306+
"node": node,
3307+
"d2_epi": d2_epi,
3308+
"tau": tau,
3309+
"timestamp": len(G.nodes[node].get("glyph_history", [])),
3310+
}
3311+
)
3312+
3313+
# Log informative message
3314+
logger.info(
3315+
f"Node {node}: ZHIR bifurcation potential detected "
3316+
f"(∂²EPI/∂t²={d2_epi:.3f} > τ={tau}). "
3317+
f"Consider applying THOL for controlled bifurcation or IL for stabilization."
3318+
)
3319+
31813320
def _validate_preconditions(self, G: TNFRGraph, node: Any) -> None:
31823321
"""Validate ZHIR-specific preconditions."""
31833322
from .preconditions import validate_mutation

0 commit comments

Comments
 (0)