@@ -1882,8 +1882,13 @@ def transition_metrics(
18821882 dnfr_before : float ,
18831883 vf_before : float ,
18841884 theta_before : float ,
1885+ epi_before : float | None = None ,
18851886) -> dict [str , Any ]:
1886- """NAV - Transition metrics: regime handoff, ΔNFR rebalancing.
1887+ """NAV - Transition metrics: regime classification, phase shift, frequency scaling.
1888+
1889+ Collects comprehensive transition metrics including regime origin/destination,
1890+ phase shift magnitude (properly wrapped), transition type classification, and
1891+ structural preservation ratios as specified in TNFR.pdf Table 2.3.
18871892
18881893 Parameters
18891894 ----------
@@ -1897,31 +1902,201 @@ def transition_metrics(
18971902 νf value before operator application
18981903 theta_before : float
18991904 Phase value before operator application
1905+ epi_before : float, optional
1906+ EPI value before operator application (for preservation tracking)
19001907
19011908 Returns
19021909 -------
19031910 dict
1904- Transition-specific metrics including handoff success
1911+ Transition-specific metrics including:
1912+
1913+ **Core metrics (existing)**:
1914+
1915+ - operator: "Transition"
1916+ - glyph: "NAV"
1917+ - delta_theta: Signed phase change
1918+ - delta_vf: Change in νf
1919+ - delta_dnfr: Change in ΔNFR
1920+ - dnfr_final: Final ΔNFR value
1921+ - vf_final: Final νf value
1922+ - theta_final: Final phase value
1923+ - transition_complete: Boolean (|ΔNFR| < |νf|)
1924+
1925+ **Regime classification (NEW)**:
1926+
1927+ - regime_origin: "latent" | "active" | "resonant"
1928+ - regime_destination: "latent" | "active" | "resonant"
1929+ - transition_type: "reactivation" | "phase_shift" | "regime_change"
1930+
1931+ **Phase metrics (NEW)**:
1932+
1933+ - phase_shift_magnitude: Absolute phase change (radians, 0-π)
1934+ - phase_shift_signed: Signed phase change (radians, wrapped to [-π, π])
1935+
1936+ **Structural scaling (NEW)**:
1937+
1938+ - vf_scaling_factor: vf_after / vf_before
1939+ - dnfr_damping_ratio: dnfr_after / dnfr_before
1940+ - epi_preservation: epi_after / epi_before (if epi_before provided)
1941+
1942+ **Latency tracking (NEW)**:
1943+
1944+ - latency_duration: Time in silence (seconds) if transitioning from SHA
1945+
1946+ Notes
1947+ -----
1948+ **Regime Classification**:
1949+
1950+ - **Latent**: latent flag set OR νf < 0.05
1951+ - **Active**: Default operational state
1952+ - **Resonant**: EPI > 0.5 AND νf > 0.8
1953+
1954+ **Transition Type**:
1955+
1956+ - **reactivation**: From latent state (SHA → NAV flow)
1957+ - **phase_shift**: Significant phase change (|Δθ| > 0.3 rad)
1958+ - **regime_change**: Regime switch without significant phase shift
1959+
1960+ **Phase Shift Wrapping**:
1961+
1962+ Phase shifts are properly wrapped to [-π, π] range to handle 0-2π boundary
1963+ crossings correctly, ensuring accurate phase change measurement.
1964+
1965+ Examples
1966+ --------
1967+ >>> from tnfr.structural import create_nfr, run_sequence
1968+ >>> from tnfr.operators.definitions import Silence, Transition
1969+ >>>
1970+ >>> # Example: SHA → NAV reactivation
1971+ >>> G, node = create_nfr("test", epi=0.5, vf=0.8)
1972+ >>> G.graph["COLLECT_OPERATOR_METRICS"] = True
1973+ >>> run_sequence(G, node, [Silence(), Transition()])
1974+ >>>
1975+ >>> metrics = G.graph["operator_metrics"][-1]
1976+ >>> assert metrics["operator"] == "Transition"
1977+ >>> assert metrics["transition_type"] == "reactivation"
1978+ >>> assert metrics["regime_origin"] == "latent"
1979+ >>> assert metrics["latency_duration"] is not None
1980+
1981+ See Also
1982+ --------
1983+ operators.definitions.Transition : NAV operator implementation
1984+ operators.definitions.Transition._detect_regime : Regime detection logic
19051985 """
1986+ import math
1987+
1988+ # Get current state (after transformation)
1989+ epi_after = _get_node_attr (G , node , ALIAS_EPI )
19061990 dnfr_after = _get_node_attr (G , node , ALIAS_DNFR )
19071991 vf_after = _get_node_attr (G , node , ALIAS_VF )
19081992 theta_after = _get_node_attr (G , node , ALIAS_THETA )
19091993
1994+ # === REGIME CLASSIFICATION ===
1995+ # Get regime origin from node attribute (stored by Transition operator before super().__call__)
1996+ regime_origin = G .nodes [node ].get ("_regime_before" , None )
1997+ if regime_origin is None :
1998+ # Fallback: detect regime from before state
1999+ regime_origin = _detect_regime_from_state (
2000+ epi_before or epi_after , vf_before , False # Cannot access latent flag from before
2001+ )
2002+
2003+ # Detect destination regime
2004+ regime_destination = _detect_regime_from_state (
2005+ epi_after , vf_after , G .nodes [node ].get ("latent" , False )
2006+ )
2007+
2008+ # === TRANSITION TYPE CLASSIFICATION ===
2009+ # Calculate phase shift (properly wrapped)
2010+ phase_shift_raw = theta_after - theta_before
2011+ if phase_shift_raw > math .pi :
2012+ phase_shift_raw -= 2 * math .pi
2013+ elif phase_shift_raw < - math .pi :
2014+ phase_shift_raw += 2 * math .pi
2015+
2016+ # Classify transition type
2017+ if regime_origin == "latent" :
2018+ transition_type = "reactivation"
2019+ elif abs (phase_shift_raw ) > 0.3 :
2020+ transition_type = "phase_shift"
2021+ else :
2022+ transition_type = "regime_change"
2023+
2024+ # === STRUCTURAL SCALING FACTORS ===
2025+ vf_scaling = vf_after / vf_before if vf_before > 0 else 1.0
2026+ dnfr_damping = dnfr_after / dnfr_before if abs (dnfr_before ) > 1e-9 else 1.0
2027+
2028+ # === EPI PRESERVATION ===
2029+ epi_preservation = None
2030+ if epi_before is not None and epi_before > 0 :
2031+ epi_preservation = epi_after / epi_before
2032+
2033+ # === LATENCY DURATION ===
2034+ # Get from node if transitioning from silence
2035+ latency_duration = G .nodes [node ].get ("silence_duration" , None )
2036+
19102037 return {
2038+ # === CORE (existing, preserved) ===
19112039 "operator" : "Transition" ,
19122040 "glyph" : "NAV" ,
1913- "dnfr_change" : abs (dnfr_after - dnfr_before ),
2041+ "delta_theta" : phase_shift_raw ,
2042+ "delta_vf" : vf_after - vf_before ,
2043+ "delta_dnfr" : dnfr_after - dnfr_before ,
19142044 "dnfr_final" : dnfr_after ,
1915- "vf_change" : abs (vf_after - vf_before ),
19162045 "vf_final" : vf_after ,
1917- "theta_shift" : abs (theta_after - theta_before ),
19182046 "theta_final" : theta_after ,
1919- # Transition complete when ΔNFR magnitude is bounded by νf magnitude
1920- # indicating structural frequency dominates reorganization dynamics
19212047 "transition_complete" : abs (dnfr_after ) < abs (vf_after ),
2048+ # Legacy compatibility
2049+ "dnfr_change" : abs (dnfr_after - dnfr_before ),
2050+ "vf_change" : abs (vf_after - vf_before ),
2051+ "theta_shift" : abs (phase_shift_raw ),
2052+ # === REGIME CLASSIFICATION (NEW) ===
2053+ "regime_origin" : regime_origin ,
2054+ "regime_destination" : regime_destination ,
2055+ "transition_type" : transition_type ,
2056+ # === PHASE METRICS (NEW) ===
2057+ "phase_shift_magnitude" : abs (phase_shift_raw ),
2058+ "phase_shift_signed" : phase_shift_raw ,
2059+ # === STRUCTURAL SCALING (NEW) ===
2060+ "vf_scaling_factor" : vf_scaling ,
2061+ "dnfr_damping_ratio" : dnfr_damping ,
2062+ "epi_preservation" : epi_preservation ,
2063+ # === LATENCY TRACKING (NEW) ===
2064+ "latency_duration" : latency_duration ,
19222065 }
19232066
19242067
2068+ def _detect_regime_from_state (epi : float , vf : float , latent : bool ) -> str :
2069+ """Detect structural regime from node state.
2070+
2071+ Helper function for transition_metrics to classify regime without
2072+ accessing the Transition operator directly.
2073+
2074+ Parameters
2075+ ----------
2076+ epi : float
2077+ EPI value
2078+ vf : float
2079+ νf value
2080+ latent : bool
2081+ Latent flag
2082+
2083+ Returns
2084+ -------
2085+ str
2086+ Regime classification: "latent", "active", or "resonant"
2087+
2088+ Notes
2089+ -----
2090+ Matches logic in Transition._detect_regime (definitions.py).
2091+ """
2092+ if latent or vf < 0.05 :
2093+ return "latent"
2094+ elif epi > 0.5 and vf > 0.8 :
2095+ return "resonant"
2096+ else :
2097+ return "active"
2098+
2099+
19252100def recursivity_metrics (
19262101 G : TNFRGraph , node : NodeId , epi_before : float , vf_before : float
19272102) -> dict [str , Any ]:
0 commit comments