"""Check of item 3: formation of the long-range part of the made field of oscillator cells (M -> infinity), A_x = B_x = q_x, source g_s q_0 switched on at lambda = 0. Made field per unit g_s on a torus of side L (exact for the harmonic contract): da(x, lam) = -(1/L^D) sum_k cos(k.x) (1 - cos(W_k lam)) / W_k^2, W_k^2 = m^2 + sum_i f(k_i), f(k) = a (1 - cos k) + b (1 - cos 2k), c0^2 = a/2 + 2b. It equals the infinite-lattice made field before the first image front arrives. Static mark: infinite lattice, da_st(x) = -G(x), G(x) = int_0^inf e^{-m^2 t} prod_i g_t(x_i) dt. Criterion (4.11): D = 3 value ratio, D = 1 discrete-gradient ratio, |Delta| <= EPS for all lam in [lam_f, lam_max], lam_max = 0.9 (L - rho - 1) / v_front (before the image front). """ import json, os, sys, threading from pathlib import Path import numpy as np from scipy import special from scipy.integrate import simpson, cumulative_trapezoid WALL_S = 1500 _t = threading.Timer(WALL_S, lambda: (sys.stderr.write("wall-clock limit\n"), os._exit(3))) _t.daemon = True _t.start() OUT = Path(__file__).parent / "output" OUT.mkdir(exist_ok=True) EPS, ETA = 0.1, 0.01 # formation accuracy, arrival threshold KS = {"nn": (2.0, 0.0), "control": (14.0, -3.0)} # (a, b); both have c0 = 1 def f1(k, a, b): return a * (1 - np.cos(k)) + b * (1 - np.cos(2 * k)) def speeds(a, b, m=0.0): k = np.linspace(1e-6, np.pi, 200001) fp = a * np.sin(k) + 2 * b * np.sin(2 * k) v_front = float(np.max(fp / (2 * np.sqrt(m**2 + f1(k, a, b))))) # max d W / d k_1 (k_perp = 0) g = np.linspace(0, np.pi, 161) F, Fp = f1(g, a, b), a * np.sin(g) + 2 * b * np.sin(2 * g) num = Fp[:, None, None]**2 + Fp[None, :, None]**2 + Fp[None, None, :]**2 den = 4 * (m**2 + F[:, None, None] + F[None, :, None] + F[None, None, :]) den[0, 0, 0] = np.inf return {"c0": float(np.sqrt(a / 2 + 2 * b)), "v_front_axis": v_front, "v_slope_max_grad": float(max(np.sqrt(np.max(num / den)), v_front))} def g_t(t, ns, a, b, alpha, nk=4000): kc = min(np.pi, np.sqrt(80.0 / (alpha * t))) k = np.linspace(0.0, kc, nk) w = np.full(nk, kc / (nk - 1)); w[0] *= 0.5; w[-1] *= 0.5 return (np.cos(np.outer(ns, k)) @ (np.exp(-t * f1(k, a, b)) * w)) / np.pi def static_axis(D, ns, a, b, m): """G(n e_1) on the infinite lattice for n in ns (Laplace representation).""" kg = np.linspace(1e-3, np.pi, 4000) alpha = float(np.min(f1(kg, a, b) / kg**2)) ts = np.logspace(-5, np.log10(60.0 / m**2), 4000) ns = np.asarray(ns) vals = np.empty((len(ts), len(ns))) for i, t in enumerate(ts): g = g_t(t, np.append(ns, 0), a, b, alpha) vals[i] = np.exp(-m**2 * t) * g[:-1] * g[-1]**(D - 1) * t return simpson(vals, x=np.log(ts), axis=0) def made_ring(L, rhos, a, b, m, lams): k = 2 * np.pi * np.arange(L) / L W = np.sqrt(m**2 + f1(k, a, b)) C = np.cos(np.outer(rhos, k)) out = np.empty((len(lams), len(rhos))) for i0 in range(0, len(lams), 200): lm = lams[i0:i0 + 200] S = 2 * np.sin(np.outer(lm, W) / 2)**2 / W**2 out[i0:i0 + 200] = -(S @ C.T) / L return out def made_torus3(L, rhos, a, b, m, lams): h = L // 2 k = 2 * np.pi * np.arange(h + 1) / L wt = np.full(h + 1, 2.0); wt[0] = 1.0; wt[h] = 1.0 F = f1(k, a, b) W2 = m**2 + F[:, None, None] + F[None, :, None] + F[None, None, :] W = np.sqrt(W2) W23 = wt[None, :, None] * wt[None, None, :] / W2 C = wt[None, :] * np.cos(np.outer(rhos, k)) out = np.empty((len(lams), len(rhos))) for i, lm in enumerate(lams): S = np.sum(2 * np.sin(W * (lm / 2))**2 * W23, axis=(1, 2)) out[i] = -(C @ S) / L**3 return out def formation(lams, Delta, lam_max): sel = lams <= lam_max lm, d = lams[sel], np.abs(Delta[sel]) bad = np.nonzero(d > EPS)[0] lf = float(lm[bad[-1] + 1]) if len(bad) and bad[-1] + 1 < len(lm) else (float(lm[0]) if not len(bad) else None) arr = np.nonzero(np.abs(Delta[sel] + 1) >= ETA)[0] return lf, (float(lm[arr[0]]) if len(arr) else None) def kg_constant(): """sup_{Y>=0} |1 - I_mu(Y)|, I_mu(Y) = int_0^Y J1(y)/sqrt(y^2+mu^2) dy.""" y = np.linspace(0, 400, 400001) res = {} for mu in [1e-3, 1e-2, 0.1, 0.5, 1.0]: I = cumulative_trapezoid(special.j1(y) / np.sqrt(y**2 + mu**2), y, initial=0) res[str(mu)] = {"sup_abs_1_minus_I": float(np.max(np.abs(1 - I))), "min_I": float(np.min(I)), "max_I": float(np.max(I))} return res def run(D, L, rhos, m, kname, dlam): a, b = KS[kname] sp = speeds(a, b) sp_m = speeds(a, b, m) rhos = np.asarray(rhos) lam_end = 0.9 * (L - rhos.max() - 1) / sp_m["v_front_axis"] if D == 3: lam_end = min(lam_end, 4.0 * rhos.max() / sp["c0"]) lams = np.arange(dlam, lam_end + dlam, dlam) if D == 1: ns = np.concatenate([rhos, rhos + 1]) made = made_ring(L, ns, a, b, m, lams) G = static_axis(1, ns, a, b, m) n = len(rhos) Delta = (made[:, n:] - made[:, :n]) / (-(G[n:] - G[:n])) - 1 Gref = None if b == 0.0: # closed form, nearest neighbour: G(n) = e^{-th n}/(a sinh th), cosh th = 1 + m^2/a th = np.arccosh(1 + m**2 / a) Gref = np.exp(-th * ns) / (a * np.sinh(th)) Gref = float(np.max(np.abs((Gref[n:] - Gref[:n]) / (G[n:] - G[:n]) - 1))) else: made = made_torus3(L, rhos, a, b, m, lams) G = static_axis(3, rhos, a, b, m) Delta = made / (-G) - 1 Gref = None if b == 0.0: # cross-check of the static with the Bessel form ts = np.logspace(-5, np.log10(min(60.0 / m**2, 2e8)), 6000) # ive is finite below 1e9 v = np.array([[np.exp(-m**2 * t) * special.ive(r, a * t) * special.ive(0, a * t)**2 * t for r in rhos] for t in ts]) Gb = simpson(v, x=np.log(ts), axis=0) tt = np.logspace(np.log10(ts[-1]), np.log10(60.0 / m**2), 2000) # tail, ive ~ (2 pi x)^{-1/2} Gb = Gb + simpson(np.exp(-m**2 * tt) * (2 * np.pi * a * tt)**-1.5 * tt, x=np.log(tt)) Gref = float(np.max(np.abs(Gb / G - 1))) rows = [] for j, r in enumerate(rhos): lmax = 0.9 * (L - r - 1) / sp_m["v_front_axis"] if D == 3: lmax = min(lmax, 4.0 * r / sp["c0"]) lf, la = formation(lams, Delta[:, j], lmax) w = lams * sp["c0"] / r late = (w >= 1.5) & (w <= 3.0) & (lams <= lmax) mid = (lams >= 1.2 * r / sp["v_front_axis"]) & (w <= 0.8) rows.append({"rho": int(r), "mu": float(m * r / sp["c0"]), "lam_max": float(lmax), "lam_arrival": la, "lam_form": lf, "lam_form_c0_over_rho": (lf * sp["c0"] / r) if lf else None, "lam_form_minus_rho_over_c0_div_rho13": ((lf - r / sp["c0"]) / r**(1 / 3)) if lf else None, "max_abs_Delta_w_1.5_to_3": float(np.max(np.abs(Delta[late, j]))) if late.any() else None, "mean_Delta_between_front_and_w0.8": float(np.mean(Delta[mid, j])) if mid.any() else None}) return {"D": D, "L": L, "K": kname, "a": a, "b": b, "m_h": m, "dlam": dlam, "speeds_m0": sp, "speeds_at_m": sp_m, "static_crosscheck_rel_err": Gref, "rows": rows} if __name__ == "__main__": res = {"EPS": EPS, "ETA": ETA, "kg_constant": kg_constant(), "runs": []} for kname in KS: res["runs"].append(run(1, 4096, [16, 32, 64, 128, 256, 512], 5e-5, kname, 0.05)) res["runs"].append(run(3, 256, [8, 16, 24, 48], 2.5e-4, kname, 0.05)) with open(OUT / "oscillator_formation.json", "w", encoding="utf-8") as fh: json.dump(res, fh, indent=1) for r in res["runs"]: print(r["D"], r["K"], r["speeds_m0"], "static check", r["static_crosscheck_rel_err"]) for row in r["rows"]: print(" ", row) print(res["kg_constant"])