Conceptual model inputs#
The conceptual model's inputs used to be six loose attributes on Catchment whose rules were
enforced nowhere in particular. They are three value objects now, each checking its own invariant
in __post_init__, so a bad combination is refused where it is made rather than several frames
into a run.
ParameterSet— the parameter values plus the(snow, maxbas)pair that fixes their width. Every route to a parameter set goes through the same width rule, including the per-trial replacements a calibration makes. It is frozen; usewith_valuesto derive a new set from an optimiser's vector.ConceptualModelSetup— the model, the catchment area, the initial condition and the initial discharge, asread_lumped_modelproduces them.ParameterBounds— the calibration's search space, held to the same width rule as the trial vectors it bounds.
ParameterSet#
hapi.conceptual.ParameterSet
dataclass
#
A parameter set together with the configuration that fixes its width.
Exactly what read_parameters produces. snow and maxbas are not tags travelling
beside the array -- they determine how many parameters there must be
(:data:PARAMETER_COUNTS), so holding the three together is what lets the rule be checked
on construction instead of at one call site.
Frozen: a calibration explores parameter sets by the thousand, and each is a different
set rather than a mutation of the last. :meth:with_values returns a new one and re-runs
the check, so a distribution function producing the wrong width fails on the trial that
produced it rather than as an index error inside the per-cell loop.
Attributes:
| Name | Type | Description |
|---|---|---|
values |
ndarray | list
|
|
snow |
bool
|
Whether the snow routine runs. |
maxbas |
bool
|
Whether the set carries a MAXBAS value instead of Muskingum's two. |
Examples:
- The width has to match the configuration:
>>> import numpy as np >>> from hapi.conceptual import ParameterSet >>> ParameterSet(np.zeros((2, 2, 12))).count 12 >>> ParameterSet(np.zeros((2, 2, 5))) Traceback (most recent call last): ... ValueError: a model with snow=False, maxbas=False takes 12 parameters, got 5
Source code in src/hapi/conceptual.py
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
__post_init__()
#
Check the width matches (snow, maxbas).
Raises:
| Type | Description |
|---|---|
ValueError
|
The count does not match. |
Source code in src/hapi/conceptual.py
191 192 193 194 195 196 197 | |
count: int
property
#
int: Number of parameters the set carries. See :func:parameter_count.
The width rule is enforced when the set is built, so this reads back what it settled on -- for a distributed set, the length of the trailing axis rather than the number of cells.
Examples:
- A lumped set is a flat vector, so the count is its length:
>>> import numpy as np >>> from hapi.conceptual import ParameterSet >>> ParameterSet(np.ones(12), snow=False, maxbas=False).count 12 - A distributed set counts the parameters per cell, not the cells:
>>> import numpy as np >>> from hapi.conceptual import ParameterSet >>> cube = np.ones((13, 14, 12)) >>> ParameterSet(cube, snow=False, maxbas=False).count 12
with_values(values: np.ndarray | list) -> ParameterSet
#
Return the same configuration with a different parameter array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
ndarray | list
|
The replacement parameter array. |
required |
Returns:
| Type | Description |
|---|---|
ParameterSet
|
A new set carrying |
Raises:
| Type | Description |
|---|---|
ValueError
|
The replacement does not carry the required count. |
Source code in src/hapi/conceptual.py
228 229 230 231 232 233 234 235 236 237 238 239 240 | |
ConceptualModelSetup#
hapi.conceptual.ConceptualModelSetup
dataclass
#
The conceptual model instance and the state it starts from.
Exactly what read_lumped_model produces. Kept apart from :class:ParameterSet because
the two are read independently and either may be read first -- pairing them would force a
half-built object to exist, which is the thing this refactor is removing.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
BaseConceptualModel
|
The conceptual model instance whose |
area |
float | int
|
Catchment area in km2. |
initial_cond |
list
|
Initial state values |
q_init |
float | None
|
Initial discharge in m3/s, or None to let the model choose. |
Examples:
>>> from hapi.conceptual import ConceptualModelSetup
>>> from hapi.rrm.hbv_bergestrom92 import HBVBergestrom92
>>> setup = ConceptualModelSetup(
... HBVBergestrom92(), 1530.0, [0, 10, 10, 10, 0], q_init=5.0
... )
>>> setup.area, setup.q_init
(1530.0, 5.0)
Source code in src/hapi/conceptual.py
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | |
__post_init__()
#
Check the initial state and discharge.
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
|
Source code in src/hapi/conceptual.py
275 276 277 278 279 280 281 282 283 | |
ParameterBounds#
hapi.conceptual.ParameterBounds
dataclass
#
The search space a calibration explores, and the configuration it explores it under.
Exactly what read_parameters_bound produces. It carries the same (snow, maxbas) pair
as :class:ParameterSet because a calibration has no parameter file to read it from --
the bounds are where the configuration enters, and every trial vector the optimiser
produces is checked against it.
Attributes:
| Name | Type | Description |
|---|---|---|
lower |
ndarray | list
|
Lower bound per element of the optimiser's search vector. |
upper |
ndarray | list
|
Upper bound per element of the same vector. |
snow |
bool
|
Whether the snow routine runs. |
maxbas |
bool
|
Whether the parameter vector carries a MAXBAS value. |
Examples:
>>> from hapi.conceptual import ParameterBounds
>>> bounds = ParameterBounds([0.0] * 12, [1.0] * 12)
>>> len(bounds)
12
Source code in src/hapi/conceptual.py
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | |
__len__() -> int
#
int: Number of parameters being calibrated.
Source code in src/hapi/conceptual.py
337 338 339 | |
__post_init__()
#
Check the two bounds describe the same parameters.
Raises:
| Type | Description |
|---|---|
ValueError
|
The bounds are different lengths. |
Source code in src/hapi/conceptual.py
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | |
Parameter-count helpers#
hapi.conceptual.parameter_count(parameters: np.ndarray | list) -> int
#
Count the parameters a set carries, whatever shape it is stored in.
A distributed set is a (rows, cols, n) array and a lumped one a flat sequence, so the
count is read from the shape rather than from a mode flag the caller has to supply --
which is how the check used to depend on spatial_resolution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parameters
|
ndarray | list
|
The parameter set, 3D for distributed or 1D for lumped. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Number of parameters per cell (distributed) or in total (lumped). |
Examples:
>>> import numpy as np
>>> from hapi.conceptual import parameter_count
>>> parameter_count(np.zeros((13, 14, 12)))
12
>>> parameter_count([1.0] * 12)
12
Source code in src/hapi/conceptual.py
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 | |
hapi.conceptual.validate_parameter_count(parameters: np.ndarray | list, snow: bool, maxbas: bool) -> None
#
Check a parameter set carries the count (snow, maxbas) requires.
Split out of the spec's constructor so a builder can run it the moment the parameters and
the configuration are both known -- which is inside read_parameters, before the
conceptual model itself has been read. Waiting for the whole spec would move a
wrong-width error to whichever later call completed it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parameters
|
ndarray | list
|
The parameter set. |
required |
snow
|
bool
|
Whether the snow routine runs. |
required |
maxbas
|
bool
|
Whether the set carries a MAXBAS value. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
The count does not match. |
Examples:
>>> import numpy as np
>>> from hapi.conceptual import validate_parameter_count
>>> validate_parameter_count(np.zeros((2, 2, 5)), snow=False, maxbas=False)
Traceback (most recent call last):
...
ValueError: a model with snow=False, maxbas=False takes 12 parameters, got 5
Source code in src/hapi/conceptual.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |