| """Generate compact hourly data retaining all 12 stations and six pollutants.""" |
|
|
| import sys |
| from pathlib import Path |
|
|
| import numpy as np |
| import yaml |
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| sys.path.insert(0, str(ROOT)) |
| from model.meteonorm_rf import BASE_FEATURES, FORMAT_VERSION, POLLUTANTS, STATIONS |
|
|
|
|
| def main(): |
| config = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) |
| rng = np.random.default_rng(int(config["seed"])) |
| days = int(config["data"]["synthetic_days"]) |
| hours = np.tile(np.arange(days * 24), len(STATIONS)) |
| station = np.repeat(np.arange(len(STATIONS)), days * 24) |
| day = hours // 24 |
| hour = hours % 24 |
| doy = day + int(config["data"]["start_day_of_year"]) |
| weekend = ((day + 1) % 7 >= 5).astype(np.float32) |
| seasonal = np.sin(2 * np.pi * (doy - 172) / 365.25) |
| diurnal = np.sin(2 * np.pi * (hour - 14) / 24) |
| synoptic = np.sin(2 * np.pi * day / 6.5 + station * 0.17) |
| wind_speed = np.maximum(0.2, 2.7 + 1.1 * synoptic + rng.normal(0, .55, len(hours))) |
| wind_direction = np.mod(185 + 75 * synoptic + station * 9 + rng.normal(0, 28, len(hours)), 360) |
| temperature = 13 + 12 * seasonal + 5.5 * diurnal + rng.normal(0, 1.4, len(hours)) |
| rh = np.clip(52 - 18 * seasonal - 10 * diurnal - 3 * wind_speed + rng.normal(0, 7, len(hours)), 15, 98) |
| pressure = 1013 - .32 * temperature + 4 * np.cos(2 * np.pi * day / 8) + rng.normal(0, 1.8, len(hours)) |
| ttrend = 2013.0 + hours / (24 * 365.25) |
| emission = 1.0 - .22 * day / max(days - 1, 1) |
| stagnation = np.exp(-wind_speed / 3.2) * (0.65 + rh / 150) |
| rush = ((hour >= 7) & (hour <= 10)) | ((hour >= 17) & (hour <= 21)) |
| station_factor = 0.82 + station * .025 |
| pm25 = (22 + 78 * stagnation + 16 * rush) * emission * station_factor |
| pm10 = 28 + 1.22 * pm25 + 7 * wind_speed |
| no2 = (15 + 37 * stagnation + 19 * rush) * emission * station_factor |
| so2 = (4 + 25 * stagnation) * emission ** 1.8 * (1.1 - .15 * seasonal) |
| o3 = np.maximum(3, 30 + 29 * np.maximum(diurnal, -.4) + 1.2 * temperature - .28 * no2) |
| co = (0.25 + .012 * pm25 + .008 * no2) * emission |
| pollution = np.column_stack([pm25, pm10, no2, so2, o3, co]) |
| pollution *= rng.lognormal(0, .09, pollution.shape) |
| output = ROOT / config["data"]["path"] |
| output.parent.mkdir(parents=True, exist_ok=True) |
| np.savez_compressed(output, format_version=np.array(FORMAT_VERSION), |
| station_names=np.asarray(STATIONS), pollutant_names=np.asarray(POLLUTANTS), |
| station_id=station.astype(np.int16), timestamp_hour=hours.astype(np.int32), |
| ttrend=ttrend.astype(np.float32), day_of_year=doy.astype(np.float32), |
| weekend=weekend, hour=hour.astype(np.float32), |
| wind_speed=wind_speed.astype(np.float32), wind_direction=wind_direction.astype(np.float32), |
| pressure=pressure.astype(np.float32), temperature=temperature.astype(np.float32), |
| relative_humidity=rh.astype(np.float32), pollution=pollution.astype(np.float32), |
| feature_names=np.asarray(BASE_FEATURES)) |
| print(f"data={output.relative_to(ROOT)} rows={len(hours)} stations=12 pollutants=6 hourly_days={days}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|