Spaces:
Running
Running
Publish the artatopics results + the ensemble stack page
Browse files- data/phasor.json +0 -0
- index.html +9 -0
- 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ᵢ² + Σᵢ 2b·aᵢ·cos(θᵢ−pᵢ) + Σᵢ<ₖ 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ᵢ² < 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>
|