ca-constraint-experiment-harness
CA Constraint Experiment Harness
metadata:
type: graphnode
no_cache: trueExecutable test harness for the message-passing invariant. Runs all constraint-breaking experiments and produces empirical data.
config
# Experiment Parameters
trials: 5
steps: 300
grid_size: 50
# Output Mode
# - null: return results for graphnode (stateless)
# - "table": write results to Results Table section (stateful)
persist_to: nullfetch
import numpy as np
from scipy.ndimage import convolve
from datetime import datetime
# Read config
trials = config.get('trials', 5)
steps = config.get('steps', 300)
size = config.get('grid_size', 50)
persist_to = config.get('persist_to', None)
kernel = np.array([[1,1,1],[1,0,1],[1,1,1]])
def run_ca(rule_fn, trials, steps, size):
"""Run CA with given rule function, return variance and density stats."""
variances = []
densities = []
for seed in range(trials):
rng = np.random.default_rng(42 + seed)
grid = rng.integers(0, 2, (size, size))
alive_history = []
for _ in range(steps):
grid = rule_fn(grid, rng)
alive_history.append(np.sum(grid))
variances.append(np.var(alive_history[-50:]))
densities.append(np.mean(grid))
return np.mean(variances), np.std(variances), np.mean(densities)
# Rule definitions
def rule_gol(grid, rng):
n = convolve(grid, kernel, mode='wrap')
return ((grid == 0) & (n == 3) | (grid == 1) & ((n == 2) | (n == 3))).astype(int)
def rule_break_conservation(grid, rng):
n = convolve(grid, kernel, mode='wrap')
base = ((grid == 0) & (n == 3) | (grid == 1) & ((n == 2) | (n == 3))).astype(int)
# Random 5% flips - breaks conservation
return np.where(rng.random(grid.shape) < 0.05, 1 - base, base)
def rule_break_memory(grid, rng):
n = convolve(grid, kernel, mode='wrap')
# Ignore self state - only use neighbor count
return ((n == 3) | (n == 2)).astype(int)
def rule_break_hierarchy(grid, rng):
n = convolve(grid, kernel, mode='wrap')
# Random thresholds per cell - breaks consistent hierarchy
thresh = rng.integers(1, 5, grid.shape)
return (n >= thresh).astype(int)
def rule_enhance_prediction(grid, rng):
n = convolve(grid, kernel, mode='wrap')
base = ((grid == 0) & (n == 3) | (grid == 1) & ((n == 2) | (n == 3))).astype(int)
# Momentum smoothing - average with previous
return ((base + grid) // 2).astype(int)
def rule_overprediction(grid, rng):
n = convolve(grid, kernel, mode='wrap')
base = ((grid == 0) & (n == 3) | (grid == 1) & ((n == 2) | (n == 3))).astype(int)
# Anticipate future - preemptive births/deaths
fn = convolve(base, kernel, mode='wrap')
result = np.where((base == 0) & (fn == 3), 1, base)
result = np.where((base == 1) & ((fn < 2) | (fn > 3)), 0, result)
return result
# Define experiments
experiments = [
("GoL (Baseline)", "2a-2d intact", "Stable reference", rule_gol, None),
("Break 2a (Conservation)", "Random 5% flips", "High variance (chaos)", rule_break_conservation, 1.5),
("Break 2b (Memory)", "Ignore self state", "No persistent structure", rule_break_memory, 2.0),
("Break 2c (Hierarchy)", "Random thresholds", "Collapse to degenerate", rule_break_hierarchy, None),
("Enhance 2d (Prediction)", "Momentum smoothing", "Lower variance (smoothing)", rule_enhance_prediction, 0.8),
("Break 2d (Overprediction)", "Anticipate future", "Never better than baseline", rule_overprediction, 1.0),
]
# Run all experiments
tests = []
baseline_var = None
passed = 0
for rule_name, constraint, prediction, rule_fn, threshold in experiments:
var_mean, var_std, density = run_ca(rule_fn, trials, steps, size)
# Determine status based on prediction
if rule_name == "GoL (Baseline)":
baseline_var = var_mean
status = "✅ BASELINE"
passed += 1
elif threshold is None:
# Hierarchy check - should collapse
if density > 0.95 or density < 0.05:
status = "✅ CONFIRMED"
passed += 1
else:
status = "❌ FAILED"
elif threshold < 1.0:
# Should be lower variance
if var_mean < baseline_var * threshold:
status = "✅ CONFIRMED"
passed += 1
else:
status = "❌ FAILED"
else:
# Should be higher variance (or at least not better)
if var_mean >= baseline_var * 0.9: # Allow 10% margin
status = "✅ CONFIRMED"
passed += 1
else:
status = "❌ FAILED"
tests.append({
"rule": rule_name,
"constraint": constraint,
"prediction": prediction,
"variance": f"{var_mean:.0f} ± {var_std:.0f}",
"density": f"{density:.3f}",
"status": status
})
# If persist mode, write to table
if persist_to == 'table':
for i, test in enumerate(tests):
execute('ca-constraint-experiment-harness:results',
action='update_row',
params={'row': i, 'values': [
test['rule'], test['constraint'], test['prediction'],
test['variance'], test['density'], test['status']
]})
# Note: poke to YAML not yet working, table update above is the main feature
pass
result = {
"tests": tests,
"summary": {
"total": len(tests),
"passed": passed,
"failed": len(tests) - passed,
"success_rate": f"{passed}/{len(tests)} ({100*passed//len(tests)}%)",
"trials_per_rule": trials,
"steps_per_trial": steps,
"grid_size": size
},
"persisted": persist_to == 'table'
}❌ Fence Execution Error: No module named 'numpy' Traceback (most recent call last): File "/app/oculus/providers/python_provider.py", line 468, in execute exec(fence_content, exec_globals, exec_locals) File "
", line 1, in ModuleNotFoundError: No module named 'numpy'
Results Table
Virtual table for persist mode writes.
| Rule | Constraint | Prediction | Variance | Density | Status |
|---|---|---|---|---|---|
| GoL (Baseline) | 2a-2d intact | Stable reference | 667 ± 639 | 0.076 | ✅ BASELINE |
| Break 2a (Conservation) | Random 5% flips | High variance (chaos) | 1165 ± 354 | 0.319 | ✅ CONFIRMED |
| Break 2b (Memory) | Ignore self state | No persistent structure | 1705 ± 384 | 0.378 | ✅ CONFIRMED |
| Break 2c (Hierarchy) | Random thresholds | Collapse to degenerate | 0 ± 0 | 1.000 | ✅ CONFIRMED |
| Enhance 2d (Prediction) | Momentum smoothing | Lower variance (smoothing) | 0 ± 0 | 0.016 | ✅ CONFIRMED |
| Break 2d (Overprediction) | Anticipate future | Never better than baseline | 669 ± 850 | 0.052 | ✅ CONFIRMED |
Provenance
- Created: 2026-01-06
- Pattern: [[pattern-looking-glass-development]]
- Case: task-54c00d7b-9129-4db2-87a3-8b758f65fb4e
- Note: Virtual table update_row bug FIXED (case:task-ff80b6ad-6240-489c-b31f-a4e999816588)
North
slots:
- slug: falsifiable-experiments-message-passing
context:
- Harness backs the falsifiable experiments documentation