kruparell-data assimilation - #283
Conversation
sylvesterkaczmarek
left a comment
There was a problem hiding this comment.
Precipitation DA silently falls back to the first hindcast feature when no precipitation key matches. That can turn assimilation_targets=['precip'] into optimisation of temperature, radiation, or another forcing while still producing a plausible rollout. Could this fail explicitly, or require a configured forcing key, instead of selecting list(hind_dict.keys())[0]? For DA this seems safer than silently changing the physical variable being assimilated.
Thanks for calling this out! You're completely right. Falling back to list(hind_dict.keys())[0] was a silent bug that could optimize radiation or temperature when precipitation keys didn't match. I have pushed an update that: Optimizes all matching precipitation features (hres_total_precipitation, graphcast_total_precipitation, imerg_precipitation, cpc_precipitation) together. |
2a03eeb to
12b9c39
Compare
grey-nearing
left a comment
There was a problem hiding this comment.
Temp & partial review while you reconfigure the PRs.
|
|
||
| mask = ~torch.isnan(t_sub) & ~torch.isnan(p_sub) | ||
| if mask.any(): | ||
| loss = torch.mean((p_sub[mask] - t_sub[mask]) ** 2) |
There was a problem hiding this comment.
Perhaps we should add the loss function to training/loss.py as a child of baseloss. And then either: (1) add an assimilation_loss arguemnt to config.py, or (2) have a specific base_loss inheritance class that is an assimilation_loss and hard-code assimilation.py to use that. The former is better unless you want to make sure the user is unable to change the assimilation loss. I would do that only if you think there is a potential for a user to make a mistake in the sense that assimilation can theoretically or practically only use one specific loss. If there are specific loss functions you want to allow for assimilation or ban for assimilation, you can add that check in config.py (there is a special section for checks on the config file).
|
|
||
| with torch.inference_mode(): | ||
| if data_assimilation: | ||
| assimilation = Assimilation(self.cfg.assimilation_config) |
There was a problem hiding this comment.
Is there a check whether the assimilation_config argument is present in the config file if the cli flag for assimilation is used?
|
|
||
| mask = ~torch.isnan(t_sub) & ~torch.isnan(p_sub) | ||
| if mask.any(): | ||
| loss = torch.mean((p_sub[mask] - t_sub[mask]) ** 2) |
|
|
||
| mask = ~torch.isnan(t_sub) & ~torch.isnan(p_sub) | ||
| if mask.any(): | ||
| loss = torch.mean((p_sub[mask] - t_sub[mask]) ** 2) |
|
|
||
| def _get_var_lr(lr_cfg: Any, var_name: str) -> float: | ||
| """Retrieves target-specific learning rate from lr_cfg.""" | ||
| if isinstance(lr_cfg, dict): |
There was a problem hiding this comment.
Outside of DA, this dict is typically meant to be {epoch_int: rl}.
There was a problem hiding this comment.
This notebook also has paths to your personal machine.
I have not reviewed either of the notebooks yet.
| loss = torch.mean((p_sub[mask] - t_sub[mask]) ** 2) | ||
| reg_loss = 0.0 | ||
| if mask_e_stat and e_stat_opt.requires_grad: | ||
| reg_loss = reg_loss + bg_stat_w * torch.sum((e_stat_opt - e_stat_base) ** 2) |
There was a problem hiding this comment.
Why do you sum here instead of mean?
|
|
||
| def _parse_target_flags(self) -> Tuple[bool, bool, bool, bool]: | ||
| """Parses self.targets into boolean flags for (c_hc, h_hc, c_fc, h_fc).""" | ||
| opt_c_hc = any(k in self.targets for k in ['c_n_hindcast', 'c_0_hindcast', 'c_hc', 'c_n', 'c_0']) |
There was a problem hiding this comment.
Can we use more descriptive variable names? Or at least have a comment that describes the variabel naming convention.
| To run evaluation with test-time 4D-Var Data Assimilation: | ||
|
|
||
| ``` | ||
| run evaluate --run-dir /path/to/your/model_run/ --data-assimilation |
There was a problem hiding this comment.
Can we use inference mode with DA as well? The difference between evaluate vs. infer is that infer saves the timeseries output whereas evaluate only saves the performance metrics.
There was a problem hiding this comment.
Yes we can, I've added a description of this to the README
|
|
||
| def get_optimizer( | ||
| model: torch.nn.Module, cfg: Config, *, is_gpu: bool = False | ||
| model: Union[torch.nn.Module, Iterable[torch.Tensor]], cfg: Config, *, is_gpu: bool = False |
There was a problem hiding this comment.
Flagging this for myself. Why do we need to allow a different model type?
grey-nearing
left a comment
There was a problem hiding this comment.
more temp comments.
| w_end = min(curr_idx + self.window, a_end) | ||
| win_len = w_end - curr_idx | ||
|
|
||
| chunk_data = _slice_hydrology_batch(data, curr_idx, w_end) |
There was a problem hiding this comment.
As discussed in person, the process of choosing the assimilation update targets should be model agnostic. I recommend having each model include a required attribute that lists the model components available for assimilation updating, and then require the user to list the components they want assimilation to target int he config file (with appropriate existence checks). Then direct the optimizer to the selected components generically, in a model-agnostic way.
36cfee5 to
6cebe9e
Compare
6cebe9e to
34d8143
Compare
Variational Data Assimilation for Hydrological Forecasting
Summary
This PR introduces a gradient-based Variational Data Assimilation (DA) framework for hydrological forecasting. This framework optimizes latent model components (such as static and dynamic catchment representations) exposed through a standardized, model-agnostic interface.
Files Changed and Rationale
1.
googlehydrology/modelzoo/basemodel.py&mean_embedding_forecast_lstm.pyBaseModelcontract: Addedsupported_assimilation_componentsproperty andget_supported_assimilation_components()helper.MeanEmbeddingForecastLSTMoverrides:data['assimilation_overrides'](with backward-compatible top-level keys) for'static_embedding','hindcast_embedding', and'forecast_embedding'.2.
googlehydrology/utils/cmal_deterministic.pycalc_cmal_mean(mu, b, tau): Vectorized numerical quadrature computing the expected valueensure_y_hat(pred, use_median=True/False): Unifies prediction dictionaries and ensuresy_hatis populated with robust handling of non-finite outputs.3.
googlehydrology/utils/assimilationconfig.pyassimilation_window,assimilation_lead_time,assimilation_components,regularization_weight,learning_rate,epochs, and component-specific learning rates and regularization weights.4.
googlehydrology/evaluation/assimilation.pytorch.mean((param - base_param) ** 2))._create_assimilation_optimizersupporting Adam, AdamW, SGD, RMSprop, and Adagrad without altering package-level training contracts.5.
googlehydrology/run.py,evaluate.py, &tester.pyrun_data_assimilationflag into CLI commands and pipeline workflows (evaluateandinfer).README.mdanddocs/source/usage/quickstart.rst.6.
test/test_assimilation.py&test/test_cli.pyVerification & Testing
All unit and integration tests pass cleanly: