"""Write a lazy 569-station protocol manifest; fields are generated per chunk.""" from pathlib import Path import numpy as np import yaml ROOT = Path(__file__).resolve().parents[1] def station_metadata(count: int): index = np.arange(count, dtype=np.float32) latitude = -43.5 + 33.0 * ((index * 0.61803398875) % 1) longitude = 113.0 + 40.0 * ((index * 0.41421356237) % 1) elevation = 20 + 1450 * ((index * 0.2718281828) % 1) return latitude, longitude, elevation.astype(np.float32) def make_split(path: Path, config: dict): data = config["data"] dates = np.asarray(data["valid_dates"], dtype="U10") lat, lon, elevation = station_metadata(data["station_count"]) history_dates = np.empty((len(dates), data["history_days"]), dtype="U10") for i, date in enumerate(dates.astype("datetime64[D]")): history_dates[i] = (date - np.arange(data["history_days"], 0, -1)).astype("U10") np.savez(path, format_version=np.asarray(data["format_version"]), valid_dates=dates, history_dates=history_dates, initialization_utc=np.asarray("1200"), lead_hours=np.arange(241), station_id=np.asarray([f"JIVE-{i:04d}" for i in range(data["station_count"])]), station_latitude=lat, station_longitude=lon, station_elevation_m=elevation, variables=np.asarray(data["variables"]), units=np.asarray(data["units"]), sources=np.asarray(data["sources"]), neighborhood_shape=np.asarray([3, 3]), target_grid_projection=np.asarray("Albers"), target_grid_nx=np.asarray(None), target_grid_ny=np.asarray(None), representation=np.asarray(data["target_representation"])) def generate_chunk(meta, date_index: int, station_selector, history: bool, seed: int, include_patch: bool = True): """Emulate lazy reads after authoritative station patches have been extracted.""" station = np.arange(len(meta["station_id"]))[station_selector] days = 30 if history else 1 lead = np.arange(241, dtype=np.float32)[None, :, None, None] day = np.arange(days, dtype=np.float32)[:, None, None, None] lat = meta["station_latitude"][station][None, None, None, :] lon = meta["station_longitude"][station][None, None, None, :] phase = date_index * 0.7 + day * 0.11 temp = 20 - 0.35 * (lat + 25) + 4 * np.sin(2 * np.pi * (lead + 12) / 24 + phase) dew = temp - 5 - 1.5 * np.cos(np.deg2rad(lon) + lead / 48) wind = 5 + 1.2 * np.abs(np.sin(np.deg2rad(lon) + lead / 18 + phase)) truth = np.concatenate((temp, dew, wind), axis=2).astype(np.float32) source = np.arange(3, dtype=np.float32)[None, None, None, :, None] forecast = truth[:, :, :, None] + (source - 0.6) * np.asarray([1.1, 0.8, 0.5], np.float32)[None, None, :, None, None] forecast += (lead[..., None] / 240) * np.asarray([0.8, -0.5, 0.6], np.float32)[None, None, :, None, None] rng = np.random.default_rng(seed + date_index * 1000 + int(station[0])) forecast += rng.normal(0, 0.08, forecast.shape).astype(np.float32) offsets = np.asarray([[-0.18, -0.10, -0.04], [-0.08, 0.0, 0.09], [0.03, 0.12, 0.20]], np.float32) elevation_delta = meta["station_elevation_m"][station] - np.mean(meta["station_elevation_m"]) if not include_patch: return forecast.astype(np.float32), truth, elevation_delta.astype(np.float32) patches = forecast[..., None, None] + offsets return patches.astype(np.float32), truth, elevation_delta.astype(np.float32) def main(): config = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) output = ROOT / config["data"]["root"] output.mkdir(parents=True, exist_ok=True) make_split(output / "protocol.npz", config) print("generated=data/protocol.npz dates=2 history_days=30 leads=241 stations=569 patch=3x3 nx=unknown ny=unknown") if __name__ == "__main__": main()