Source code for mercurial.hierarchy.emergence

"""Tripartite emergence criterion for new level detection (LADDER B.1)."""

from typing import Dict, Optional, Tuple

import numpy as np

from mercurial.core.patterns import Pattern
from mercurial.hierarchy.complexity import ComplexityMeasure


[docs] class EmergenceDetector: """ Detects when a pattern constitutes a genuine new emergent level. """ def __init__( self, closure_threshold: float = 0.8, novelty_threshold: float = 0.7, incommensurability_threshold: float = 0.6, ): self.closure_threshold = closure_threshold self.novelty_threshold = novelty_threshold self.incommensurability_threshold = incommensurability_threshold
[docs] def operational_closure( self, pattern: Pattern, time_series: Optional[np.ndarray] = None ) -> float: """ Measure operational closure: degree to which pattern dynamics are self‑determined. Closure = 1 - (mutual information between internal and external variables) """ if time_series is None: # Simplified: use autocorrelation as proxy for self‑determination v = pattern.V.flatten() if len(v) < 2: return 0.0 # Autocorrelation at lag 1 autocorr = np.corrcoef(v[:-1], v[1:])[0, 1] # Closure is high when autocorrelation is high (self‑sustaining) return max(0.0, (autocorr + 1.0) / 2.0) else: # If time series provided, compute external influence # Placeholder: assume some external drive return 0.5
[docs] def novel_causal_powers( self, pattern: Pattern, lower_level_pattern: Optional[Pattern] = None ) -> float: """ Measure novelty: degree to which pattern has irreducible causal influence. Novelty = 1 - (reduction error / total variance) """ if lower_level_pattern is None: # If no lower level provided, assume novelty is moderate return 0.5 # Compare prediction of pattern's behavior from lower level # Simplified: use correlation between pattern and lower‑level reconstruction v_high = pattern.V.flatten() v_low = lower_level_pattern.V.flatten() min_len = min(len(v_high), len(v_low)) if min_len < 2: return 0.0 corr = np.corrcoef(v_high[:min_len], v_low[:min_len])[0, 1] # Novelty is high when correlation is low (cannot be reduced) return max(0.0, 1.0 - abs(corr))
[docs] def descriptive_incommensurability( self, pattern: Pattern, lower_level_pattern: Optional[Pattern] = None ) -> float: """ Measure incommensurability: degree to which pattern requires distinct description. Incommensurability = 1 - (information overlap / total information) """ # Use complexity difference as proxy measure = ComplexityMeasure() comp_high = pattern.complexity(measure) if lower_level_pattern is None: comp_low = 0.0 else: comp_low = lower_level_pattern.complexity(measure) # Incommensurability is high when complexity jump is large diff = max(0.0, comp_high - comp_low) # Normalize by expected maximum (e.g., 10) return min(1.0, diff / 10.0)
[docs] def is_emergent( self, pattern: Pattern, time_series: Optional[np.ndarray] = None, lower_level_pattern: Optional[Pattern] = None, ) -> Tuple[bool, Dict[str, float]]: """ Apply tripartite criterion. Returns (is_emergent, {closure_score, novelty_score, incomm_score}) """ closure = self.operational_closure(pattern, time_series) novelty = self.novel_causal_powers(pattern, lower_level_pattern) incomm = self.descriptive_incommensurability(pattern, lower_level_pattern) scores = { "operational_closure": closure, "novel_causal_powers": novelty, "descriptive_incommensurability": incomm, } is_emerg = ( closure >= self.closure_threshold and novelty >= self.novelty_threshold and incomm >= self.incommensurability_threshold ) return is_emerg, scores