Contributor contract¶
This page is the binding contract for adding or modifying a hydrologeez model.
1. State-space model interface¶
A model subclasses hydrologeez.ssm.StateSpaceModel, an abstract
torch.nn.Module, and implements exactly two methods:
def init_state(self, parameters, *, batch_size): ...
def transition(self, state, forcing, parameters): ...
model.run(
forcing,
parameters=None,
warmup=None,
warmup_parameters=None,
observation_operator=default_streamflow_observation,
return_fluxes=False,
)
init_state receives the resolved parameter mapping and batch size. transition
receives one timestep of batched forcing and the parameter tensors, and returns
the next state and fluxes. The base run method performs the eager Python time
loop. It uses registered parameters when parameters is omitted; otherwise it
uses the supplied complete mapping. It returns observations [B, T], or
(observations, fluxes, final_state) when return_fluxes=True, with dataclass
flux leaves stacked on dimension 1.
2. Process and observation functions¶
Process equations belong in plain free functions in each model's processes.py,
where they are independently testable and reusable. transition wires them
together. The pluggable observation operator maps (state, fluxes) to an
observable; the canonical default is
hydrologeez.default_streamflow_observation.
3. Tensor shapes and fixed kernels¶
All forcing leaves share [B, T]. Per-step state and flux tensor leaves preserve
the leading batch dimension. Explicit parameter leaves may be scalar [],
per-basin [B], or per-basin/per-time [B, T].
GR6J unit-hydrograph and HBV routing delay lines remain fixed-size. This preserves model semantics and consistent batched tensor shapes while their ordinates remain tensor functions of continuous parameters. Structural integers are ordinary, non-calibrated module configuration.
4. Dtype and device¶
Float64 on CPU is the reference and golden-fixture path. Float32 on an explicitly
selected accelerator is the training path. Use reference_tensor,
training_tensor, reference_defaults, and training_defaults for explicit
local choices. Preserve caller dtype/device. Do not call
torch.set_default_dtype, mutate the default device, or add an import-time guard.
5. Warmup¶
Warmup is a separate forcing container with the same batch size and may be empty.
Its transitions run under torch.no_grad() and its final state is detached before
the main period creates its own graph. warmup_parameters may be supplied
separately; otherwise the resolved main parameters are reused.
6. Explicit parameters and calibration¶
Registered nn.Parameter leaves are ordinary model state. Functional calibration
must pass a complete explicit mapping without mutating the template model during
objective evaluation. Scalar, per-basin, and per-basin/per-time mappings support
spatial and time-varying parameterization.
calibrate_gradient from hydrologeez.calibration uses a bounded sigmoid
transform, explicit mappings, torch.optim, and optional SSM warmup forcing. It
returns a calibrated model and detached loss history. Evolutionary GA/NSGA-II
keeps ctrl-freak's NumPy population/results boundary but evaluates each population
in one batched Torch call under torch.no_grad(). Its integer warmup slices the
objective period and is distinct from SSM warmup forcing.
7. Tooling and releases¶
- Use
uvonly; do not use pip, poetry, conda, or pip-tools. - Run
ruffformatting/linting,tytype checking, andpytest. - Use modern typing (
list[str],str | None). - Use
numpy.testingfor NumPy leaves andtorch.testing.assert_closefor Torch tensors; retain xarray and polars testing utilities for those objects. - Versions are bumped only in release commits through
bump-my-version, never in ordinary commits. Publishing and tags are created only by GitHub Releases/OIDC.
8. HDX I/O¶
The model core is format-agnostic. Optional Parquet dependencies load lazily
through HDX entry points. HDX is role-opaque, so hydrologeez owns the canonical
precip, pet, temp, and streamflow vocabulary and foreign-name overrides.
from_hdx returns NumPy forcing-dictionary leaves, streamflow, statics, mask, and
NumPy times. .torch(dtype=..., device=...) is the explicit bridge that constructs
the selected model forcing dataclass and converts the mask to torch.bool.
Padding and masks represent ragged multi-basin data. to_hdx must preserve the
HDX 0.2 round-trip contract: string basin IDs, sorted datetime64[us] times,
per-basin dynamic files, root statics, and the six-field manifest.
9. hcx boundary¶
Adapters in hydrologeez.hcx exist only for development conformance. They import
hcx lazily during forecast creation, consume only scalar_dynamic, and preserve
point metadata. Do not import them from the default package path or expose package
entry points. hydrologeez must not register an hcx.models plugin.