lantern

ca-constraint-experiment-harness

CA Constraint Experiment Harness

metadata:
  type: graphnode
  no_cache: true

Executable 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: null

fetch

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