nika5 / lib.py
zyan1deOG's picture
nika5: a world model of the Sun
5d5ec7b verified
Raw History Blame Contribute Delete
2.73 kB
"""Shared constants and utilities. Everything else imports from here."""
import math
import torch
# ── channels & physics ───────────────────────────────────────────────
AIA_CHANNELS = ["94A", "131A", "171A", "193A", "211A", "304A", "335A", "1600A", "1700A"]
HMI_CHANNELS = ["Bx", "By", "Bz"]
# log10 peak response temperature per AIA channel (AIA instrument papers)
LOG_T = {"94A": 6.8, "131A": 5.6, "171A": 5.8, "193A": 6.2, "211A": 6.3,
"304A": 4.7, "335A": 6.4, "1600A": 5.0, "1700A": 3.7}
LOGT = torch.tensor([LOG_T[c] for c in AIA_CHANNELS])
LOGT_LO, LOGT_HI = 3.5, 7.0
# robust per-channel scales (99.5th pct, 2010 sample); I -> asinh(I/s)
# p99.5/scale ~= 0.5 for every channel β€” unequal scales silently reweight
# the loss toward whichever channel is normalized hottest
AIA_SCALE = torch.tensor([12.0, 60.0, 1400.0, 1800.0, 900.0, 350.0, 90.0, 280.0, 3440.0])
B_SCALE = 500.0
# ── data splits (AIA small store covers August 2010 only) ────────────
TRAIN_DAYS = ["2010-08-07", "2010-08-10", "2010-08-14"]
VAL_DAYS = ["2010-08-01", "2010-08-21"] # eruptive, quieter
HOURS = range(0, 24, 2)
def timestamps(days):
return [f"{d}T{h:02d}:00:00" for d in days for h in HOURS]
# ── tensor utilities ─────────────────────────────────────────────────
def norm_logt(t):
return (t - LOGT_LO) / (LOGT_HI - LOGT_LO) * 2 - 1
def fourier(x, freqs=(1, 2, 4, 8)):
return torch.cat([f(2 * math.pi * k * x) for k in freqs for f in (torch.sin, torch.cos)], -1)
def mlp(d_in, d_hid, d_out, depth=2):
import torch.nn as nn
layers = [nn.Linear(d_in, d_hid), nn.SiLU()]
for _ in range(depth - 1):
layers += [nn.Linear(d_hid, d_hid), nn.SiLU()]
return nn.Sequential(*layers, nn.Linear(d_hid, d_out))
def scatter_mean(src, index, n):
out = torch.zeros(*src.shape[:-2], n, src.shape[-1], device=src.device, dtype=src.dtype)
out.index_add_(-2, index, src)
deg = torch.zeros(n, 1, device=src.device, dtype=src.dtype)
deg.index_add_(0, index, torch.ones(index.shape[0], 1, device=src.device, dtype=src.dtype))
return out / deg.clamp(min=1)
def prep(frame, res):
"""Raw frame -> normalized pooled tensors (the space all scoring lives in)."""
pool = lambda x: torch.as_tensor(x).view(res, 512 // res, res, 512 // res).mean((1, 3))
out = {c: torch.asinh(pool(frame[c]) / s) for c, s in zip(AIA_CHANNELS, AIA_SCALE.tolist())}
out |= {c: torch.tanh(pool(frame[c]) / B_SCALE) for c in ("Bx", "By", "Bz")}
return out