First-Hit Interacting Optimizer / first_hit_toy.py
Failed on benchmark
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()