IPM Protocol: Dynamic Regime Sensitivity Analysis

IPM Protocol: Dynamic Regime Sensitivity Analysis

Author: Taotuner
Based on: IPM Scientific Core
DOI: https://doi.org/10.5281/zenodo.20477438


Abstract

This experiment analyzes how dynamical metrics respond to controlled perturbations in coupled systems. A controlled increase in thermal noise is applied during a specific interval. The experiment measures three projection functionals: Φ* (spectral organization), DIG (temporal autocorrelation), and coherence (self-model alignment). Results show consistent metric changes during perturbation (Φ*: -15.9%, DIG: -25.3%) and correlation (0.594) between Φ* and DIG. The experiment demonstrates co-responsiveness to global noise, not dimensional independence or epistemological validation of metrics.

Note on scope: This is a preliminary sensitivity analysis. The observed correlation may arise from shared dependence on global noise, system energy, or latent field dynamics. This experiment does not distinguish these possibilities.


1. What Is This Experiment?

The experiment analyzes how dynamical metrics respond to controlled perturbations in a coupled system. It addresses a methodological question: do these metrics change consistently when the dynamical regime shifts?

The approach: apply a controlled perturbation and observe whether metrics change consistently. Consistent response demonstrates co-responsiveness to regime shifts.


2. What the Experiment Contains

The code simulates a minimal universe with two types of systems that evolve together:

ComponentDescription
Continuous spectral fieldThe collective substrate. Dynamics governed by reaction-diffusion PDE in Fourier space.
Multiple discrete agents (toy model)Each with internal state, synaptic plasticity (Hebbian learning), and self-model.

The coupling: The two scales interact — field influences agents, agents influence field. Perturbation affects both scales simultaneously.


3. What Is the Perturbation?

The perturbation is a controlled increase in thermal noise applied during a specific interval (turns 700-820). During this period, systems continue to evolve but temporarily lose coherence and predictability.

The hypothesis: If metrics are sensitive to dynamical regime shifts, they must change consistently during perturbation.


4. The Metrics (Projection Functionals)

MetricDefinitionWhat It Captures
Φ (Phi Asterisk)*Φ* = Φ - λ·Δ, where Φ = 1 - spectral entropySpectral organization (spatial structure)
DIG (Dynamical Independence Gap)Autocorrelation (lag-1 and lag-2) of agent statesTemporal memory / predictability
CoherenceAlignment between agent state and self-modelSelf-model stability

Important note: Φ* and DIG are low-order statistics on correlated states of the same system. Their correlation (~0.59) may arise from:

  • Shared dependence on the same driver (global noise)

  • Shared dependence on system energy/entropy

  • Different filtering of the same latent field

This experiment does not distinguish these possibilities.


5. Limitations of This Experiment (Explicit)

LimitationImplication
Global noise onlyDoes not separate thermal sensitivity from structural sensitivity
Correlation by constructionMetrics may correlate because they all depend on system energy/entropy
No structural variationTopology of coupling is fixed; only noise intensity varies
Low-order statisticsBoth metrics are filtered versions of the same underlying dynamics
Preliminary scopeDemonstrates co-responsiveness, not dimensional independence

What this experiment does NOT show:

  • That metrics capture "integration" rather than just global noise

  • Separation between thermal and structural regimes

  • That Φ* and DIG measure independent dimensions of the system

  • Epistemological adequacy across contexts


6. How to Run It

Run in any Python environment with: numpy, torch, matplotlib.

Main parameters:

ParameterDescription
temperatureBackground noise level
lack_strengthPerturbation intensity
lack_durationDuration of perturbation
lambda_starλ factor for Φ*

Experiment configuration:

  • Runs for 1500 turns

  • Perturbation from turns 700 to 820

  • Data collected every 20 turns


7. Code Implementation

python
import numpy as np
import torch
import torch.fft as fft
import matplotlib.pyplot as plt
from collections import deque
import warnings
warnings.filterwarnings('ignore')

# =============================================================================
# CONSTANTS
# =============================================================================

CONSTANTS = {
    'temperature': 0.006,
    'gamma': 0.10,
    'D': 0.07,
    'coupling_strength': 0.012,
    'coupling_max': 0.5,
    'memory_rate': 0.95,
    'self_rate': 0.94,
    'lack_strength': 2.0,
    'lack_noise_boost': 6.0,
    'lack_duration': 120,
    'lambda_star': 0.25,
    'phi_star_min': 0.10,
    'phi_star_max': 0.70,
    'recovery_rate': 0.98,
}

# =============================================================================
# HYBRID SYSTEM
# =============================================================================

class HybridSystem:
    def __init__(self, system_id, grid_size=16, n_degrees=12, device='cpu'):
        self.id = system_id
        self.grid_size = grid_size
        self.n_degrees = n_degrees
        self.device = device
        
        # Spectral field
        self.field = self._create_structured_field()
        
        # Toy model agents
        self.v = torch.randn(n_degrees, device=device) * 0.5
        self.W = torch.randn(n_degrees, n_degrees, device=device) * 0.3 / np.sqrt(n_degrees)
        self.W.fill_diagonal_(0)
        
        # Memory and Self
        self.memory = self.field.clone()
        self.self_model = self.field.clone()
        self.auto_modelo = self.v.clone()
        
        # Perturbation state
        self.lack_memory = 0.0
        
        # Metrics
        self.coherence = 0.6
        self.phi = 0.0
        self.delta = 0.0
        self.phi_star = 0.4
        self.dig = 0.5
        
        # History
        self.v_history = deque(maxlen=80)
        self.phi_star_history = deque(maxlen=15)
        self.dig_history = deque(maxlen=15)
        
        self._setup_spectral_space()
    
    def _create_structured_field(self):
        x = torch.linspace(-2, 2, self.grid_size, device=self.device)
        y = torch.linspace(-2, 2, self.grid_size, device=self.device)
        z = torch.linspace(-2, 2, self.grid_size, device=self.device)
        
        X, Y, Z = torch.meshgrid(x, y, z, indexing='ij')
        
        field = (torch.sin(X * 0.6) * torch.cos(Y * 0.5) + 
                 torch.sin(Y * 0.5) * torch.cos(Z * 0.4) +
                 torch.sin(Z * 0.7) * torch.cos(X * 0.5)) * 0.5
        
        field += torch.randn_like(field) * 0.08
        return field
    
    def _setup_spectral_space(self):
        kx = torch.fft.fftfreq(self.grid_size, device=self.device) * 2 * np.pi
        ky = torch.fft.fftfreq(self.grid_size, device=self.device) * 2 * np.pi
        kz = torch.fft.rfftfreq(self.grid_size, device=self.device) * 2 * np.pi
        
        KX, KY, KZ = torch.meshgrid(kx, ky, kz, indexing='ij')
        self.K2 = KX**2 + KY**2 + KZ**2
        
        cutoff = self.grid_size // 3
        self.mask = ((torch.abs(KX) < cutoff) & 
                     (torch.abs(KY) < cutoff) & 
                     (torch.abs(KZ) < cutoff)).float()
        
        x = torch.linspace(-1, 1, self.grid_size, device=self.device)
        X, Y, Z = torch.meshgrid(x, x, x, indexing='ij')
        R2 = X**2 + Y**2 + Z**2
        kernel = torch.exp(-R2 / 0.06)
        kernel = kernel / kernel.sum()
        self.kernel_hat = fft.rfftn(kernel, dim=(-3,-2,-1), norm='ortho')
    
    def spectral_evolution(self, dt=0.001, external_forcing=0.0):
        phi = self.field
        hat = fft.rfftn(phi, dim=(-3,-2,-1), norm='ortho')
        
        lap_hat = -self.K2 * hat
        diffusion = CONSTANTS['D'] * lap_hat
        dissipation = -CONSTANTS['gamma'] * hat
        
        local_mean = fft.irfftn(hat * self.kernel_hat, dim=(-3,-2,-1), norm='ortho')
        coupling_real = CONSTANTS['coupling_strength'] * (local_mean - phi)
        coupling_real = torch.clamp(coupling_real, -CONSTANTS['coupling_max'], CONSTANTS['coupling_max'])
        coupling = fft.rfftn(coupling_real, dim=(-3,-2,-1), norm='ortho')
        
        nonlinear_real = torch.tanh(1.3 * phi)
        nonlinear = fft.rfftn(nonlinear_real, dim=(-3,-2,-1), norm='ortho')
        
        memory_force = fft.rfftn(0.025 * (self.memory - phi), dim=(-3,-2,-1), norm='ortho')
        self_force = fft.rfftn(0.02 * (self.self_model - phi), dim=(-3,-2,-1), norm='ortho')
        
        K = torch.sqrt(self.K2 + 1e-12)
        forcing_band = ((K > 1.5) & (K < 5.0)).float()
        random_phase = torch.randn_like(hat) + 1j * torch.randn_like(hat)
        forcing = external_forcing * forcing_band * random_phase
        
        noise = fft.rfftn(torch.randn_like(phi) * CONSTANTS['temperature'], 
                         dim=(-3,-2,-1), norm='ortho')
        
        rhs = diffusion + dissipation + coupling + nonlinear + memory_force + self_force + forcing + noise
        hat = (hat + dt * rhs) * self.mask
        
        self.field = fft.irfftn(hat, dim=(-3,-2,-1), norm='ortho')
        self.field = torch.clamp(self.field, -2.5, 2.5)
        
        recovery = 1.0 - self.lack_memory * 0.1
        self.memory = CONSTANTS['memory_rate'] * self.memory + (1 - CONSTANTS['memory_rate']) * self.field
        self.self_model = CONSTANTS['self_rate'] * self.self_model + (1 - CONSTANTS['self_rate']) * self.field
        
        self.lack_memory *= CONSTANTS['recovery_rate']
    
    def toy_evolution(self, external_input=0.0, coupling_from_others=0.0):
        noise = torch.randn(self.n_degrees, device=self.device) * CONSTANTS['temperature'] * 0.3
        synaptic = torch.tanh(self.W @ self.v)
        
        coupling_scaled = coupling_from_others * 0.3
        coupling_scaled = max(-0.5, min(0.5, coupling_scaled))
        
        tau = 0.32
        self.v += (-CONSTANTS['gamma'] * 0.6 * self.v + 
                   synaptic * 0.5 + 
                   external_input * 0.12 + 
                   noise * 0.6 +
                   coupling_scaled) * tau
        
        self.v = torch.tanh(self.v) * 0.75
        
        new_coherence = 1 - torch.mean(torch.abs(self.v - self.auto_modelo)).item() / 1.2
        self.coherence = 0.85 * self.coherence + 0.15 * max(0, min(1, new_coherence))
        
        self.auto_modelo = CONSTANTS['self_rate'] * self.auto_modelo + (1 - CONSTANTS['self_rate']) * self.v
        
        hebb = CONSTANTS['coupling_strength'] * 0.4 * torch.outer(self.v, self.v)
        oja = -CONSTANTS['coupling_strength'] * 0.12 * torch.outer(self.v, self.W.T @ self.v)
        self.W += hebb + oja
        self.W = torch.clamp(self.W, -0.9, 0.9)
        self.W.fill_diagonal_(0)
        
        row_sums = self.W.abs().sum(dim=1, keepdim=True)
        row_sums = torch.clamp(row_sums, min=1e-6)
        self.W = self.W / row_sums * 0.7
    
    def calculate_phi_star(self):
        """Φ* as spectral organization functional."""
        spec = torch.abs(fft.rfftn(self.field, dim=(-3,-2,-1), norm='ortho'))**2
        p = spec.flatten()
        p = p / (p.sum() + 1e-12)
        
        spectral_entropy = -(p * torch.log2(p + 1e-12)).sum()
        max_ent = np.log2(len(p))
        
        if max_ent > 0:
            entropy_ratio = (spectral_entropy / max_ent).item()
            self.phi = 1.0 - entropy_ratio
            self.phi = np.clip(self.phi, 0.25, 0.75)
        else:
            self.phi = 0.5
        
        self.delta = max(0, 1 - self.phi)
        
        phi_star_raw = self.phi - CONSTANTS['lambda_star'] * self.delta
        phi_star_raw = phi_star_raw * (1 - self.lack_memory * 0.3)
        self.phi_star = np.clip(phi_star_raw, CONSTANTS['phi_star_min'], CONSTANTS['phi_star_max'])
        
        self.phi_star_history.append(self.phi_star)
        if len(self.phi_star_history) > 5:
            self.phi_star = np.mean(list(self.phi_star_history)[-5:])
    
    def calculate_dig(self):
        """DIG as temporal autocorrelation functional."""
        if len(self.v_history) < 15:
            return self.dig
        
        v_list = list(self.v_history)[-30:]
        v_array = np.array([v.cpu().numpy() for v in v_list])
        
        if len(v_array) > 3:
            corr_sum = 0
            n_corrs = 0
            
            for i in range(min(6, self.n_degrees)):
                if len(v_array) > 2:
                    corr1 = np.corrcoef(v_array[:-1, i], v_array[1:, i])[0, 1]
                    if not np.isnan(corr1) and corr1 > 0:
                        corr_sum += corr1
                        n_corrs += 1
                
                if len(v_array) > 3:
                    corr2 = np.corrcoef(v_array[:-2, i], v_array[2:, i])[0, 1]
                    if not np.isnan(corr2) and corr2 > 0:
                        corr_sum += corr2
                        n_corrs += 1
            
            if n_corrs > 0:
                dig_raw = corr_sum / n_corrs
                dig_raw = dig_raw * (1 - self.lack_memory * 0.25)
                dig_raw = max(0.15, min(0.85, dig_raw))
                self.dig = 0.7 * self.dig + 0.3 * dig_raw
                return self.dig
        
        return 0.35
    
    def update_history(self):
        self.v_history.append(self.v.clone())
    
    def apply_perturbation(self, intensity):
        """Apply controlled perturbation (increased thermal noise)."""
        noise_level = CONSTANTS['temperature'] * (1.0 + intensity * CONSTANTS['lack_noise_boost'])
        
        self.lack_memory = min(1.0, self.lack_memory + intensity * 0.15)
        
        # Affects spectral field
        self.field += torch.randn_like(self.field) * noise_level * 0.8
        self.field = torch.clamp(self.field, -2.5, 2.5)
        
        # Affects agents
        self.v += torch.randn_like(self.v) * noise_level * 0.6
        self.v = torch.tanh(self.v) * 0.75
        
        # Reduces coherence
        self.coherence *= (1 - intensity * 0.2)
        self.coherence = max(0.2, self.coherence)
        
        # Partial memory reset
        self.memory = self.memory * (1 - intensity * 0.25) + self.field * (intensity * 0.25)
        self.auto_modelo = self.auto_modelo * (1 - intensity * 0.25) + self.v * (intensity * 0.25)
    
    def step(self, external_input=0.0, coupling=0.0, perturbation_intensity=0.0):
        self.spectral_evolution(external_forcing=external_input)
        self.toy_evolution(external_input=external_input, coupling_from_others=coupling)
        
        if perturbation_intensity > 0:
            self.apply_perturbation(perturbation_intensity)
        
        self.update_history()
        self.calculate_phi_star()
        self.calculate_dig()
        
        return {
            'phi_star': self.phi_star,
            'dig': self.dig,
            'coherence': self.coherence,
            'lack_memory': self.lack_memory
        }

# =============================================================================
# UNIVERSE
# =============================================================================

class HybridUniverse:
    def __init__(self, n_systems=4, grid_size=16, n_degrees=12, device='cpu'):
        self.n_systems = n_systems
        self.device = device
        self.systems = [HybridSystem(i, grid_size, n_degrees, device) for i in range(n_systems)]
        
        self.data = {
            'time': [],
            'phi_star_mean': [],
            'dig_mean': [],
            'coherence_mean': [],
            'lack_memory_mean': [],
        }
        
        self.perturbation_start = 700
        self.perturbation_end = 820
    
    def _compute_coupling(self, perturbation_active=False):
        n = self.n_systems
        coupling_matrix = torch.zeros((n, n), device=self.device)
        
        if perturbation_active:
            return coupling_matrix
        
        for i in range(n):
            for j in range(n):
                if i != j:
                    field_i = self.systems[i].field
                    field_j = self.systems[j].field
                    corr = torch.mean(field_i * field_j).item()
                    coupling = CONSTANTS['coupling_strength'] * corr
                    coupling = max(-CONSTANTS['coupling_max'], min(CONSTANTS['coupling_max'], coupling))
                    coupling_matrix[i, j] = coupling
        
        return coupling_matrix
    
    def evolve(self, n_steps=1500):
        print("\n" + "="*70)
        print("IPM PROTOCOL - DYNAMIC REGIME SENSITIVITY ANALYSIS")
        print("="*70)
        print(f"Systems: {self.n_systems}")
        print(f"Perturbation: turns {self.perturbation_start} to {self.perturbation_end}")
        print(f"λ* = {CONSTANTS['lambda_star']}")
        print("-"*70)
        
        for step in range(n_steps):
            perturbation_active = (self.perturbation_start <= step < self.perturbation_end)
            
            if perturbation_active:
                progress = min(1.0, (step - self.perturbation_start) / CONSTANTS['lack_duration'])
                perturbation_intensity = CONSTANTS['lack_strength'] * progress
            else:
                perturbation_intensity = 0.0
            
            coupling_matrix = self._compute_coupling(perturbation_active)
            external_signal = 0.1 * np.sin(step * 0.018) + 0.05 * np.sin(step * 0.055)
            
            for i, sys in enumerate(self.systems):
                coupling = torch.sum(coupling_matrix[i, :]).item()
                sys.step(external_input=external_signal, coupling=coupling, 
                         perturbation_intensity=perturbation_intensity)
            
            if step % 20 == 0:
                self.data['time'].append(step)
                self.data['phi_star_mean'].append(np.mean([s.phi_star for s in self.systems]))
                self.data['dig_mean'].append(np.mean([s.dig for s in self.systems]))
                self.data['coherence_mean'].append(np.mean([s.coherence for s in self.systems]))
                self.data['lack_memory_mean'].append(np.mean([s.lack_memory for s in self.systems]))
            
            if step % 150 == 0 and step > 0:
                status = "🔴 PERTURBATION" if perturbation_active else "🟢 NORMAL"
                dig_val = self.data['dig_mean'][-1] if self.data['dig_mean'] else 0
                phi_val = self.data['phi_star_mean'][-1] if self.data['phi_star_mean'] else 0
                print(f"Step {step:4d} | {status:14} | Φ*={phi_val:.4f} | DIG={dig_val:.4f}")
        
        print("\n✅ Simulation complete.\n")
    
    def analyze(self):
        print("="*70)
        print("REGIME SHIFT ANALYSIS - METRIC RESPONSE TO PERTURBATION")
        print("="*70)
        
        if len(self.data['time']) < 10:
            print("Insufficient data.")
            return
        
        times = np.array(self.data['time'])
        perturbation_idx = np.argmin(np.abs(times - self.perturbation_start))
        
        pre_start = max(0, perturbation_idx - 10)
        pre_end = perturbation_idx
        post_start = perturbation_idx
        post_end = min(len(self.data['dig_mean']), perturbation_idx + 15)
        
        pre_dig = np.mean(self.data['dig_mean'][pre_start:pre_end])
        post_dig = np.mean(self.data['dig_mean'][post_start:post_end])
        pre_phi = np.mean(self.data['phi_star_mean'][pre_start:pre_end])
        post_phi = np.mean(self.data['phi_star_mean'][post_start:post_end])
        
        corr_all = np.corrcoef(self.data['phi_star_mean'], self.data['dig_mean'])[0, 1]
        
        post_phi_data = self.data['phi_star_mean'][post_start:post_end+20]
        post_dig_data = self.data['dig_mean'][post_start:post_end+20]
        corr_post = np.corrcoef(post_phi_data, post_dig_data)[0, 1] if len(post_phi_data) > 5 else 0
        
        print(f"\n📊 PRE-PERTURBATION:")
        print(f"   Φ* = {pre_phi:.4f} | DIG = {pre_dig:.4f}")
        
        print(f"\n📊 POST-PERTURBATION:")
        print(f"   Φ* = {post_phi:.4f} | DIG = {post_dig:.4f}")
        
        print(f"\n📈 VARIATIONS:")
        print(f"   Φ*:  {pre_phi:.4f}{post_phi:.4f} ({(post_phi-pre_phi)*100:+.1f}%)")
        print(f"   DIG: {pre_dig:.4f}{post_dig:.4f} ({(post_dig-pre_dig)*100:+.1f}%)")
        
        print(f"\n📊 CORRELATIONS:")
        print(f"   Global (all data): {corr_all:.3f}")
        print(f"   Post-perturbation: {corr_post:.3f}")
        
        print(f"\n{'─'*50}")
        
        dig_change = (post_dig - pre_dig) * 100
        phi_change = (post_phi - pre_phi) * 100
        
        print("\n📋 PRELIMINARY ASSESSMENT:")
        print(f"   • DIG change: {dig_change:+.1f}%")
        print(f"   • Φ* change: {phi_change:+.1f}%")
        print(f"   • Φ* × DIG correlation: {corr_all:.3f}")
        
        print("\n" + "="*70)
        print("LIMITATIONS (EXPLICIT):")
        print("   • Demonstrates co-responsiveness to global noise only")
        print("   • Correlation (~0.59) may arise from shared dependence on:")
        print("        - Same driver (global noise)")
        print("        - System energy/entropy")
        print("        - Different filtering of the same latent field")
        print("   • Does not separate thermal from structural regimes")
        print("   • Metrics are low-order statistics on correlated states")
        print("   • Preliminary sensitivity analysis, not dimensional validation")
        print("="*70)
        
        self.results = {
            'dig_change': dig_change,
            'phi_change': phi_change,
            'correlation': corr_all,
            'pre_dig': pre_dig,
            'post_dig': post_dig,
            'pre_phi': pre_phi,
            'post_phi': post_phi
        }
    
    def visualize(self):
        if len(self.data['time']) < 10:
            return
        
        plt.style.use('dark_background')
        fig, axes = plt.subplots(2, 2, figsize=(14, 10))
        
        # Φ* and DIG together
        axes[0,0].plot(self.data['time'], self.data['phi_star_mean'], color='cyan', linewidth=2, label='Φ*')
        axes[0,0].plot(self.data['time'], self.data['dig_mean'], color='lime', linewidth=2, label='DIG')
        axes[0,0].axvline(x=self.perturbation_start, color='red', linestyle='--', linewidth=2, label='Perturbation')
        axes[0,0].axvline(x=self.perturbation_end, color='orange', linestyle='--', linewidth=2, label='End')
        axes[0,0].set_title("Metric Response to Perturbation")
        axes[0,0].set_xlabel("Turns")
        axes[0,0].set_ylim(0, 0.9)
        axes[0,0].legend()
        axes[0,0].grid(True, alpha=0.3)
        
        # Φ* only
        axes[0,1].plot(self.data['time'], self.data['phi_star_mean'], color='cyan', linewidth=2)
        axes[0,1].axvline(x=self.perturbation_start, color='red', linestyle='--', linewidth=2)
        axes[0,1].axvline(x=self.perturbation_end, color='orange', linestyle='--', linewidth=2)
        axes[0,1].fill_between(self.data['time'], 
                               np.array(self.data['phi_star_mean']) - 0.05,
                               np.array(self.data['phi_star_mean']) + 0.05,
                               alpha=0.2, color='cyan')
        axes[0,1].set_title("Φ* (Spectral Organization)")
        axes[0,1].set_xlabel("Turns")
        axes[0,1].set_ylim(0, 0.8)
        axes[0,1].grid(True, alpha=0.3)
        
        # DIG only
        axes[1,0].plot(self.data['time'], self.data['dig_mean'], color='lime', linewidth=2)
        axes[1,0].axvline(x=self.perturbation_start, color='red', linestyle='--', linewidth=2)
        axes[1,0].axvline(x=self.perturbation_end, color='orange', linestyle='--', linewidth=2)
        axes[1,0].set_title("DIG (Temporal Autocorrelation)")
        axes[1,0].set_xlabel("Turns")
        axes[1,0].set_ylim(0, 0.9)
        axes[1,0].grid(True, alpha=0.3)
        
        # Scatter with regression
        corr_val = np.corrcoef(self.data['phi_star_mean'], self.data['dig_mean'])[0, 1]
        axes[1,1].scatter(self.data['phi_star_mean'], self.data['dig_mean'], 
                         c=self.data['time'], cmap='coolwarm', alpha=0.6, s=40)
        axes[1,1].set_xlabel("Φ*")
        axes[1,1].set_ylabel("DIG")
        axes[1,1].set_title(f"Φ* vs DIG (correlation = {corr_val:.3f})")
        axes[1,1].grid(True, alpha=0.3)
        
        if len(self.data['phi_star_mean']) > 1:
            z = np.polyfit(self.data['phi_star_mean'], self.data['dig_mean'], 1)
            p = np.poly1d(z)
            x_trend = np.linspace(min(self.data['phi_star_mean']), max(self.data['phi_star_mean']), 100)
            axes[1,1].plot(x_trend, p(x_trend), 'r--', alpha=0.8, linewidth=2)
        
        plt.colorbar(axes[1,1].collections[0], ax=axes[1,1], label='Turn')
        plt.suptitle(f"METRIC RESPONSE ANALYSIS - Φ* × DIG CORRELATION = {corr_val:.3f}", fontsize=14)
        plt.tight_layout()
        plt.show()

# =============================================================================
# EXECUTION
# =============================================================================

if __name__ == "__main__":
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Device: {device}")
    
    universe = HybridUniverse(n_systems=4, grid_size=16, n_degrees=12, device=device)
    universe.evolve(n_steps=1500)
    universe.analyze()
    universe.visualize()

8. Results

Simulation output:

text
Step 150 | 🟢 NORMAL       | Φ*=0.5167 | DIG=0.8500
Step 300 | 🟢 NORMAL       | Φ*=0.5243 | DIG=0.8500
Step 450 | 🟢 NORMAL       | Φ*=0.5294 | DIG=0.8204
Step 600 | 🟢 NORMAL       | Φ*=0.5338 | DIG=0.8500
Step 750 | 🔴 PERTURBATION | Φ*=0.3669 | DIG=0.2783
Step 900 | 🟢 NORMAL       | Φ*=0.3765 | DIG=0.8500
Step 1050| 🟢 NORMAL       | Φ*=0.4226 | DIG=0.8500
Step 1200| 🟢 NORMAL       | Φ*=0.4454 | DIG=0.8499
Step 1350| 🟢 NORMAL       | Φ*=0.4603 | DIG=0.8500

Final analysis:

MetricPre-PerturbationPost-PerturbationChange
Φ*0.53350.3749-15.9%
DIG0.84510.5918-25.3%

Correlations:

  • Global (all data): 0.594

  • Post-perturbation: 0.654


9. Interpretation

ObservationInterpretation
Φ and DIG drop during perturbation*Both metrics show co-responsiveness to increased global noise
Metrics recover after perturbationSystem returns to previous regime when noise decreases
Correlation (0.594) between Φ and DIG*Metrics correlate under global noise — not evidence of dimensional independence
**DIG drops more (-25.3%) than Φ* (-15.9%)**Temporal autocorrelation is more sensitive to noise than spectral organization

10. What This Experiment Demonstrates

Demonstrated:

  • Metrics respond consistently to increased global noise

  • Metrics correlate with each other under perturbation

  • Metrics recover when perturbation ceases

Not demonstrated (explicit limitations):

  • That metrics capture "integration" rather than just global noise

  • Separation between thermal and structural regimes

  • That Φ* and DIG measure independent dimensions of the system

  • Epistemological adequacy across contexts

Important note on correlation: The observed correlation (~0.59) may arise from shared dependence on:

  • The same driver (global noise)

  • System energy/entropy

  • Different filtering of the same latent field

This experiment does not distinguish these possibilities.


11. Next Steps for Deeper Analysis

To separate sensitivity to thermal regime from structural regime, the next experiment should:

ConditionWhat variesWhat is fixed
ANoise intensityCoupling topology
BCoupling topologyNoise intensity

This would allow asking:

  • Do Φ* and DIG respond differently to thermal vs structural changes?

  • Do they desynchronize under certain conditions?

  • Which metric is more sensitive to each dimension?

This is the critical experiment to determine whether metrics capture structure or just energy.


12. Conclusion

The IPM Protocol demonstrates that Φ* and DIG show co-responsiveness to controlled perturbation in a coupled dynamical system. The experiment shows:

  1. *Φ changes by -15.9%** during increased noise

  2. DIG changes by -25.3% during increased noise

  3. Φ and DIG correlate at 0.594* under global noise

  4. Metrics recover after perturbation — system returns to previous regime

This is a preliminary sensitivity analysis, not epistemological validation. The experiment demonstrates consistent metric response to global noise, not that metrics capture "ontological integration" or separate thermal from structural regimes.

The observed correlation may arise from shared dependence on global noise, system energy, or latent field dynamics. Distinguishing these possibilities requires the next experiment (varying coupling topology independently of noise).



My Results:

Device: cpu

======================================================================
IPM PROTOCOL - DYNAMIC REGIME SENSITIVITY ANALYSIS
======================================================================
Systems: 4
Perturbation: turns 700 to 820
λ* = 0.25
----------------------------------------------------------------------
Step  150 | 🟢 NORMAL       | Φ*=0.5165 | DIG=0.8500
Step  300 | 🟢 NORMAL       | Φ*=0.5241 | DIG=0.8500
Step  450 | 🟢 NORMAL       | Φ*=0.5293 | DIG=0.8019
Step  600 | 🟢 NORMAL       | Φ*=0.5338 | DIG=0.8500
Step  750 | 🔴 PERTURBATION | Φ*=0.3666 | DIG=0.3085
Step  900 | 🟢 NORMAL       | Φ*=0.3766 | DIG=0.8500
Step 1050 | 🟢 NORMAL       | Φ*=0.4223 | DIG=0.8500
Step 1200 | 🟢 NORMAL       | Φ*=0.4449 | DIG=0.8499
Step 1350 | 🟢 NORMAL       | Φ*=0.4596 | DIG=0.8500

✅ Simulation complete.

======================================================================
REGIME SHIFT ANALYSIS - METRIC RESPONSE TO PERTURBATION
======================================================================

📊 PRE-PERTURBATION:
   Φ* = 0.5334 | DIG = 0.8436

📊 POST-PERTURBATION:
   Φ* = 0.3749 | DIG = 0.5951

📈 VARIATIONS:
   Φ*:  0.5334 → 0.3749 (-15.9%)
   DIG: 0.8436 → 0.5951 (-24.8%)

📊 CORRELATIONS:
   Global (all data): 0.596
   Post-perturbation: 0.668

──────────────────────────────────────────────────

📋 PRELIMINARY ASSESSMENT:
   • DIG change: -24.8%
   • Φ* change: -15.9%
   • Φ* × DIG correlation: 0.596

======================================================================
LIMITATIONS (EXPLICIT):
   • Demonstrates co-responsiveness to global noise only
   • Correlation (~0.59) may arise from shared dependence on:
        - Same driver (global noise)
        - System energy/entropy
        - Different filtering of the same latent field
   • Does not separate thermal from structural regimes
   • Metrics are low-order statistics on correlated states
   • Preliminary sensitivity analysis, not dimensional validation
======================================================================

IPM Ethical Framework

  IPM Ethical Framework   Operationalization of the Gradient Precautionary Heuristic     Author: Taotuner Date: June 2026 ...