# IO Simulation v2.4 Code (1D, P_target Adapt v3, Dynamic CA - Stability Weighted)
## 1. Objective
This node provides the updated Python code (v2.4) for the 1D Information Dynamics (IO) simulation. This version refines the dynamic Causality (CA) weight update rule, implementing **stability-weighted reinforcement** as specified in [[0118_IO_Formalism_Refinement]]. This is the **decisive test** for the dynamic CA mechanism within the current v2.x formalism, as per [[releases/archive/Information Ontology 1/0121_IO_Fail_Fast_Directive]]. It builds directly upon the v2.3 code [[releases/archive/Information Ontology 1/0119_IO_Simulation_v2.3_Code]].
## 2. Key Changes from v2.3 Code ([[0119]])
* **Modified Phase 4 (Edge Weight Update):** The calculation of the weight change `Δw` now incorporates the stability of the *target* node's state, using the `f_Theta` function to weight the correlation.
## 3. Python Code (v2.4)
```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import io
import base64
# --- Parameters ---
# These will be set specifically in execution nodes (e.g., 0123)
# Example placeholder values:
N = 200
S_max = 1000
h = 0.5
alpha = 0.1
gamma = 1.0
p_M = 0.25
delta_theta_inc = 0.05
theta_max = 5.0
theta_base = 0.0
lambda_base_adapt = 0.05
beta_adapt = 0.1
p_min = 1e-9
w_init = 1.0
delta_w_base = 0.01
decay_rate = 0.001
w_max = 10.0
seed = 42
# --- Helper Functions (Identical to 0119) ---
def normalize_p_target(p_target_array):
"""Normalizes P_target rows and applies p_min floor."""
if p_target_array.ndim == 1: p_target_array = p_target_array.reshape(1, -1)
p_target_array = np.maximum(p_target_array, p_min)
row_sums = p_target_array.sum(axis=1)
row_sums[row_sums == 0] = 1
p_target_array = p_target_array / row_sums[:, np.newaxis]
return p_target_array
def calculate_k_local(epsilon_state):
"""Calculates local contrast K based on immediate neighbors."""
neighbors_left = np.roll(epsilon_state, 1)
neighbors_right = np.roll(epsilon_state, -1)
k_local = 0.5 * (np.abs(epsilon_state - neighbors_left) +
np.abs(epsilon_state - neighbors_right))
return k_local
def f_H(h_param, p_leave_array):
"""Η drive function."""
return np.clip(h_param * p_leave_array, 0.0, 1.0)
def f_Theta(theta_val_array, alpha_param):
"""Θ resistance function."""
return 1.0 / (1.0 + alpha_param * theta_val_array)
def f_K(k_local_array, gamma_param):
"""K gating function."""
return np.power(k_local_array, gamma_param)
# --- Simulation Function ---
def run_io_simulation_v2_4(params):
"""Runs the IO v2.4 simulation with dynamic CA weights (stability weighted)."""
N = params['N']
S_max = params['S_max']
h = params['h']
alpha = params['alpha']
gamma = params['gamma']
p_M = params['p_M']
delta_theta_inc = params['delta_theta_inc']
theta_max = params['theta_max']
theta_base = params['theta_base']
lambda_base_adapt = params['lambda_base_adapt']
beta_adapt = params['beta_adapt']
p_min = params['p_min']
# CA Params
w_init = params['w_init']
delta_w_base = params['delta_w_base']
decay_rate = params['decay_rate']
w_max = params['w_max']
seed = params.get('seed', None)
if seed is not None:
np.random.seed(seed)
else:
np.random.seed()
# Initialization
epsilon_state = np.random.randint(0, 2, size=N)
p_target_state = np.full((N, 2), 0.5)
theta_state = np.zeros(N)
# Initialize causal weights (nearest neighbors in 1D ring)
w_left = np.full(N, w_init) # Weight for i-1 -> i
w_right = np.full(N, w_init) # Weight for i+1 -> i
epsilon_history = np.zeros((S_max, N), dtype=int)
avg_theta_history = np.zeros(S_max)
avg_ptarget_entropy_history = np.zeros(S_max)
avg_w_left_history = np.zeros(S_max)
avg_w_right_history = np.zeros(S_max)
# --- Simulation Loop ---
for S in range(S_max):
epsilon_history[S, :] = epsilon_state
prev_epsilon = epsilon_state.copy()
prev_p_target = p_target_state.copy()
prev_theta = theta_state.copy()
prev_w_left = w_left.copy()
prev_w_right = w_right.copy()
# --- Phase 1: Calculate Influences (using dynamic weights) ---
k_local = calculate_k_local(prev_epsilon)
neighbors_left_eps = np.roll(prev_epsilon, 1)
neighbors_right_eps = np.roll(prev_epsilon, -1)
# Influence_k = weighted sum of neighbors in state k
influence_0 = (neighbors_left_eps == 0).astype(int) * prev_w_left + \
(neighbors_right_eps == 0).astype(int) * prev_w_right
influence_1 = (neighbors_left_eps == 1).astype(int) * prev_w_left + \
(neighbors_right_eps == 1).astype(int) * prev_w_right
total_causal_weight = influence_0 + influence_1
# --- Phase 2: Determine Probability of State Change ---
p_leave = 1.0 - prev_p_target[np.arange(N), prev_epsilon]
prob_H_driven = f_H(h, p_leave)
prob_Theta_resisted = f_Theta(prev_theta, alpha)
prob_K_gated = f_K(k_local, gamma)
P_change = prob_H_driven * prob_Theta_resisted * prob_K_gated
# --- Phase 3: Execute State Transition ---
r_change = np.random.rand(N)
change_mask = r_change < P_change
no_change_mask = ~change_mask
current_epsilon = prev_epsilon # Use current_epsilon for clarity
next_epsilon = current_epsilon.copy()
next_theta = prev_theta.copy()
next_p_target = prev_p_target.copy()
# Determine Target State for Changing Nodes
changing_indices = np.where(change_mask)[0]
if len(changing_indices) > 0:
p_target_0_intrinsic = prev_p_target[changing_indices, 0]
p_target_1_intrinsic = prev_p_target[changing_indices, 1]
# Multiplicative bias rule from 0101/0104
mod_factor_0 = (1 + p_M * influence_0[changing_indices] / 2.0)
mod_factor_1 = (1 + p_M * influence_1[changing_indices] / 2.0)
p_prime_0 = p_target_0_intrinsic * mod_factor_0
p_prime_1 = p_target_1_intrinsic * mod_factor_1
sum_p_prime = p_prime_0 + p_prime_1
sum_p_prime[sum_p_prime == 0] = 1
P_modified_target_0 = p_prime_0 / sum_p_prime
r_target = np.random.rand(len(changing_indices))
epsilon_target = (r_target >= P_modified_target_0).astype(int)
next_epsilon[changing_indices] = epsilon_target
# Update Theta for ALL nodes
next_theta[change_mask] = theta_base
next_theta[no_change_mask] = np.minimum(prev_theta[no_change_mask] + delta_theta_inc, theta_max)
# Update P_target using Mechanism v3
p_context = np.zeros_like(prev_p_target)
valid_ca = total_causal_weight > 0
valid_indices = np.where(valid_ca)[0]
if len(valid_indices) > 0:
p_context[valid_indices, 0] = influence_0[valid_indices] / total_causal_weight[valid_indices]
p_context[valid_indices, 1] = influence_1[valid_indices] / total_causal_weight[valid_indices]
invalid_indices = np.where(~valid_ca)[0]
if len(invalid_indices) > 0:
p_context[invalid_indices, :] = 0.5
f_theta_adapt = 1.0 / (1.0 + beta_adapt * prev_theta)
lambda_eff = lambda_base_adapt * f_theta_adapt
next_p_target = (1.0 - lambda_eff[:, np.newaxis]) * prev_p_target + lambda_eff[:, np.newaxis] * p_context
next_p_target = normalize_p_target(next_p_target)
# --- Phase 4: Update Causal Network Weights (Stability Weighted) ---
# Calculate correlations: +1 if neighbor state S matches target state S+1, -1 otherwise
corr_left = np.where(neighbors_left_eps == next_epsilon, 1, -1)
corr_right = np.where(neighbors_right_eps == next_epsilon, 1, -1)
# Stability-weighted correlation (f_Theta_corr = 1 + theta/theta_max)
f_theta_corr_left = 1.0 + next_theta / theta_max
f_theta_corr_right = f_theta_corr_left # Same theta for both neighbors
dw_left = delta_w_base * corr_left * f_theta_corr_left
dw_right = delta_w_base * corr_right * f_theta_corr_right
# Apply decay and update weights
w_left = np.maximum(0, prev_w_left * (1.0 - decay_rate) + dw_left)
w_right = np.maximum(0, prev_w_right * (1.0 - decay_rate) + dw_right)
# Apply optional max weight
if w_max is not None:
w_left = np.minimum(w_left, w_max)
w_right = np.minimum(w_right, w_max)
# --- Assign final states for next step ---
epsilon_state = next_epsilon
theta_state = next_theta
p_target_state = next_p_target
# --- Calculate Metrics for History ---
avg_theta_history[S] = np.mean(theta_state)
p0 = p_target_state[:, 0]
p1 = p_target_state[:, 1]
ptarget_entropy = - (p0 * np.log2(p0) + p1 * np.log2(p1))
avg_ptarget_entropy_history[S] = np.mean(ptarget_entropy)
avg_w_left_history[S] = np.mean(w_left)
avg_w_right_history[S] = np.mean(w_right)
# --- Prepare Results ---
results = {
"parameters": params,
"epsilon_history": epsilon_history,
"avg_theta_history": avg_theta_history,
"avg_ptarget_entropy_history": avg_ptarget_entropy_history,
"avg_w_left_history": avg_w_left_history,
"avg_w_right_history": avg_w_right_history
}
return results
# --- Plotting Function (Identical to 0119) ---
def plot_results_v2_3(results, title_suffix=""):
"""Generates plots for v2.3 results (adds weight plots) and returns base64 string."""
params = results["parameters"]
epsilon_history = results["epsilon_history"]
avg_theta_history = results["avg_theta_history"]
avg_ptarget_entropy_history = results["avg_ptarget_entropy_history"]
avg_w_left_history = results["avg_w_left_history"]
avg_w_right_history = results["avg_w_right_history"]
S_max = params['S_max']
fig, axs = plt.subplots(4, 1, figsize=(10, 10), sharex=True) # Increased height for 4 plots
cmap = mcolors.ListedColormap(['black', 'white'])
# Spacetime plot
axs[0].imshow(epsilon_history, cmap=cmap, aspect='auto', interpolation='none')
axs[0].set_title(f'IO v2.3 Simulation {title_suffix}: ε State Evolution')
axs[0].set_ylabel('Sequence Step (S)')
# Average Theta
axs[1].plot(avg_theta_history)
axs[1].set_title('Average Stability (Θ_val)')
axs[1].set_ylabel('Avg Θ_val')
axs[1].grid(True)
# Average P_target Entropy
axs[2].plot(avg_ptarget_entropy_history)
axs[2].set_title('Average Potentiality Entropy (H(P_target))')
axs[2].set_ylabel('Avg Entropy (bits)')
axs[2].grid(True)
# Average Causal Weights
axs[3].plot(avg_w_left_history, label='Avg W(left -> self)')
axs[3].plot(avg_w_right_history, label='Avg W(right -> self)')
axs[3].set_title('Average Causal Weights (w)')
axs[3].set_xlabel('Sequence Step (S)')
axs[3].set_ylabel('Avg Weight')
axs[3].legend()
axs[3].grid(True)
plt.tight_layout()
buf = io.BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
plot_base64 = base64.b64encode(buf.read()).decode('utf-8')
buf.close()
plt.close(fig)
return plot_base64
# --- Example Usage (commented out - execution happens in separate nodes) ---
# params_run10 = {
# 'N': 200, 'S_max': 1000, 'h': 0.5, 'alpha': 0.1, 'gamma': 1.0,
# 'p_M': 0.25, 'delta_theta_inc': 0.05, 'theta_max': 5.0,
# 'theta_base': 0.0, 'lambda_base_adapt': 0.05, 'beta_adapt': 0.1,
# 'p_min': 1e-9,
# 'w_init': 1.0, 'delta_w_base': 0.01, 'decay_rate': 0.001, 'w_max': 10.0,
# 'seed': 42
# }
# results_run10 = run_io_simulation_v2_3(params_run10)
# plot_b64_run10 = plot_results_v2_3(results_run10, title_suffix="(Run 10 - Dynamic CA Stability Weighted)")
# print(f"Run 10 Complete.")
# print(f"Final Avg Theta: {results_run10['avg_theta_history'][-1]:f}")
# print(f"Final Avg P_target Entropy: {results_run10['avg_ptarget_entropy_history'][-1]:f}")
# print(f"Final Avg W_Left: {results_run10['avg_w_left_history'][-1]:f}")
# print(f"Final Avg W_Right: {results_run10['avg_w_right_history'][-1]:f}")
# print(f"Plot generated: {plot_b64_run10[:100]}...")
```
## 4. Code Structure and Logic
* The code builds directly upon [[releases/archive/Information Ontology 1/0119_IO_Simulation_v2.3_Code]].
* The key change is in **Phase 4 (Update Causal Network Weights)**:
* The calculation of `dw_left` and `dw_right` now incorporates the stability-weighted correlation:
```python
f_theta_corr_left = 1.0 + next_theta / theta_max
f_theta_corr_right = f_theta_corr_left # Same theta for both neighbors
dw_left = delta_w_base * corr_left * f_theta_corr_left
dw_right = delta_w_base * corr_right * f_theta_corr_right
```
This means that links are reinforced more strongly if they contribute to a *stable* state (high `next_theta`).
## 5. Next Steps
This code (v2.4) is now ready for the decisive test of the dynamic CA mechanism. The next node ([[releases/archive/Information Ontology 1/0123_IO_Simulation_Run10]]) will execute this code, using parameters similar to Run 8/9 (dynamic regime) and analyze the results to determine if this stability-weighted reinforcement leads to meaningful network adaptation and more complex emergent behavior. If it fails, we will pivot to a different approach.