from pathlib import Path import sys import numpy as np ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from model.pbl_emulator import INPUT_NAMES, OUTPUT_NAMES, load_yaml def main() -> None: config = load_yaml(ROOT / "conf/config.yaml") count, seed = int(config["data"]["samples"]), int(config["data"]["seed"]) interval_hours = int(config["data"]["interval_hours"]) rng = np.random.default_rng(seed) hours = (np.arange(count) * interval_hours) % 24 days = (np.arange(count) * interval_hours / 24) % 365 daylight = np.maximum(0.0, np.sin(np.pi * (hours - 6) / 12)) season = np.sin(2 * np.pi * (days - 172) / 365) swdown = (760 + 110 * season) * daylight t2 = 284 + 10 * season + 5 * np.sin(2 * np.pi * (hours - 14) / 24) + rng.normal(0, 0.8, count) q2 = np.clip(0.0065 + 0.004 * (season + 1) / 2 + 0.0012 * daylight + rng.normal(0, 0.0003, count), 0.001, None) ug = 5.5 + 1.8 * np.sin(2 * np.pi * days / 9) + rng.normal(0, 0.5, count) vg = 2.0 + 1.5 * np.cos(2 * np.pi * days / 13) + rng.normal(0, 0.5, count) u10 = 0.62 * ug + rng.normal(0, 0.45, count); v10 = 0.62 * vg + rng.normal(0, 0.45, count) hfx = 12 + 150 * daylight * (0.75 + 0.25 * season) + rng.normal(0, 5, count) lh = 25 + 110 * daylight * (0.8 + 0.2 * season) + rng.normal(0, 5, count) pblh = np.clip(170 + 1200 * daylight * (0.75 + 0.25 * season) + rng.normal(0, 45, count), 80, 1800) ust = np.clip(0.12 + 0.0014 * hfx + 0.022 * np.hypot(u10, v10), 0.05, 1.2) tsk = t2 + 1.5 + 4.0 * daylight; tslb = 283 + 7.0 * season smois = np.clip(0.25 + 0.04 * np.sin(2 * np.pi * days / 40) - 0.025 * season + rng.normal(0, 0.008, count), 0.08, 0.45) grdflx = 0.12 * hfx - 8 * (1 - daylight); glw = 285 + 2.2 * (t2 - 273.15) + 450 * q2 x = np.column_stack((q2, t2, u10, v10, grdflx, swdown, glw, lh, hfx, pblh, ust, tsk, tslb, smois, ug, vg)).astype(np.float32) heights = np.array([10, 35, 70, 110, 160, 220, 290, 370, 460, 560, 680, 820, 980, 1160, 1370, 1600, 1880], dtype=np.float32) z = heights[None, :]; mix = np.exp(-z / pblh[:, None]) u = ug[:, None] + (u10 - ug)[:, None] * mix + 0.18 * np.sin(z / 260) v = vg[:, None] + (v10 - vg)[:, None] * mix + 0.14 * np.cos(z / 300) w = 0.20 * daylight[:, None] * np.sin(np.pi * np.minimum(z / pblh[:, None], 1)) - 0.015 * (1 - daylight[:, None]) + rng.normal(0, 0.008, (count, 17)) tk = t2[:, None] - 0.0062 * z + 0.9 * mix * daylight[:, None] qv = np.clip(q2[:, None] * np.exp(-z / 2100) * (0.88 + 0.12 * mix) + 1e-4 * smois[:, None], 1e-5, None) y = np.stack((u, v, w, tk, qv), axis=-1).astype(np.float32) y += rng.normal(size=y.shape).astype(np.float32) * np.array([0.035, 0.035, 0.003, 0.04, 0.000015], dtype=np.float32) n_train, n_val = int(0.70 * count), int(0.15 * count) slices = {"train": slice(0, n_train), "val": slice(n_train, n_train + n_val), "test": slice(n_train + n_val, count)} output = ROOT / config["paths"]["data"]; output.parent.mkdir(parents=True, exist_ok=True) payload = {"heights_m": heights, "input_names": np.asarray(INPUT_NAMES), "output_names": np.asarray(OUTPUT_NAMES)} for name, section in slices.items(): payload.update({f"x_{name}": x[section], f"y_{name}": y[section], f"time_{name}": (np.arange(count)[section] * interval_hours).astype(np.int64), f"pblh_{name}": pblh[section].astype(np.float32)}) np.savez_compressed(output, **payload) print(f"saved {output}: x={x.shape}, y={y.shape}, split={[len(range(*s.indices(count))) for s in slices.values()]}") if __name__ == "__main__": main()