artaquest commited on
Commit
5daf7bc
·
verified ·
1 Parent(s): 28f5468

Publish the artatopics results + the ensemble stack page

Browse files
Files changed (3) hide show
  1. data/phasor.json +0 -0
  2. index.html +9 -0
  3. phasor.html +128 -0
data/phasor.json ADDED
The diff for this file is too large to render. See raw diff
 
index.html CHANGED
@@ -420,6 +420,15 @@ damped trend scores −2.04; every single-family GPU model loses to the baseline
420
  the era-shift finding behind it, and a lab that rebuilds the winner in your browser (Pyodide):
421
  <a href="ensemble.html"><b>the ensemble stack →</b></a></p>
422
 
 
 
 
 
 
 
 
 
 
423
  <footer>No causal claims. Code MIT · data CC0 (OpenAlex) ·
424
  <a href="https://github.com/ArtaQuest/artatopics">source</a></footer>
425
  </main></body></html>
 
420
  the era-shift finding behind it, and a lab that rebuilds the winner in your browser (Pyodide):
421
  <a href="ensemble.html"><b>the ensemble stack →</b></a></p>
422
 
423
+ <h2>6 · The exact phasor, solved analytically, on the daily record</h2>
424
+ <p>The model above, |b + Σaᵢe<sup>i(θᵢ−pᵢ)</sup>|², solved in closed form for b, aᵢ, pᵢ from the sky
425
+ alone (kerykeion sidereal, cross-checked against three engines) and tested day by day on every
426
+ arXiv submission since 1991: peak-detection AUC 0.49–0.50 across 127 categories, infeasible in 78%
427
+ unregularised, and under L2 the inner walls shrink the arrows to nothing in 118 of 127. On the yearly
428
+ share task the exact form scores −8.25 where the record's √-linear approximation scores −3.58 — the
429
+ approximation was carrying it. A lab runs the closed-form solve in your browser:
430
+ <a href="phasor.html"><b>the exact phasor →</b></a></p>
431
+
432
  <footer>No causal claims. Code MIT · data CC0 (OpenAlex) ·
433
  <a href="https://github.com/ArtaQuest/artatopics">source</a></footer>
434
  </main></body></html>
phasor.html ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
2
+ <title>artatopics — the exact phasor, solved</title>
3
+ <style>
4
+ :root { --gold:#8A6D0B; --blue:#1746DC; --bg:#fff; --card:#f6f7f9; --ink:#1a2330; --ink2:#4a5568; --ink3:#6b7686; --line:#e3e6ea; }
5
+ @media (prefers-color-scheme: dark) { :root { --gold:#E8B923; --blue:#6f8dff; --bg:#010C17; --card:#06121E; --ink:#eef2f7; --ink2:#b8c2cf; --ink3:#8b98a8; --line:#12283c; } }
6
+ * { box-sizing:border-box } body { margin:0; background:var(--bg); color:var(--ink); font:15px/1.55 -apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif }
7
+ main { max-width:760px; margin:0 auto; padding:36px 20px 70px } h1 { font-size:26px; margin:0 } h1 b { color:var(--gold) } h2 { font-size:16px; margin:34px 0 8px }
8
+ p { color:var(--ink2); margin:8px 0 } table { border-collapse:collapse; width:100%; font-size:14px } th,td { text-align:left; padding:6px 10px; border-bottom:1px solid var(--line) }
9
+ th { color:var(--ink3); font-size:11px; text-transform:uppercase; letter-spacing:.08em } td.n { font-variant-numeric:tabular-nums; text-align:right }
10
+ .mono { font-family:ui-monospace,Menlo,monospace; background:var(--card); border:1px solid var(--line); border-radius:10px; padding:10px 14px; overflow-x:auto; font-size:13.5px }
11
+ select,button { padding:8px 12px; border-radius:10px; border:1px solid var(--line); background:var(--card); color:var(--ink); font-size:14px } button { cursor:pointer }
12
+ #pyout { white-space:pre-wrap; font:12.5px/1.5 ui-monospace,Menlo,monospace; background:var(--card); border:1px solid var(--line); border-radius:10px; padding:10px 14px; min-height:40px }
13
+ .row { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin:8px 0 } .cap { font-size:12.5px; color:var(--ink3) } a { color:var(--blue) } footer { margin-top:44px; font-size:12.5px; color:var(--ink3) }
14
+ </style></head><body><main>
15
+ <h1>arta<b>topics</b> · the exact phasor, solved analytically</h1>
16
+ <p>The model this campaign was built around, solved in closed form — no optimiser, no seed — and
17
+ tested on the finest record we have: every arXiv submission since 1991, day by day, 127 categories.</p>
18
+
19
+ <h2>1 · The model and its exact solution</h2>
20
+ <p class="mono">y(t) = | b + Σᵢ aᵢ · e<sup> i(θᵢ(t) − pᵢ)</sup> |²</p>
21
+ <p>The only inputs are θᵢ(t): the sidereal (Lahiri) longitudes of Sun, Moon, Mercury, Venus, Mars,
22
+ Jupiter, Saturn and the true node from kerykeion (Swiss Ephemeris). The unknowns b, aᵢ, pᵢ are
23
+ solved analytically. Expanding the square exactly:</p>
24
+ <p class="mono">y = b² + Σaᵢ² &nbsp;+&nbsp; Σᵢ 2b·aᵢ·cos(θᵢ−pᵢ) &nbsp;+&nbsp; Σᵢ&lt;ₖ 2aᵢaₖ·cos((θᵢ−pᵢ)−(θₖ−pₖ))</p>
25
+ <p>Every term is linear in the fixed basis {1, cos θᵢ, sin θᵢ, cos(θᵢ−θₖ), sin(θᵢ−θₖ)}, so one
26
+ least-squares solve gives its coefficients c. Then, exactly: <b>pᵢ = atan2(βᵢ, αᵢ)</b>;
27
+ <b>Mᵢ = √(αᵢ²+βᵢ²) = 2b·aᵢ</b>; and from c₀ = b² + ΣMᵢ²/4b² the quadratic
28
+ 4b⁴ − 4c₀b² + ΣMᵢ² = 0 gives <b>b² = (c₀ + √(c₀² − ΣMᵢ²))/2</b> — the '+' root, the only one that
29
+ reproduces the fitted curve. Self-test on data generated by the exact model: b recovered to six
30
+ decimals, aᵢ and pᵢ to 10⁻¹³; with 5% noise, R² 0.9976.</p>
31
+ <p>The form <em>over-determines</em> itself: the aspect coefficients must equal 2aᵢaₖcos(pᵢ−pₖ) with the
32
+ aᵢ, pᵢ the transit terms already fixed. How far the freely fitted aspects sit from that is the
33
+ <b>aspect residual</b> — the part of the data the phasor cannot be. If disc = c₀² − ΣMᵢ² &lt; 0, no
34
+ real (b, aᵢ) exists at all: the fit is <b>infeasible</b>.</p>
35
+
36
+ <h2>2 · What it scored on the daily record</h2>
37
+ <p>Each of 127 arXiv categories fitted independently on the first 80% of its reliable days (the
38
+ series as a ratio to its own trailing-365-day level, so b carries the level and the arrows the
39
+ timing), scored on the last 20% as a <b>peak detector</b>: does the forecast rank the days before
40
+ a submission peak above the rest? One AUC per category, averaged.</p>
41
+ <table><tr><th>exact phasor, kerykeion sidereal</th><th>level AUC</th><th>rise AUC</th><th>shift-null</th><th>feasible</th><th>held-out R² (median)</th></tr><tr><td>all 8 bodies, unregularised</td><td class='n'>0.4999</td><td class='n'>0.5002</td><td class='n'>nan</td><td class='n'>22%</td><td class='n'>-1.327</td></tr><tr><td>Sun–Mars only (cycle-complete)</td><td class='n'>0.5001</td><td class='n'>0.4958</td><td class='n'>0.4973</td><td class='n'>83%</td><td class='n'>-0.411</td></tr><tr><td><b>all 8, L2, λ per category on an inner wall</b></td><td class='n'>0.4932</td><td class='n'>0.4972</td><td class='n'>0.5068</td><td class='n'>95%</td><td class='n'>-0.052</td></tr></table>
42
+ <p class="cap">Chance for these autocorrelated labels is the circular-shift null, ~0.50–0.52. Under L2 the
43
+ inner walls chose λ=0.0001: 4 · λ=0.1: 3 · λ=10.0: 2 · λ=100.0: 118 — in 118 of 127 categories the strongest shrinkage on offer, i.e. the model
44
+ prefers b alone. Aspect residual is 1.000 throughout: the aspect terms the data wants bear no relation
45
+ to the ones the transit terms determine.</p>
46
+ <p>On the yearly citation-share task — where the campaign's <em>record</em> model scores −3.58 against
47
+ carry-forward's −2.56 (per-field R² vs holdout mean) — the exact form with the same horizon anchor,
48
+ selection on inner walls, scores <b>−8.25</b> on the live board. The record model fits
49
+ √y ≈ b + Σaᵢcos(θᵢ−pᵢ), which drops the (Σaᵢ sin)² term of |z|²; keeping it makes the fit stiffer,
50
+ not better. That approximation was carrying the record model.</p>
51
+
52
+ <h2>3 · Solve it yourself</h2>
53
+ <p>Pick a category. The button loads Python (Pyodide + numpy, ~10 MB), runs the <em>same closed-form
54
+ solution</em> on the same kerykeion sky and the same daily series, and prints b, every aᵢ, every pᵢ,
55
+ the feasibility, the aspect residual and the held-out peak AUC. Nothing here is precomputed.</p>
56
+ <div class="row"><select id="cat"></select><button id="run">Solve</button><span id="st" class="cap"></span></div>
57
+ <div id="pyout">(not run yet)</div>
58
+ <footer>No causal claims. Ephemeris cross-checked against three engines (<a href="https://github.com/ArtaQuest/artatopics/blob/main/analysis/arxivtopics/competition/daily/EPHEMERIS_CHECK.md">the check</a>) ·
59
+ <a href="https://github.com/ArtaQuest/artatopics/blob/main/analysis/arxivtopics/competition/phasor_exact.py">solver source</a> ·
60
+ <a href="index.html">main results</a> · <a href="ensemble.html">the ensemble stack</a> · <a href="https://huggingface.co/spaces/artaquest/artatopics">also on Hugging Face</a></footer>
61
+ </main>
62
+ <script>
63
+ (async () => {
64
+ const D = await (await fetch("data/phasor.json")).json();
65
+ const sel = document.getElementById("cat"), out = document.getElementById("pyout"), st = document.getElementById("st");
66
+ Object.keys(D.series).forEach(c => { const o = document.createElement("option"); o.textContent = c; sel.appendChild(o); });
67
+ let py = null;
68
+ const CODE = `
69
+ import numpy as np, json, datetime as dt
70
+ D = json.loads(DATA); LON = np.array(D["lon"], float); e0 = dt.date.fromisoformat(D["e0"])
71
+ def basis(TH):
72
+ T,B = TH.shape; C=[np.ones(T)]
73
+ for i in range(B): C += [np.cos(TH[:,i]), np.sin(TH[:,i])]
74
+ for i in range(B):
75
+ for k in range(i+1,B): d=TH[:,i]-TH[:,k]; C += [np.cos(d), np.sin(d)]
76
+ return np.stack(C,1)
77
+ def solve(TH, y, ridge):
78
+ T,B = TH.shape; Phi = basis(TH); R = np.eye(Phi.shape[1]); R[0,0]=0
79
+ c = np.linalg.solve(Phi.T@Phi + ridge*R + 1e-12*np.eye(Phi.shape[1]), Phi.T@y)
80
+ c0=c[0]; al=c[1:1+2*B:2]; be=c[2:2+2*B:2]; p=np.arctan2(be,al); M=np.sqrt(al**2+be**2)
81
+ disc=c0**2-(M**2).sum(); feas = disc>=0 and c0>0
82
+ if not feas: M=M*min(1.0, np.sqrt(max(c0,1e-12)**2/max((M**2).sum(),1e-24))); disc=max(c0**2-(M**2).sum(),0)
83
+ b=np.sqrt(max((c0+np.sqrt(disc))/2,1e-18)); a=M/(2*b)
84
+ gf=c[1+2*B::2]; df_=c[2+2*B::2]; gi=[]; di=[]
85
+ for i in range(B):
86
+ for k in range(i+1,B): gi.append(2*a[i]*a[k]*np.cos(p[i]-p[k])); di.append(2*a[i]*a[k]*np.sin(p[i]-p[k]))
87
+ fr=np.concatenate([gf,df_]); im=np.concatenate([gi,di]); res=float(np.sqrt(((fr-im)**2).sum())/max(np.sqrt((fr**2).sum()),1e-12))
88
+ return b,a,p,feas,res
89
+ def predict(TH,b,a,p): return np.abs(b + (a[None,:]*np.exp(1j*(TH-p[None,:]))).sum(1))**2
90
+ def auc(y,s):
91
+ o=np.argsort(s); r=np.empty(len(s)); r[o]=np.arange(1,len(s)+1); n1=y.sum(); n0=len(y)-n1
92
+ return float((r[y==1].sum()-n1*(n1+1)/2)/(n1*n0)) if n1>0 and n0>0 else float("nan")
93
+ S = D["series"][CAT]; x=np.array(S["x"],float); start=dt.date.fromisoformat(S["start"]); off=(start-e0).days
94
+ TH=np.deg2rad(LON[off:off+len(x)]); n=len(x); cut=int(n*0.8)
95
+ lvl=np.array([x[max(0,i-365):i].mean() if i>60 else x[:60].mean() for i in range(n)]); xr=x/np.maximum(lvl,1e-9)
96
+ # peaks: 7-day centred mean, local max above trailing-90 median + max(20%, 1sd)
97
+ s7=np.convolve(x,np.ones(7)/7,mode="same"); med=np.array([np.median(s7[max(0,i-90):i]) if i>30 else np.nan for i in range(n)])
98
+ sd=np.array([np.std(s7[max(0,i-90):i]-np.nanmean(med[max(0,i-90):i])) if i>30 else np.nan for i in range(n)])
99
+ thr=np.where(np.isnan(med),np.inf,med+np.maximum(0.2*np.nan_to_num(med),np.nan_to_num(sd,nan=np.inf)))
100
+ peak=np.zeros(n,bool)
101
+ for i in range(3,n-3):
102
+ if s7[i]>=s7[i-3:i+4].max() and s7[i]>thr[i]: peak[i]=True
103
+ y=np.zeros(n,int)
104
+ for i in np.where(peak)[0]: y[max(0,i-7):i]=1
105
+ best=None
106
+ for lam in (1e-4,1e-3,1e-2,1e-1,1.0,10.0,100.0):
107
+ k=int(cut*0.75); b,a,p,f,r=solve(TH[:k],xr[:k],lam); yh=predict(TH,b,a,p)
108
+ r2=1-((xr[k:cut]-yh[k:cut])**2).sum()/((xr[k:cut]-xr[:k].mean())**2).sum()
109
+ if best is None or r2>best[0]: best=(r2,lam)
110
+ b,a,p,feas,res=solve(TH[:cut],xr[:cut],best[1]); yh=predict(TH,b,a,p); te=slice(cut,n)
111
+ r2=1-((xr[te]-yh[te])**2).sum()/((xr[te]-xr[:cut].mean())**2).sum()
112
+ print(f"{CAT}: {n} days from {start} · {int(peak.sum())} peaks · fit on first {cut} days, scored on the last {n-cut}")
113
+ print(f"lambda chosen on the inner split: {best[1]}")
114
+ print(f"b = {b:.4f} feasible: {feas} aspect residual: {res:.3f}")
115
+ for nm,ai,pi in zip(D["bodies"],a,p): print(f" {nm:<10} a = {ai:.4f} p = {np.rad2deg(pi)%360:7.2f} deg")
116
+ print(f"held-out R2 (ratio to level): {r2:+.4f} · peak-detection AUC (level): {auc(y[te],yh[te]):.4f} (rise): {auc(y[te],np.diff(yh,prepend=yh[0])[te]):.4f}")
117
+ `;
118
+ document.getElementById("run").onclick = async () => {
119
+ try {
120
+ st.textContent = "loading Pyodide…";
121
+ if (!py) { const m = await import("https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.mjs"); py = await m.loadPyodide(); await py.loadPackage("numpy"); }
122
+ st.textContent = "solving…"; py.globals.set("DATA", JSON.stringify(D)); py.globals.set("CAT", sel.value);
123
+ let buf = ""; py.setStdout({ batched: s => { buf += s + "\n"; out.textContent = buf; } });
124
+ await py.runPythonAsync(CODE); st.textContent = "done";
125
+ } catch (e) { st.textContent = ""; out.textContent = "failed: " + e; }
126
+ };
127
+ })();
128
+ </script></body></html>