First-Hit Interacting Optimizer / first_hit_toy.py

Failed on benchmark

Raw ⬇ ZIP
  1#!/usr/bin/env python3
  2"""First-Hit Interacting Optimizer: exact 1-D solvable toy verification.
  3
  4Particles start distance `a` from an absorbing target.  A particle with
  5constant targetward drift v and diffusion D has the exact inverse-Gaussian
  6first-passage distribution.  The pair-kick model is represented by its exact
  7marginal effective diffusion D+kappa*(N-1), which is the mechanism in the
  8paper's solvable Example B.
  9"""
 10import json
 11import math
 12from pathlib import Path
 13import numpy as np
 14from scipy.stats import invgauss
 15
 16SEED = 1729
 17A = 1.0
 18D = 1.0
 19ALPHA = 1.0
 20KAPPA = 0.20
 21NS = np.array([4, 8, 16, 32, 64, 128, 256])
 22TRIALS = 30000
 23
 24
 25def hit_samples(n, trials, drift=0.0, diffusivity=D, rng=None):
 26    """Return trials of min first-passage time for n independent labels."""
 27    rng = np.random.default_rng() if rng is None else rng
 28    # X_t = -drift*t + sqrt(2*diffusivity) W_t, distance is A.
 29    # For drift > 0, T ~ IG(mean=A/drift, shape=A^2/(2D)).
 30    if drift > 0:
 31        shape = A * A / (2.0 * diffusivity)
 32        mean = A / drift
 33        # scipy's invgauss(mu, scale) has mean mu*scale and shape=scale.
 34        mu = mean / shape
 35        t = invgauss.rvs(mu, scale=shape, size=(trials, n), random_state=rng)
 36    else:
 37        # Levy law at zero drift: T = A^2 / (2D Z^2).
 38        z = rng.standard_normal((trials, n))
 39        t = A * A / (2.0 * diffusivity * z * z)
 40    return np.min(t, axis=1)
 41
 42
 43def fit_loglog(ns, means):
 44    return float(np.polyfit(np.log(ns), np.log(means), 1)[0])
 45
 46
 47def fit_loginv(ns, means):
 48    # log mean = c - beta log(log N); beta=1 is the logarithmic law.
 49    return float(np.polyfit(np.log(np.log(ns)), np.log(means), 1)[0])
 50
 51
 52def summarize(name, means):
 53    return {
 54        "name": name,
 55        "means": [float(x) for x in means],
 56        "loglog_slope": fit_loglog(NS, means),
 57        "loglog_slope_highN": fit_loglog(NS[-4:], means[-4:]),
 58        "loglogN_slope": fit_loginv(NS, means),
 59    }
 60
 61
 62def main():
 63    rng = np.random.default_rng(SEED)
 64    regimes = {}
 65    for name, drift_fn, diff_fn in [
 66        ("independent", lambda n: 0.0, lambda n: D),
 67        ("normalized_bounded", lambda n: ALPHA, lambda n: D),
 68        ("unnormalized_coherent", lambda n: ALPHA * (n - 1), lambda n: D),
 69        ("pair_kicks", lambda n: 0.0, lambda n: D + KAPPA * (n - 1)),
 70    ]:
 71        means = []
 72        for n in NS:
 73            means.append(np.mean(hit_samples(int(n), TRIALS, drift_fn(int(n)),
 74                                              diff_fn(int(n)), rng)))
 75        regimes[name] = summarize(name, np.array(means))
 76
 77    # Quantitative mechanism checks, with predictions from the paper.
 78    # 1) normalized/independent remain in the log-extreme class (slope -1
 79    # versus log log N), while coherent drift tends to -1 versus log N.
 80    # 2) pair kicks have effective D_N=D+kappa(N-1), so N*log(N)*E[T]
 81    # tends to A^2/(4*kappa), here 1.25.
 82    # 3) direct parameter sweep verifies linear coherent force and diffusion.
 83    coherent_alpha = [0.8, 1.6, 3.2, 6.4]
 84    ncheck = 128
 85    coh_alpha_means = []
 86    for alpha in coherent_alpha:
 87        coh_alpha_means.append(float(np.mean(hit_samples(ncheck, TRIALS,
 88                                                           alpha*(ncheck-1), D, rng))))
 89    # In the zero-noise coherent limit the prediction is exact, providing a
 90    # clean parameter-scaling check independent of Monte Carlo crossover.
 91    deterministic_alpha_times_mean = [float(alpha * (A / (alpha * (ncheck - 1))))
 92                                      for alpha in coherent_alpha]
 93    kick_kappas = [0.05, 0.10, 0.20, 0.40]
 94    kick_kappa_means = []
 95    for kappa in kick_kappas:
 96        kick_kappa_means.append(float(np.mean(hit_samples(ncheck, TRIALS, 0.0,
 97                                                           D+kappa*(ncheck-1), rng))))
 98    checks = {
 99        "log_extreme_prediction": {
100            "predicted": "slope of log(E[T]) vs log(log N) approximately -1",
101            "independent_observed": regimes["independent"]["loglogN_slope"],
102            "normalized_observed": regimes["normalized_bounded"]["loglogN_slope"],
103        },
104        "coherent_prediction": {
105            "predicted": "E[T] approximately A/(alpha*(N-1)); high-N log-log slope -1",
106            "observed_highN_slope": regimes["unnormalized_coherent"]["loglog_slope_highN"],
107            "alpha_values": coherent_alpha,
108            "alpha_times_mean": [a*m for a,m in zip(coherent_alpha, coh_alpha_means)],
109            "target_alpha_times_mean": A/(ncheck-1),
110            "zero_noise_alpha_times_mean": deterministic_alpha_times_mean,
111        },
112        "pair_kick_prediction": {
113            "predicted": "N*log(N)*E[T] tends to A^2/(4*kappa)=%.4f" % (A*A/(4*KAPPA)),
114            "observed_highN_NlogN_mean": [float(NS[i]*math.log(NS[i])*regimes["pair_kicks"]["means"][i]) for i in range(len(NS)-3, len(NS))],
115            "observed_highN_loglog_slope": regimes["pair_kicks"]["loglog_slope_highN"],
116            "kappa_values": kick_kappas,
117            "kappa_times_NlogN_mean": [float(k*ncheck*math.log(ncheck)*m) for k,m in zip(kick_kappas, kick_kappa_means)],
118            "target_kappa_times_NlogN_mean": A*A/4,
119        },
120    }
121    out = {"seed": SEED, "A": A, "D": D, "alpha": ALPHA,
122           "kappa": KAPPA, "trials": TRIALS,
123           "note": "pair_kicks uses the paper's exact effective marginal diffusivity; cross-label covariance is not time-discretely simulated",
124           "regimes": regimes, "checks": checks}
125    Path("results.json").write_text(json.dumps(out, indent=2))
126    print(json.dumps(out, indent=2))
127
128if __name__ == "__main__":
129    main()