| """Generate a small, structured PPNN dataset while preserving paper dimensions.""" |
|
|
| from pathlib import Path |
|
|
| import numpy as np |
| import yaml |
|
|
| ROOT = Path(__file__).resolve().parents[1] |
|
|
|
|
| def main(): |
| config = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) |
| data = config["data"] |
| if (data["station_count"], data["member_count"], data["variable_count"], data["lead_hours"]) != (537, 50, 18, 48): |
| raise ValueError("paper station/member/variable/lead dimensions cannot be reduced") |
| rng = np.random.default_rng(int(config["seed"])) |
| station_index_all = np.arange(537) |
| latitude = (47.0 + 8.0 * ((station_index_all * 0.61803398875) % 1)).astype(np.float32) |
| longitude = (5.5 + 9.5 * ((station_index_all * 0.41421356237) % 1)).astype(np.float32) |
| elevation = (5.0 + 1450.0 * ((station_index_all * 0.2718281828) % 1)).astype(np.float32) |
| station_id = np.asarray([f"DWD-{i:04d}" for i in station_index_all]) |
| active = np.linspace(0, 536, int(data["active_station_count"]), dtype=np.int64) |
| dates = np.asarray(data["dates"], dtype="datetime64[D]") |
| date_id, station_id_index = np.meshgrid(np.arange(len(dates)), active, indexing="ij") |
| date_id, station_id_index = date_id.ravel(), station_id_index.ravel() |
| n = len(date_id) |
| day_of_year = (dates[date_id] - dates[date_id].astype("datetime64[Y]")).astype(int) + 1 |
| seasonal = np.sin(2 * np.pi * (day_of_year - 172) / 365.25).astype(np.float32) |
| lat = latitude[station_id_index] |
| lon = longitude[station_id_index] |
| elev = elevation[station_id_index] |
| spatial = (-0.42 * (lat - 51.0) + 0.13 * (lon - 10.0) - 0.0062 * elev).astype(np.float32) |
| common_weather = rng.normal(0, 2.2, len(dates)).astype(np.float32)[date_id] |
| target = (11.0 + 9.0 * seasonal + spatial + common_weather + 0.18 * np.sin(station_id_index * 0.17)).astype(np.float32) |
| variable_offset = np.linspace(-4.0, 5.0, 18, dtype=np.float32) |
| variable_scale = np.linspace(0.72, 1.25, 18, dtype=np.float32) |
| centre = target[:, None] * variable_scale[None] + variable_offset[None] + 0.3 * common_weather[:, None] |
| spread = (0.45 + 0.025 * np.abs(common_weather) + 0.00045 * elev + 0.08 * (1.0 + seasonal)).astype(np.float32) |
| member_axis = np.linspace(-2.1, 2.1, 50, dtype=np.float32) |
| wave = np.sin((station_id_index[:, None, None] + 1) * 0.13 + np.arange(18)[None, None] * 0.31 + member_axis[None, :, None] * 1.7) |
| ensemble = centre[:, None, :] + spread[:, None, None] * variable_scale[None, None, :] * (member_axis[None, :, None] + 0.22 * wave) |
| ensemble[:, :, 0] += (0.35 + 0.14 * seasonal)[:, None] |
| auxiliary = np.stack((latitude[station_id_index], longitude[station_id_index], elevation[station_id_index], day_of_year / 366.0), axis=1).astype(np.float32) |
| output = ROOT / data["file"] |
| output.parent.mkdir(parents=True, exist_ok=True) |
| np.savez_compressed( |
| output, format_version=np.asarray(data["format_version"]), ensemble=ensemble.astype(np.float32), |
| auxiliary=auxiliary, target=target, station_index=station_id_index.astype(np.int64), date_index=date_id.astype(np.int64), |
| dates=dates.astype("U10"), lead_hours=np.asarray(data["lead_hours"], np.int64), variables=np.asarray(["t2m"] + [f"var_{i:02d}" for i in range(1, 18)]), |
| station_id=station_id, station_latitude=latitude, station_longitude=longitude, station_elevation_m=elevation, |
| ) |
| if ensemble.shape != (n, 50, 18) or len(station_id) != 537 or not np.isfinite(ensemble).all(): |
| raise RuntimeError("generated data failed shape or finite-value validation") |
| print(f"saved={output.relative_to(ROOT)} samples={n} ensemble={ensemble.shape} metadata_stations=537 lead=48h") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|