Poisson-Calibrated Candidate-Pool Scheduler / poisson_scheduler_experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2import math
  3from pathlib import Path
  4import numpy as np
  5
  6SEED = 2136
  7rng = np.random.default_rng(SEED)
  8
  9
 10def sample_deficits(n_trials, n_candidates, dim, kappa=2.0, curvature=1.0, batch=2000):
 11    """Q(u*)-max Q(U_i) for U uniform in [-1,1]^d and Q=-curvature*||u||^k."""
 12    out = np.empty(n_trials)
 13    for lo in range(0, n_trials, batch):
 14        m = min(batch, n_trials - lo)
 15        u = rng.uniform(-1.0, 1.0, size=(m, n_candidates, dim))
 16        r = np.linalg.norm(u, axis=-1)
 17        out[lo:lo+m] = curvature * np.min(r ** kappa, axis=1)
 18    return out
 19
 20
 21def fit_log_slope(ns, means):
 22    return float(np.polyfit(np.log(ns), np.log(means), 1)[0])
 23
 24
 25def main():
 26    # Prediction 1: E[Delta_N] scales as N^(-kappa/d).
 27    # Prediction 2: doubling N multiplies error by 2^(-kappa/d).
 28    # We check both d=1 and d=2, with kappa=2.
 29    n_trials = 12000
 30    kappa = 2.0
 31    scaling = {}
 32    for dim in (1, 2):
 33        ns = np.array([4, 8, 16, 32, 64, 128, 256])
 34        means = np.array([sample_deficits(n_trials, int(n), dim, kappa).mean() for n in ns])
 35        slope = fit_log_slope(ns, means)
 36        ratios = means[1:] / means[:-1]
 37        predicted_slope = -kappa / dim
 38        predicted_ratio = 2 ** predicted_slope
 39        scaling[str(dim)] = {
 40            "N": ns.tolist(), "mean_deficit": means.tolist(),
 41            "observed_loglog_slope": slope, "predicted_loglog_slope": predicted_slope,
 42            "observed_doubling_ratio_mean": float(ratios.mean()),
 43            "predicted_doubling_ratio": predicted_ratio,
 44            "doubling_ratios": ratios.tolist(),
 45        }
 46
 47    # Prediction 3: scheduler N = ceil((C / ((1-beta) eps))^(d/kappa))
 48    # has N proportional to eps^(-d/kappa), and achieves the requested error scale.
 49    dim = 2
 50    beta = 0.9
 51    epsilons = np.array([0.04, 0.02, 0.01, 0.005])
 52    n_cal = 64
 53    cal = sample_deficits(30000, n_cal, dim, kappa)
 54    C_hat = float(cal.mean() * n_cal ** (kappa / dim))
 55    scheduled_ns = np.ceil((C_hat / ((1-beta) * epsilons)) ** (dim/kappa)).astype(int)
 56    scheduled_ns = np.maximum(scheduled_ns, 1)
 57    measured = np.array([sample_deficits(16000, int(n), dim, kappa).mean() for n in scheduled_ns])
 58    value_bound_scale = measured / (1-beta)
 59    eps_slope = fit_log_slope(epsilons, scheduled_ns)
 60    # For this geometry the asymptotic constant is known: area(pi r^2)/(area(box)) = pi r^2/4,
 61    # hence E[min ||U||^2] ~ 4/(pi N). This is also a check that C_hat is calibrated.
 62    C_exact = 4.0 / math.pi
 63    expected_at_schedule = C_exact * scheduled_ns ** (-dim / kappa) / (1-beta)
 64    scheduler = {
 65        "beta": beta, "C_hat": C_hat, "C_exact_asymptotic": C_exact,
 66        "C_hat_over_C_exact": C_hat / C_exact, "epsilons": epsilons.tolist(),
 67        "scheduled_N": scheduled_ns.tolist(), "measured_mean_delta": measured.tolist(),
 68        "measured_delta_over_1_minus_beta": value_bound_scale.tolist(),
 69        "expected_value_proxy_at_schedule": expected_at_schedule.tolist(),
 70        "mean_expected_proxy_over_epsilon": float(np.mean(expected_at_schedule / epsilons)),
 71        "observed_loglog_N_vs_epsilon_slope": eps_slope,
 72        "predicted_loglog_N_vs_epsilon_slope": -dim/kappa,
 73        "fraction_meeting_epsilon_bound": float(np.mean(value_bound_scale <= epsilons)),
 74    }
 75
 76    # Mini comparison: fixed pools versus the adaptive pool over heterogeneous states.
 77    # Curvature changes the local C, so fixed N is deliberately wasteful on easy states.
 78    n_states = 3000
 79    curvatures = rng.uniform(0.35, 2.0, size=n_states)
 80    eps = 0.015
 81    fixed_ns = [8, 32, 118, 128]
 82    rows = []
 83    # Use common state curvature and independent candidate draws; report action error
 84    # and Bellman-value error proxy Delta/(1-beta), with mean Q evaluations.
 85    for n in fixed_ns:
 86        ds = np.array([sample_deficits(1, n, dim, kappa, float(a))[0] for a in curvatures])
 87        rows.append({"method": f"fixed_N={n}", "mean_evaluations": n,
 88                     "mean_delta": float(ds.mean()), "mean_value_error_proxy": float((ds/(1-beta)).mean()),
 89                     "fraction_value_proxy_le_eps": float(np.mean(ds/(1-beta) <= eps))})
 90    # Estimate C per state from a cheap N=8 pilot, then use the formula and cap at 128.
 91    pilot_n = 8
 92    pilot = np.array([sample_deficits(1, pilot_n, dim, kappa, float(a))[0] for a in curvatures])
 93    local_C = pilot * pilot_n ** (kappa/dim)
 94    adaptive_n = np.ceil((local_C / ((1-beta)*eps)) ** (dim/kappa)).astype(int)
 95    adaptive_n = np.clip(adaptive_n, pilot_n, 128)
 96    adaptive_ds = np.array([sample_deficits(1, int(n), dim, kappa, float(a))[0]
 97                            for a, n in zip(curvatures, adaptive_n)])
 98    rows.append({"method": "adaptive_scheduler", "mean_evaluations": float(adaptive_n.mean()),
 99                 "mean_delta": float(adaptive_ds.mean()), "mean_value_error_proxy": float((adaptive_ds/(1-beta)).mean()),
100                 "fraction_value_proxy_le_eps": float(np.mean(adaptive_ds/(1-beta) <= eps)),
101                 "pool_min": int(adaptive_n.min()), "pool_median": float(np.median(adaptive_n)),
102                 "pool_max": int(adaptive_n.max())})
103
104    result = {"seed": SEED, "theory": {"action": "uniform box, Q(u)=-||u||^2", "kappa": kappa},
105              "scaling_checks": scaling, "scheduler_scaling_check": scheduler,
106              "mini_comparison": rows}
107    Path("results.json").write_text(json.dumps(result, indent=2))
108    print(json.dumps(result, indent=2))
109
110
111if __name__ == "__main__":
112    main()