File size: 3,375 Bytes
a00d152 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | """Generate structured station-day PCA predictors with realistic surge drivers."""
import argparse
from pathlib import Path
import numpy as np
import yaml
ROOT = Path(__file__).resolve().parents[1]
def correlated_features(rng, latent, count, phase):
weights = rng.normal(0, 0.35, (latent.shape[1], count))
weights[0, :12] += np.linspace(1.5, 0.4, 12)
weights[1, 12:24] += np.linspace(1.2, 0.3, 12)
features = latent @ weights + rng.normal(0, 0.12, (len(latent), count))
features += 0.08 * np.sin(np.arange(count)[None, :] * 0.13 + phase)
return features.astype(np.float32)
def make_split(path, count, config, seed, day_offset):
rng = np.random.default_rng(seed)
day = np.arange(count, dtype=np.float32) + day_offset
latitude = rng.uniform(-62, 62, count).astype(np.float32)
longitude = rng.uniform(-180, 180, count).astype(np.float32)
annual = np.sin(2 * np.pi * day / 365.25)
synoptic = np.zeros(count, np.float32)
for index in range(1, count):
synoptic[index] = 0.72 * synoptic[index - 1] + rng.normal(0, 0.7)
cyclone = np.maximum(synoptic - 0.55, 0) ** 2
tropical = (np.abs(latitude) < 30).astype(np.float32)
latent = np.column_stack([synoptic, cyclone, annual, np.cos(np.deg2rad(latitude)), tropical]).astype(np.float32)
data = config["data"]
rs_daily = correlated_features(rng, latent, int(data["rs_daily_features"]), 0.1)
rs_lagged = correlated_features(rng, latent, int(data["rs_lagged_features"]), 0.4)
ar_daily = correlated_features(rng, latent, int(data["ar_daily_features"]), 0.7)
ar_lagged = correlated_features(rng, latent, int(data["ar_lagged_features"]), 1.0)
surge = (0.035 * synoptic + 0.055 * cyclone + 0.012 * annual +
0.015 * rs_lagged[:, 0] - 0.010 * rs_lagged[:, 13])
surge += rng.normal(0, 0.012 + 0.010 * tropical, count)
gtsr = 0.72 * surge + rng.normal(0, 0.025, count)
timestamps = (np.datetime64("2000-01-01") + day.astype("timedelta64[D]")).astype("datetime64[s]").astype(np.int64)
np.savez_compressed(
path, format_version=np.asarray(data["format_version"]), data_source=np.asarray("structured_synthetic"),
rs_daily=rs_daily, rs_lagged=rs_lagged, ar_daily=ar_daily, ar_lagged=ar_lagged,
targets_m=surge.astype(np.float32)[:, None], gtsr_m=gtsr.astype(np.float32)[:, None],
latitude_degrees=latitude, longitude_degrees=longitude, timestamps_unix_s=timestamps,
feature_normalization=np.asarray("PCA scores centered and scaled per training feature"),
)
def main():
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, day_offset) in enumerate((("train.npz", config["data"]["train_samples"], 0),
("test.npz", config["data"]["test_samples"], 1000))):
path = output / name
if args.force or not path.exists():
make_split(path, int(count), config, int(config["seed"]) + offset, day_offset)
print(f"generated={path.relative_to(ROOT)} samples={count} daily=50 lagged=300 target=1")
if __name__ == "__main__":
main()
|