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()