DHDRL commited on
Commit
fcc2f55
·
verified ·
1 Parent(s): b62e656

Update zone_observation.py

Browse files
Files changed (1) hide show
  1. zone_observation.py +57 -4
zone_observation.py CHANGED
@@ -10,7 +10,7 @@ import logging
10
  import math
11
  import random
12
  import zlib
13
- from dataclasses import dataclass, field, asdict
14
  from datetime import datetime, timedelta, timezone
15
  from enum import Enum, unique
16
  from typing import Any, ClassVar, Dict, List, Optional, Tuple
@@ -172,11 +172,27 @@ class ForecastConfig:
172
 
173
  seed: Optional[int] = None
174
 
175
- real_data_ratio: float = 0.7 # fraction of episodes that attempt real data
 
 
 
 
 
 
 
176
  era5_ratio: float = 0.5 # of real-data attempts, fraction using ERA5
177
  force_data_source: Optional[DataSource] = None # pin source for debug/test (overrides above)
178
- inject_noise: bool = False # apply stochastic noise after fetch
 
179
  noise_scale: float = 0.05 # noise magnitude (fraction of field range)
 
 
 
 
 
 
 
 
180
 
181
  use_satellite_precip: bool = False # prefer IMERG/CHIRPS over ERA5 precip
182
  use_satellite_soil: bool = False # prefer SMAP over ERA5 soil moisture
@@ -313,6 +329,7 @@ class ForecastConfig:
313
  ),
314
  "inject_noise": self.inject_noise,
315
  "noise_scale": self.noise_scale,
 
316
  "use_satellite_precip": self.use_satellite_precip,
317
  "use_satellite_soil": self.use_satellite_soil,
318
  "include_basin_context": self.include_basin_context,
@@ -947,7 +964,25 @@ class BasinContext:
947
  _check_schema(sv, "BasinContext")
948
  d["valid_date"] = datetime.fromisoformat(d["valid_date"])
949
  d["source"] = DataSource(d.get("source", "synthetic"))
950
- return cls(**{k: v for k, v in d.items() if not k.startswith("_")})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
 
952
  def make_synthetic_basin_context(
953
  valid_date: Optional[datetime] = None,
@@ -1382,6 +1417,11 @@ if __name__ == "__main__":
1382
  _assert(cfg2.force_data_source is None, "ForecastConfig force_data_source round-trip")
1383
  _assert(cfg2.inject_noise is False, "ForecastConfig inject_noise round-trip")
1384
  _assert(cfg2.noise_scale == 0.05, "ForecastConfig noise_scale round-trip")
 
 
 
 
 
1385
  cfg_era5 = ForecastConfig(force_data_source=DataSource.ERA5_REANALYSIS)
1386
  cfg_era5_back = ForecastConfig.from_dict(cfg_era5.to_dict())
1387
  _assert(
@@ -1514,6 +1554,19 @@ if __name__ == "__main__":
1514
  f"kp={bc.kp_index:.1f} regime={bc.helio_regime} "
1515
  f"round-trip OK, EpisodeContext integration OK")
1516
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1517
  # 11. New optional ZoneObs satellite fields: None-by-default, clipping, round-trip
1518
  obs_sat = ZoneObs(
1519
  zone_id="sat_zone", valid_time=now,
 
10
  import math
11
  import random
12
  import zlib
13
+ from dataclasses import dataclass, field, asdict, fields as _dc_fields
14
  from datetime import datetime, timedelta, timezone
15
  from enum import Enum, unique
16
  from typing import Any, ClassVar, Dict, List, Optional, Tuple
 
172
 
173
  seed: Optional[int] = None
174
 
175
+ real_data_ratio: float = 0.7 # fraction of episodes that attempt real data.
176
+ # Consumed by era5_data_pipeline.py when
177
+ # BUILDING a historical cache, AND (new) by
178
+ # WeatherForecastEnv.reset() when
179
+ # real_data_pkl_path is set, as the
180
+ # per-episode probability of sampling a real
181
+ # historical EpisodeContext instead of a
182
+ # synthetic one during training.
183
  era5_ratio: float = 0.5 # of real-data attempts, fraction using ERA5
184
  force_data_source: Optional[DataSource] = None # pin source for debug/test (overrides above)
185
+ inject_noise: bool = False # apply stochastic noise after fetch, AND (new)
186
+ # after WeatherForecastEnv real-data sampling
187
  noise_scale: float = 0.05 # noise magnitude (fraction of field range)
188
+ real_data_pkl_path: Optional[str] = None # path to a historical trajectory cache
189
+ # (see real_episode_sampler.RealEpisodeIndex).
190
+ # When set, WeatherForecastEnv.reset() may
191
+ # sample real episodes for training with
192
+ # probability real_data_ratio, respecting
193
+ # RealEpisodeIndex's holdout exclusion. When
194
+ # None (default), training is 100% synthetic,
195
+ # unchanged from prior behavior.
196
 
197
  use_satellite_precip: bool = False # prefer IMERG/CHIRPS over ERA5 precip
198
  use_satellite_soil: bool = False # prefer SMAP over ERA5 soil moisture
 
329
  ),
330
  "inject_noise": self.inject_noise,
331
  "noise_scale": self.noise_scale,
332
+ "real_data_pkl_path": self.real_data_pkl_path,
333
  "use_satellite_precip": self.use_satellite_precip,
334
  "use_satellite_soil": self.use_satellite_soil,
335
  "include_basin_context": self.include_basin_context,
 
964
  _check_schema(sv, "BasinContext")
965
  d["valid_date"] = datetime.fromisoformat(d["valid_date"])
966
  d["source"] = DataSource(d.get("source", "synthetic"))
967
+ known = {f.name for f in _dc_fields(cls)}
968
+ unrecognized = {
969
+ k: v for k, v in d.items()
970
+ if not k.startswith("_") and k not in known
971
+ }
972
+ if unrecognized:
973
+ # Forward-compat, not silent data loss: seen in practice with
974
+ # historical_continuous_indonesia_v1_helio_backfilled_canonical_v2.pkl,
975
+ # which stamps helio_source_label / helio_backfill_valid_date
976
+ # directly into basin_context dicts from an out-of-repo backfill
977
+ # script (surgical_helio_backfill_v2.py) rather than through this
978
+ # class. Dropping them here is deliberate -- add a real field to
979
+ # BasinContext if this provenance data should be preserved and
980
+ # consumed, rather than widening this filter.
981
+ logger.debug(
982
+ "BasinContext.from_dict: dropping unrecognized fields %s "
983
+ "(schema forward-compat)", sorted(unrecognized),
984
+ )
985
+ return cls(**{k: v for k, v in d.items() if k in known})
986
 
987
  def make_synthetic_basin_context(
988
  valid_date: Optional[datetime] = None,
 
1417
  _assert(cfg2.force_data_source is None, "ForecastConfig force_data_source round-trip")
1418
  _assert(cfg2.inject_noise is False, "ForecastConfig inject_noise round-trip")
1419
  _assert(cfg2.noise_scale == 0.05, "ForecastConfig noise_scale round-trip")
1420
+ _assert(cfg2.real_data_pkl_path is None, "ForecastConfig real_data_pkl_path default round-trip")
1421
+ cfg_pkl = ForecastConfig(real_data_pkl_path="/tmp/example.pkl")
1422
+ cfg_pkl_back = ForecastConfig.from_dict(cfg_pkl.to_dict())
1423
+ _assert(cfg_pkl_back.real_data_pkl_path == "/tmp/example.pkl",
1424
+ "non-default real_data_pkl_path round-trip")
1425
  cfg_era5 = ForecastConfig(force_data_source=DataSource.ERA5_REANALYSIS)
1426
  cfg_era5_back = ForecastConfig.from_dict(cfg_era5.to_dict())
1427
  _assert(
 
1554
  f"kp={bc.kp_index:.1f} regime={bc.helio_regime} "
1555
  f"round-trip OK, EpisodeContext integration OK")
1556
 
1557
+ # 10b. BasinContext.from_dict tolerates unrecognized fields (schema
1558
+ # drift from out-of-repo backfill scripts) instead of crashing.
1559
+ d_drift = bc.to_dict()
1560
+ d_drift["helio_source_label"] = "gfz_kp_daily_max+neutral_sw_goes_v2"
1561
+ d_drift["helio_backfill_valid_date"] = "2023-08-04"
1562
+ bc_drift = BasinContext.from_dict(d_drift)
1563
+ _assert(abs(bc_drift.enso_oni - bc.enso_oni) < 1e-9,
1564
+ "BasinContext.from_dict with unrecognized fields lost a known field")
1565
+ _assert(not hasattr(bc_drift, "helio_source_label"),
1566
+ "BasinContext.from_dict should not silently attach unknown attrs")
1567
+ print(" BasinContext.from_dict schema-drift tolerance OK "
1568
+ "(helio_source_label-style extra keys no longer crash)")
1569
+
1570
  # 11. New optional ZoneObs satellite fields: None-by-default, clipping, round-trip
1571
  obs_sat = ZoneObs(
1572
  zone_id="sat_zone", valid_time=now,