| """Generate structured annual forcing windows and climate responses.""" |
|
|
| import argparse |
| from pathlib import Path |
|
|
| import numpy as np |
| import yaml |
|
|
|
|
| ROOT = Path(__file__).resolve().parents[1] |
|
|
|
|
| def make_split(path: Path, count: int, config: dict, seed: int, test: bool) -> None: |
| rng = np.random.default_rng(seed) |
| data = config["data"] |
| time, height, width = int(data["time_steps"]), int(data["height"]), int(data["width"]) |
| lat = np.linspace(-90, 90, height, dtype=np.float32) |
| lon = np.linspace(0, 360, width, endpoint=False, dtype=np.float32) |
| lat2d, lon2d = np.meshgrid(lat, lon, indexing="ij") |
| windows, responses, years = [], [], [] |
| for sample in range(count): |
| end_year = 2100 if test else 2060 + 10 * sample |
| annual_years = np.arange(end_year - time + 1, end_year + 1, dtype=np.int32) |
| progress = (annual_years - 1850).astype(np.float32) |
| co2 = 0.90 * progress + 0.0018 * progress ** 2 |
| ch4 = 0.42 * progress + 12.0 * np.sin(progress / 38.0) |
| industrial = np.exp(-((lat2d - 35) / 22) ** 2) * (0.65 + 0.35 * np.cos(np.deg2rad(lon2d - 90))) |
| tropical = np.exp(-(lat2d / 20) ** 2) * (0.65 + 0.35 * np.sin(np.deg2rad(2 * lon2d))) |
| forcing = np.empty((time, 4, height, width), dtype=np.float32) |
| for index, year_progress in enumerate(progress): |
| forcing[index, 0] = co2[index] |
| forcing[index, 1] = ch4[index] |
| forcing[index, 2] = industrial * (32.0 * np.exp(-((year_progress - 125) / 75) ** 2)) |
| forcing[index, 3] = tropical * (5.0 + 2.0 * np.sin(year_progress / 27.0)) |
| forcing += rng.normal(0, 0.01, forcing.shape).astype(np.float32) |
| co2_level = forcing[-1, 0].mean() / 300.0 |
| methane = forcing[-3:, 1].mean() / 120.0 |
| aerosol = forcing[-3:, 2:].mean(axis=(0, 1)) / 25.0 |
| arctic = 1.0 + 1.3 * (np.abs(lat2d) / 90.0) ** 2 |
| land_pattern = np.cos(np.deg2rad(2 * lon2d)) * np.cos(np.deg2rad(lat2d)) |
| itcz = np.sin(np.deg2rad(lon2d)) * np.exp(-(lat2d / 17) ** 2) |
| tas = 1.35 * co2_level * arctic + 0.22 * methane - 0.30 * aerosol + 0.05 * land_pattern |
| dtr = 0.10 * co2_level + 0.23 * aerosol * land_pattern - 0.04 * methane |
| pr = 0.16 * co2_level * np.cos(np.deg2rad(lat2d)) + 0.20 * itcz - 0.07 * aerosol |
| pr90 = 1.35 * pr + 0.08 * co2_level * np.exp(-(lat2d / 28) ** 2) |
| response = np.stack([tas, dtr, pr, pr90]).astype(np.float32) |
| response += rng.normal(0, 0.003, response.shape).astype(np.float32) |
| windows.append(forcing) |
| responses.append(response) |
| years.append(annual_years) |
| evaluation = config["evaluation"] |
| np.savez_compressed( |
| path, inputs=np.stack(windows).astype(np.float32), targets=np.stack(responses).astype(np.float32), |
| years=np.stack(years), latitude=lat, longitude=lon, |
| channel_names=np.asarray(data["channels"]), target_names=np.asarray(data["targets"]), |
| format_version=np.asarray(data["format_version"]), storage_layout=np.asarray("NTCHW"), |
| temporal_resolution=np.asarray("annual"), data_source=np.asarray("structured_synthetic"), |
| scenario=np.asarray(evaluation["scenario"] if test else "synthetic_training_scenarios"), |
| target_aggregation=np.asarray("2080-2100 climatological mean" if test else "annual response"), |
| evaluation_start_year=np.asarray(evaluation["start_year"], dtype=np.int32), |
| evaluation_end_year=np.asarray(evaluation["end_year"], dtype=np.int32), |
| ) |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--force", action="store_true") |
| args = parser.parse_args() |
| config = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) |
| output = ROOT / config["data"]["root"] |
| output.mkdir(parents=True, exist_ok=True) |
| for offset, (name, count, test) in enumerate((("train.npz", config["data"]["train_samples"], False), |
| ("test.npz", config["data"]["test_samples"], True))): |
| path = output / name |
| if args.force or not path.exists(): |
| make_split(path, int(count), config, int(config["seed"]) + offset, test) |
| print(f"generated={path.relative_to(ROOT)} samples={count} layout=NTCHW annual=true") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|