Characteristic-Region Gain Controller / experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2import math
  3import random
  4from pathlib import Path
  5
  6import numpy as np
  7
  8SEED = 982
  9np.random.seed(SEED)
 10random.seed(SEED)
 11
 12
 13def q_weights(q, alpha, K):
 14    """Finite truncation of (q^alpha;q)_k/(q;q)_k from the paper."""
 15    w = np.empty(K, dtype=float)
 16    w[0] = 1.0
 17    for k in range(1, K):
 18        w[k] = w[k - 1] * (1.0 - q ** (alpha + k - 1)) / (1.0 - q ** k)
 19    return w
 20
 21
 22def companion(g, weights):
 23    K = len(weights)
 24    M = np.zeros((K, K), dtype=float)
 25    M[0, :] = g * weights
 26    M[1:, :-1] = np.eye(K - 1)
 27    return M
 28
 29
 30def rho(g, weights):
 31    return float(np.max(np.abs(np.linalg.eigvals(companion(g, weights)))))
 32
 33
 34def controller(g, rho_hat, delta=0.05, eps=1e-8, g_max=10.0, eta_gain=0.01):
 35    target = 1.0 - delta
 36    # Conservative branch from the proposal; optional slow growth is only used
 37    # when the estimate is comfortably below the target.
 38    if rho_hat > target:
 39        return max(0.0, g * min(1.0, target / (rho_hat + eps)))
 40    return min(g_max, g * (1.0 + eta_gain))
 41
 42
 43def threshold_sweep(weights):
 44    predicted = 1.0 / float(np.sum(weights))
 45    gs = predicted * np.linspace(0.70, 1.30, 25)
 46    rs = np.array([rho(float(g), weights) for g in gs])
 47    # Linear interpolation of the closest sign change in rho-1.
 48    j = int(np.argmin(np.abs(rs - 1.0)))
 49    if j == 0 or j == len(gs) - 1:
 50        observed = float(gs[j])
 51    else:
 52        observed = float(gs[j - 1] + (1-rs[j-1]) * (gs[j]-gs[j-1])/(rs[j]-rs[j-1]))
 53    return predicted, observed, gs, rs
 54
 55
 56def gradient_slope(weights, g, n=160):
 57    M = companion(g, weights)
 58    # A generic perturbation avoids selecting a special eigenvector.
 59    v = np.linspace(1.0, 0.3, len(weights))
 60    vals = []
 61    for _ in range(n):
 62        vals.append(np.linalg.norm(v))
 63        v = M @ v
 64    vals = np.maximum(np.asarray(vals), 1e-300)
 65    # Ignore the transient and fit the asymptotic log slope.
 66    slope = float(np.polyfit(np.arange(40, n), np.log(vals[40:]), 1)[0])
 67    return slope
 68
 69
 70def run_controller(weights, g0, delta=0.05, steps=20):
 71    g = float(g0)
 72    rows = []
 73    for t in range(steps):
 74        r = rho(g, weights)
 75        rows.append((t, g, r))
 76        g = controller(g, r, delta=delta)
 77    return rows
 78
 79
 80def main():
 81    q, alpha, K = 0.7, 0.5, 30
 82    w = q_weights(q, alpha, K)
 83    predicted, observed, gs, rs = threshold_sweep(w)
 84
 85    # Prediction 1: positive-memory boundary is g*S=1 (z=1).
 86    boundary_rel_error = abs(observed - predicted) / predicted
 87    below = rho(0.90 * predicted, w)
 88    above = rho(1.10 * predicted, w)
 89
 90    # Prediction 2: near the boundary, rho crosses one monotonically.
 91    monotone = bool(np.all(np.diff(rs) > -1e-9))
 92    crossing = bool(below < 1.0 and above > 1.0)
 93
 94    # Prediction 3: gradient norm has asymptotic slope log(rho).
 95    g_stable = 0.80 * predicted
 96    g_unstable = 1.20 * predicted
 97    r_stable, r_unstable = rho(g_stable, w), rho(g_unstable, w)
 98    slope_stable = gradient_slope(w, g_stable)
 99    slope_unstable = gradient_slope(w, g_unstable)
100    slope_err = abs(slope_stable - math.log(r_stable))
101
102    # Controller starts 35% above the predicted boundary and should move to margin.
103    rows = run_controller(w, 1.35 * predicted, delta=0.05, steps=50)
104    final_g, final_r = rows[-1][1], rows[-1][2]
105    controller_target = 0.95
106    controller_ok = final_r <= controller_target + 0.005
107
108    # A tiny nonlinear rollout provides an interpretable baseline comparison.
109    # Both systems use the same initial state; controller rescales g when rho is high.
110    x0 = np.zeros(K); x0[0] = 1.0
111    def rollout(controlled):
112        state, g, maxabs = x0.copy(), 1.35 * predicted, 1.0
113        losses = []
114        for t in range(80):
115            r = rho(g, w)
116            if controlled:
117                g = controller(g, r, delta=0.05, eta_gain=0.0)
118            state = companion(g, w) @ state
119            maxabs = max(maxabs, float(np.max(np.abs(state))))
120            losses.append(float(np.sum(state * state)))
121        return maxabs, losses[-1]
122    base_max, base_loss = rollout(False)
123    ctrl_max, ctrl_loss = rollout(True)
124
125    report = {
126        "setup": {"q": q, "alpha": alpha, "K": K, "weight_sum": float(np.sum(w))},
127        "prediction_1_boundary": {
128            "predicted_gcrit_1_over_sum_w": predicted,
129            "observed_interpolated_gcrit": observed,
130            "relative_error": boundary_rel_error,
131            "pass_within_20pct": boundary_rel_error <= 0.20,
132        },
133        "prediction_2_transition": {
134            "rho_at_0.90_gcrit": below,
135            "rho_at_1.10_gcrit": above,
136            "crosses_unit_circle": crossing,
137            "rho_sweep_monotone": monotone,
138        },
139        "prediction_3_gradient_scaling": {
140            "stable_g": g_stable, "stable_rho": r_stable,
141            "measured_log_slope": slope_stable,
142            "predicted_log_slope": math.log(r_stable),
143            "absolute_slope_error": slope_err,
144            "unstable_g": g_unstable, "unstable_rho": r_unstable,
145            "unstable_measured_log_slope": slope_unstable,
146        },
147        "controller": {
148            "initial_g": rows[0][1], "initial_rho": rows[0][2],
149            "final_g": final_g, "final_rho": final_r,
150            "target_rho": controller_target, "pass": controller_ok,
151            "trajectory": [{"step": t, "g": g, "rho": r} for t, g, r in rows],
152        },
153        "mini_comparison": {
154            "uncontrolled_max_state": base_max, "uncontrolled_final_squared_norm": base_loss,
155            "controlled_max_state": ctrl_max, "controlled_final_squared_norm": ctrl_loss,
156        },
157        "mechanism_manifested": bool(boundary_rel_error <= .20 and crossing and monotone and slope_err <= .03 and controller_ok),
158    }
159    Path("results.json").write_text(json.dumps(report, indent=2))
160    print(json.dumps(report, indent=2))
161
162
163if __name__ == "__main__":
164    main()