Calorimetric Training Transition Detector / calorimetric_detector.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2import math
  3import numpy as np
  4
  5
  6def detailed_balance_check(seed=0):
  7    rng = np.random.default_rng(seed)
  8    beta, nu = 1.7, 0.8
  9    vals = []
 10    for _ in range(1000):
 11        work, du = rng.normal(size=2)
 12        kf = nu * np.exp(beta * (work - du) / 2)
 13        # Reverse work is -work and reverse energy change is -du.
 14        kr = nu * np.exp(beta * (-work + du) / 2)
 15        vals.append(math.log(kf / kr) - beta * (work - du))
 16    return float(np.max(np.abs(vals)))
 17
 18
 19def exact_response(eta, lam, T, delta, P):
 20    """Expected excess response for x'=a*x+sqrt(2 eta T) z, q=dx^2/eta."""
 21    a = 1.0 - eta * lam
 22    v0 = 2.0 * eta * T / (1.0 - a*a)
 23    v = v0
 24    q0 = 4.0 * T / (1.0 + a)
 25    excess = 0.0
 26    for _ in range(P):
 27        v = a*a * v + 2.0 * eta * (T + delta)
 28        q = (1.0 - a)**2 * v / eta + 2.0 * (T + delta)
 29        excess += q - q0
 30    return excess / delta
 31
 32
 33def monte_carlo_response(eta, lam, T, delta, P, ntraj=30000, seed=1):
 34    rng = np.random.default_rng(seed)
 35    a = 1.0 - eta * lam
 36    v0 = 2.0 * eta * T / (1.0 - a*a)
 37    x = rng.normal(0.0, math.sqrt(v0), size=ntraj)
 38    # Estimate baseline q from independent stationary transitions.
 39    xb = rng.normal(0.0, math.sqrt(v0), size=ntraj)
 40    qss = np.mean(((a * xb + math.sqrt(2*eta*T)*rng.normal(size=ntraj) - xb)**2) / eta)
 41    total = 0.0
 42    for _ in range(P):
 43        xn = a*x + math.sqrt(2*eta*(T+delta))*rng.normal(size=ntraj)
 44        total += np.mean((xn-x)**2 / eta) - qss
 45        x = xn
 46    return total / delta
 47
 48
 49def run():
 50    lam, T, P = 1.0, 1.0, 20
 51    # Prediction 1: explicit Euler SGD is stable iff 0 < eta*lambda < 2.
 52    etas = np.array([*np.linspace(0.2, 1.8, 9), 1.9, 1.95, 1.99, 2.01, 2.1])
 53    rows = []
 54    for eta in etas:
 55        a = 1 - eta*lam
 56        C = exact_response(eta, lam, T, 0.05*T, P)
 57        tau = -1.0 / math.log(abs(a)) if abs(a) not in (0, 1) else (0.0 if a == 0 else float('inf'))
 58        rows.append({'eta': float(eta), 'eta_lambda': float(eta*lam), 'stable': bool(abs(a)<1), 'C': float(C), 'tau': float(tau)})
 59    # Predicted pole C ~ const/(2-eta lambda): test scaling at the stable edge.
 60    edge = [r for r in rows if 1.8 <= r['eta_lambda'] < 2.0]
 61    scaled = [r['C'] * (2-r['eta_lambda']) for r in edge]
 62    edge_cv = float(np.std(scaled) / np.mean(scaled))
 63    response_monotone_stable = bool(np.all(np.diff([r['C'] for r in rows if r['stable']]) > 0))
 64    # Prediction 2: relaxation time increases toward the stability boundary.
 65    stable_near = [r for r in rows if r['stable'] and r['eta_lambda'] > 1.2]
 66    taus = np.array([r['tau'] for r in stable_near])
 67    monotone_tau = bool(np.all(np.diff(taus) > 0))
 68    # Prediction 3: differential response is linear in pulse size for small pulses.
 69    deltas = np.array([0.01, 0.02, 0.05, 0.1]) * T
 70    Cs = np.array([exact_response(1.5, lam, T, d, P) for d in deltas])
 71    # Compare to infinitesimal numerical derivative and coefficient of variation.
 72    C0 = exact_response(1.5, lam, T, 1e-5, P)
 73    linear_rel_error = float(np.max(np.abs(Cs/C0 - 1)))
 74    mc = monte_carlo_response(1.5, lam, T, 0.05, P, ntraj=100000, seed=4)
 75    exact = exact_response(1.5, lam, T, 0.05, P)
 76
 77    # Small optimizer comparison: noisy quadratic, fixed eta vs calorimetric trigger.
 78    def train(adaptive, seed):
 79        rng = np.random.default_rng(seed); x = 4.0; eta = 1.5; losses=[]; triggers=0
 80        for step in range(300):
 81            # Probe every 30 updates, using a short temperature pulse around current state.
 82            if adaptive and step > 0 and step % 30 == 0:
 83                c = exact_response(eta, lam, T, 0.05*T, 20)
 84                if c > 35.0:  # response threshold selected before trajectory, near edge
 85                    eta *= 0.7; triggers += 1
 86            grad = lam*x + rng.normal(0, math.sqrt(2*T/eta))
 87            x -= eta*grad
 88            losses.append(0.5*lam*x*x)
 89            if abs(x) > 1e6: return float('inf'), triggers
 90        return float(np.mean(losses[-30:])), triggers
 91    base = [train(False, s)[0] for s in range(20)]
 92    idea = [train(True, s)[0] for s in range(20)]
 93    triggers = [train(True, s)[1] for s in range(20)]
 94    out = {
 95      'detailed_balance_max_log_error': detailed_balance_check(),
 96      'prediction_stability_boundary': {'predicted_eta_lambda': 2.0, 'sweep_last_stable': max(r['eta_lambda'] for r in rows if r['stable']), 'sweep_first_unstable': min(r['eta_lambda'] for r in rows if not r['stable']), 'all_stable_below_2': all(r['stable'] for r in rows if r['eta_lambda'] < 2.0), 'first_unstable_is_above_2': all(not r['stable'] for r in rows if r['eta_lambda'] > 2.0)},
 97      'prediction_response_pole': {'predicted': 'C*(2-eta*lambda) approximately constant near edge', 'edge_cv': edge_cv, 'response_monotone_on_stable_sweep': response_monotone_stable, 'edge_rows': edge},
 98      'prediction_critical_slowing': {'predicted': 'tau increases as eta*lambda approaches 2 from below', 'monotone_on_1.2_to_edge': monotone_tau, 'taus': [float(x) for x in taus]},
 99      'prediction_pulse_linearity': {'deltas_over_T': [float(x) for x in deltas], 'C_values': [float(x) for x in Cs], 'max_relative_error_vs_infinitesimal': linear_rel_error},
100      'monte_carlo_check': {'exact_C': exact, 'mc_C': mc, 'relative_error': abs(mc-exact)/abs(exact)},
101      'mini_experiment': {'baseline_final_loss_mean': float(np.mean(base)), 'idea_final_loss_mean': float(np.mean(idea)), 'baseline_divergences': int(sum(np.isinf(base))), 'idea_divergences': int(sum(np.isinf(idea))), 'idea_trigger_count_mean': float(np.mean(triggers))},
102      'rows': rows
103    }
104    with open('results.json','w') as f: json.dump(out, f, indent=2)
105    print(json.dumps(out, indent=2))
106
107if __name__ == '__main__': run()