coarse-grained-forecasting-benchmark's picture
Add anonymous reproduction code
41dd7e0 verified
Raw History Blame Contribute Delete
4.53 kB
"""Coarse spectral <-> field maps for the 196-dim state (numpy, no plotting).
The (kx, ky) <-> index map is the single source of truth for the 196-dim coarse spectral vector shared by the
five datasets.
Reconstruction (exact inverse of extract_group.get_coarse_vec):
vec = [Re(oh_norm[mask]), Im(oh_norm[mask])], oh_norm = omega_hat / N_sim^2,
mask = (|k| <= k_c) & Hermitian-half. Un-normalize (clouds are per-feature
standardized), place each complex coefficient and its conjugate partner on an
N_disp grid, field = N_disp^2 * ifft2(OH).real.
There is deliberately no default dataset path in this module: the normalization
statistics that the wrapper un/re-normalizes with must come from the dataset
the model is trained on (the registry injects ``data.packaged_dataset_path``).
"""
from __future__ import annotations
from pathlib import Path
import numpy as np
K_C = 8
N_SIM = 1024 # the resolution get_coarse_vec normalized by (omega_hat / N_sim^2)
def mode_int_wavenumbers(k_c: int = K_C, M: int = N_SIM):
"""Integer (kx, ky) for each of the 98 complex modes, in get_coarse_vec
order. axis0<->kx, axis1<->ky (matches extract_group.make_kgrids)."""
k1d = np.fft.fftfreq(M, d=1.0 / M).astype(int) # 0,1,..,-1
ky_grid, kx_grid = np.meshgrid(k1d, k1d)
k_mag = np.sqrt(kx_grid**2 + ky_grid**2)
hermitian = (kx_grid > 0) | ((kx_grid == 0) & (ky_grid > 0))
mask = (k_mag <= k_c) & hermitian
return kx_grid[mask], ky_grid[mask] # each (n_modes,)
class FieldReconstructor:
def __init__(self, packaged: str | Path,
n_disp: int = 128, k_c: int = K_C):
norm = np.load(Path(packaged) / "normalization.npz")
self.mean, self.std = norm["mean"], norm["std"]
self.n_disp = n_disp
self.kx, self.ky = mode_int_wavenumbers(k_c)
self.n_modes = len(self.kx)
# precompute grid placement indices (mod N for negative wavenumbers)
self.ix = self.kx % n_disp
self.iy = self.ky % n_disp
self.ix_c = (-self.kx) % n_disp
self.iy_c = (-self.ky) % n_disp
def _spectral_grid(self, vec_norm: np.ndarray) -> np.ndarray:
"""(196,) standardized coarse vector -> full Hermitian ω̂ grid (n_disp²)."""
vec = np.asarray(vec_norm, float) * self.std + self.mean # un-normalize
coeff = vec[:self.n_modes] + 1j * vec[self.n_modes:] # oh_norm[mask]
OH = np.zeros((self.n_disp, self.n_disp), dtype=complex)
OH[self.ix, self.iy] += coeff
OH[self.ix_c, self.iy_c] += np.conj(coeff)
return OH
def reconstruct_field(self, vec_norm: np.ndarray,
field: str = "vorticity") -> np.ndarray:
"""(196,) standardized coarse vector -> (N_disp, N_disp) real field.
Only the 'vorticity' branch (the observed scalar itself) exists.
"""
OH = self._spectral_grid(vec_norm)
n2 = self.n_disp ** 2
if field == "vorticity":
return n2 * np.fft.ifft2(OH).real
raise ValueError(f"unknown field: {field!r} (only 'vorticity' is supported here)")
def reconstruct_many(self, vecs_norm: np.ndarray,
field: str = "vorticity") -> np.ndarray:
"""(N, 196) -> (N, N_disp, N_disp)."""
return np.stack([self.reconstruct_field(v, field) for v in vecs_norm])
def extract_vec(self, field: np.ndarray) -> np.ndarray:
"""(N_disp, N_disp) real field -> (2*n_modes,) standardized coarse vector.
Exact left-inverse of reconstruct_field when the field is band-limited
to |k| <= k_c on this grid (no aliasing): OH = fft2(field)/N_disp^2 undoes
the N_disp^2 scaling in reconstruct_field, the placed coefficients are
read back at the mode indices, and the per-feature standardization is
re-applied. Used to verify the FFT wrapper round-trips and as the forward
leg of the unet2 spectral<->field map."""
OH = np.fft.fft2(np.asarray(field, float)) / (self.n_disp ** 2)
coeff = OH[self.ix, self.iy]
vec = np.concatenate([coeff.real, coeff.imag])
return (vec - self.mean) / self.std
def roundtrip_maxerr(self, vecs_norm: np.ndarray) -> float:
"""max |extract_vec(reconstruct_field(v)) - v| over the given vectors."""
vecs_norm = np.atleast_2d(np.asarray(vecs_norm, float))
errs = [np.abs(self.extract_vec(self.reconstruct_field(v)) - v).max()
for v in vecs_norm]
return float(max(errs))