Skip to content

Run#

Run#

hapi.run.Run #

Run the catchment model.

A namespace of static entry points, not a class to instantiate. Each one validates the model it is given and hands it to :class:~hapi.wrapper.Wrapper, returning the :class:~hapi.results.SimulationResults the run produced. The same object is also assigned to the model's results, so the result arrays stay readable off the model afterwards.

Methods:

Name Description
run_distributed

Run the distributed hydrological model.

run_distributed_with_lake

Run the distributed model with a lake component.

run_maxbas

Run the FW1 distributed model.

run_maxbas_with_lake

Run the FW1 model with a lake component.

run_lumped

Run the lumped conceptual model.

run_flood

Run the flood model.

Examples:

  • Build a model and run it; the results come back and stay on the model:
    >>> from hapi.catchment import Catchment
    >>> from hapi.routing import Routing
    >>> from hapi.run import Run
    >>> model = Catchment.from_yaml(
    ...     "examples/hydrological-model/coello/run/coello-lumped-model-run.yaml"
    ... )
    >>> results = Run.run_lumped(model, 1, Routing.muskingum_v)
    >>> results.routing.value
    'lumped'
    >>> results is model.results
    True
    
See Also

hapi.catchment.Catchment.from_yaml: Builds a model from a run configuration.

Source code in src/hapi/run.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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
241
242
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
284
285
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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
class Run:
    """Run the catchment model.

    A namespace of static entry points, not a class to instantiate. Each one validates the
    model it is given and hands it to :class:`~hapi.wrapper.Wrapper`, returning the
    :class:`~hapi.results.SimulationResults` the run produced. The same object is also
    assigned to the model's `results`, so the result arrays stay readable off the model
    afterwards.

    Methods:
        run_distributed: Run the distributed hydrological model.
        run_distributed_with_lake: Run the distributed model with a lake component.
        run_maxbas: Run the FW1 distributed model.
        run_maxbas_with_lake: Run the FW1 model with a lake component.
        run_lumped: Run the lumped conceptual model.
        run_flood: Run the flood model.

    Examples:
        - Build a model and run it; the results come back and stay on the model:
            ```python
            >>> from hapi.catchment import Catchment
            >>> from hapi.routing import Routing
            >>> from hapi.run import Run
            >>> model = Catchment.from_yaml(
            ...     "examples/hydrological-model/coello/run/coello-lumped-model-run.yaml"
            ... )
            >>> results = Run.run_lumped(model, 1, Routing.muskingum_v)
            >>> results.routing.value
            'lumped'
            >>> results is model.results
            True

            ```

    See Also:
        hapi.catchment.Catchment.from_yaml: Builds a model from a run configuration.
    """

    @staticmethod
    def run_distributed(model: CatchmentLike) -> SimulationResults:
        """Run the distributed hydrological model.

        Validates that all input arrays (precipitation, evapotranspiration,
        temperature, parameters, and flow direction) have consistent
        dimensions, then executes the rainfall-runoff model via the
        Wrapper.

        Args:
            model: The model to run. See :class:`DistributedModel` for what it must carry.

        Returns:
            SimulationResults: The run's output, also assigned to `model.results`:

            - `state_variables`: 4D array (rows, cols, time, states) where
                states are [sp, wc, sm, uz, lv].
            - `qlz`: 3D array of the lower zone discharge.
            - `quz`: 3D array of the upper zone discharge.
            - `quz_routed`: 3D array of the upper zone discharge
                accumulated and routed at each time step.
            - `qlz_translated`: 3D array of the lower zone discharge
                translated at each time step.
            - `q_total`: `quz_routed + qlz_translated`. Routed by Muskingum, so the
                outlet cell carries the outlet hydrograph; `extract_discharge` fills
                `qout` from it.

        Raises:
            ValueError: If input data arrays have inconsistent
                row counts, column counts, or temporal lengths.
        """
        run = DistributedRun.from_model(model)
        results = Wrapper.run_muskingum(run)

        model.results = results
        logger.info(RUN_FINISHED)
        return results

    @staticmethod
    def run_flood(
        model: CatchmentLike, skip_hydraulic_cells: bool | None = None
    ) -> SimulationResults:
        """Run the flood model.

        Runs the conceptual distributed hydrological model with
        additional validation for river geometry inputs (bankfull depth,
        river width, river roughness, and flood plain roughness).

        Args:
            model: The model to run. See :class:`FloodModel` for what it must carry.
            skip_hydraulic_cells: Leave river cells (a positive `bankfull_depth`) unrouted
                by the Muskingum pass, because the kinematic-wave model routes them instead.
                `None`, the default, derives it from the catchment's own
                `routing_method` -- `"Kinematic"` means yes, anything else no -- and warns,
                because the hydraulic model that was supposed to take those cells is not part
                of Hapi. Pass `True` to state that something downstream takes them and
                silence the warning, or `False` to route every cell here.

        Warns:
            UserWarning: The skip was derived from `routing_method="Kinematic"`, so the river
                cells are left unrouted and their discharge is absent from the results.

        Raises:
            ValueError: If meteorological input arrays, parameter
                arrays, or river geometry arrays have inconsistent
                dimensions.
        """
        derived = skip_hydraulic_cells is None
        skip = (
            model.routing_method == "Kinematic"
            if derived
            else bool(skip_hydraulic_cells)
        )

        # Every check the old inline block made now happens in the run type: `RiverGeometry`
        # settles that the five rasters share a grid, and `DistributedRun` that the grid is the
        # catchment's and that a requested skip has geometry to identify the river cells with.
        run = DistributedRun.from_model(
            model, with_river_geometry=True, skip_hydraulic_cells=skip
        )

        if skip and derived and run.river_geometry is not None:
            _warn_about_the_unrouted_river_cells(run, run.river_geometry)

        results = Wrapper.run_muskingum(run)
        model.results = results
        logger.info("RRM has finished")
        # SV = SaintVenant()
        # SV.KinematicRaster(model)
        # print("1D model Run has finished")
        return results

    @staticmethod
    def run_distributed_with_lake(
        model: CatchmentLike, lake: LakeType
    ) -> SimulationResults:
        """Run the distributed model with a lake component.

        Validates that all input arrays have consistent dimensions and
        that the lake meteorological data matches the simulation period,
        then executes the rainfall-runoff model with lake routing via
        the Wrapper.

        Args:
            model: The model to run. See :class:`DistributedModel` for what it must carry.
            lake: Lake object containing lake configuration and
                meteorological data. Must have a `MeteoData` attribute
                with shape `(time_steps, >= 4)` where columns are
                rain, ET, temperature, and the long-term average
                temperature the wrappers read as column 3.

        Returns:
            SimulationResults: The run's output, also assigned to `model.results`.

        Raises:
            ValueError: If input data arrays have inconsistent
                dimensions or if the lake meteorological data length
                does not match the distributed raster data length.
        """
        run = DistributedRun.from_model(model)
        _check_lake_meteo(run, lake)
        results = Wrapper.run_muskingum_with_lake(run, lake)

        model.results = results
        logger.info(RUN_FINISHED)
        return results

    @staticmethod
    def run_maxbas(model: CatchmentLike) -> SimulationResults:
        """Run the FW1 distributed hydrological model.

        Validates that all input arrays have consistent dimensions,
        then executes the FW1 model via the Wrapper. The flow-direction
        raster is not checked here because MAXBAS never reads it.

        Args:
            model: The model to run. See :class:`DistributedModel` for what it must carry.

        Returns:
            SimulationResults: The run's output, also assigned to `model.results`:

            - `state_variables`: 4D array of state variables.
            - `qout`: 1D array of calculated discharge at the catchment
                outlet, summed over every cell.
            - `quz`: 3D array of distributed discharge for each cell.
            - `q_total`, `quz_routed`, `qlz_translated`: 3D per-cell fields
                read by `results.save` and `results.animate`. MAXBAS routes each
                cell straight to the outlet, so a cell of `q_total` is that cell's
                *contribution* to the outlet — `np.nansum` over the domain
                reproduces `qout`. `extract_discharge` reads the routing off the
                results and takes the basin-wide sum on this path automatically.

        Raises:
            ValueError: If input data arrays have inconsistent
                row counts, column counts, or temporal lengths.
        """
        run = DistributedRun.from_model(model, needs_flow_direction=False)
        results = Wrapper.run_maxbas(run)

        model.results = results
        logger.info(RUN_FINISHED)
        return results

    @staticmethod
    def run_maxbas_with_lake(model: CatchmentLike, lake: LakeType) -> SimulationResults:
        """Run the FW1 distributed model with a lake component.

        Validates that all input arrays have consistent dimensions and
        that the lake meteorological data matches the simulation period,
        then executes the FW1 model with lake routing via the Wrapper.

        Args:
            model: The model to run. See :class:`DistributedModel` for what it must carry.
            lake: Lake object containing lake configuration and
                meteorological data. Must have a `MeteoData` attribute
                with shape `(time_steps, >= 4)` where columns are
                rain, ET, temperature, and the long-term average
                temperature the wrappers read as column 3.

        Returns:
            SimulationResults: The run's output, also assigned to `model.results`.

        Raises:
            ValueError: If input data arrays have inconsistent
                dimensions or if the lake meteorological data length
                does not match the distributed raster data length.
        """
        run = DistributedRun.from_model(model, needs_flow_direction=False)
        _check_lake_meteo(run, lake)

        results = Wrapper.run_maxbas_with_lake(run, lake)
        model.results = results
        return results

    @staticmethod
    def run_lumped(
        model: SupportsQsim,
        Route: int = 0,
        routing_fn: Callable[..., Any] | None = None,
    ) -> SimulationResults:
        """Run the lumped conceptual model.

        Executes a lumped conceptual hydrological model, optionally
        routing the generated discharge hydrograph. The simulated
        discharge is stored in `model.Qsim` as a pandas DataFrame
        indexed by the simulation date range.

        Args:
            model: The model to run. See :class:`LumpedModelInputs` for what it must carry.
            Route: Flag to decide whether to route the generated
                discharge hydrograph. Use 0 for no routing or 1 to
                enable routing. Defaults to 0.
            routing_fn: Function to route the discharge hydrograph.
                Required when `Route` is not 0.

        Returns:
            SimulationResults: The run's output, also assigned to `model.results`. A lumped
            run applies no spatial routing, so the routed fields stay None and
            `routing` is `RoutingKind.LUMPED`.

        Raises:
            ValueError: `Route` is not 0 and no routing function was given.
        """
        if routing_fn is None and Route != 0:
            raise ValueError("routing_fn must be a callable when Route != 0")
        # The calendar belongs to the period, which derives it from the span and the
        # resolution -- this branch used to be written out here for the fourth time.
        ind = model.period.date_index

        run = LumpedRun.from_model(model)
        results = Wrapper.run_lumped(run, Route, routing_fn)

        # The engine puts the lumped total in `results.q_total`; indexing it by the period and
        # putting the frame on the model is this layer's job, not the engine's.
        Qsim = pd.DataFrame(index=ind)
        Qsim["q"] = results.q_total
        model.Qsim = Qsim[:]
        model.results = results
        logger.info("Lumped model run has finished successfully")
        return results

run_distributed(model: CatchmentLike) -> SimulationResults staticmethod #

Run the distributed hydrological model.

Validates that all input arrays (precipitation, evapotranspiration, temperature, parameters, and flow direction) have consistent dimensions, then executes the rainfall-runoff model via the Wrapper.

Parameters:

Name Type Description Default
model CatchmentLike

The model to run. See :class:DistributedModel for what it must carry.

required

Returns:

Type Description
SimulationResults

The run's output, also assigned to model.results:

- `state_variables`

4D array (rows, cols, time, states) where states are [sp, wc, sm, uz, lv].

- `qlz`

3D array of the lower zone discharge.

- `quz`

3D array of the upper zone discharge.

- `quz_routed`

3D array of the upper zone discharge accumulated and routed at each time step.

- `qlz_translated`

3D array of the lower zone discharge translated at each time step.

- `q_total`

quz_routed + qlz_translated. Routed by Muskingum, so the outlet cell carries the outlet hydrograph; extract_discharge fills qout from it.

Raises:

Type Description
ValueError

If input data arrays have inconsistent row counts, column counts, or temporal lengths.

Source code in src/hapi/run.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
@staticmethod
def run_distributed(model: CatchmentLike) -> SimulationResults:
    """Run the distributed hydrological model.

    Validates that all input arrays (precipitation, evapotranspiration,
    temperature, parameters, and flow direction) have consistent
    dimensions, then executes the rainfall-runoff model via the
    Wrapper.

    Args:
        model: The model to run. See :class:`DistributedModel` for what it must carry.

    Returns:
        SimulationResults: The run's output, also assigned to `model.results`:

        - `state_variables`: 4D array (rows, cols, time, states) where
            states are [sp, wc, sm, uz, lv].
        - `qlz`: 3D array of the lower zone discharge.
        - `quz`: 3D array of the upper zone discharge.
        - `quz_routed`: 3D array of the upper zone discharge
            accumulated and routed at each time step.
        - `qlz_translated`: 3D array of the lower zone discharge
            translated at each time step.
        - `q_total`: `quz_routed + qlz_translated`. Routed by Muskingum, so the
            outlet cell carries the outlet hydrograph; `extract_discharge` fills
            `qout` from it.

    Raises:
        ValueError: If input data arrays have inconsistent
            row counts, column counts, or temporal lengths.
    """
    run = DistributedRun.from_model(model)
    results = Wrapper.run_muskingum(run)

    model.results = results
    logger.info(RUN_FINISHED)
    return results

run_distributed_with_lake(model: CatchmentLike, lake: LakeType) -> SimulationResults staticmethod #

Run the distributed model with a lake component.

Validates that all input arrays have consistent dimensions and that the lake meteorological data matches the simulation period, then executes the rainfall-runoff model with lake routing via the Wrapper.

Parameters:

Name Type Description Default
model CatchmentLike

The model to run. See :class:DistributedModel for what it must carry.

required
lake Lake

Lake object containing lake configuration and meteorological data. Must have a MeteoData attribute with shape (time_steps, >= 4) where columns are rain, ET, temperature, and the long-term average temperature the wrappers read as column 3.

required

Returns:

Type Description
SimulationResults

The run's output, also assigned to model.results.

Raises:

Type Description
ValueError

If input data arrays have inconsistent dimensions or if the lake meteorological data length does not match the distributed raster data length.

Source code in src/hapi/run.py
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
@staticmethod
def run_distributed_with_lake(
    model: CatchmentLike, lake: LakeType
) -> SimulationResults:
    """Run the distributed model with a lake component.

    Validates that all input arrays have consistent dimensions and
    that the lake meteorological data matches the simulation period,
    then executes the rainfall-runoff model with lake routing via
    the Wrapper.

    Args:
        model: The model to run. See :class:`DistributedModel` for what it must carry.
        lake: Lake object containing lake configuration and
            meteorological data. Must have a `MeteoData` attribute
            with shape `(time_steps, >= 4)` where columns are
            rain, ET, temperature, and the long-term average
            temperature the wrappers read as column 3.

    Returns:
        SimulationResults: The run's output, also assigned to `model.results`.

    Raises:
        ValueError: If input data arrays have inconsistent
            dimensions or if the lake meteorological data length
            does not match the distributed raster data length.
    """
    run = DistributedRun.from_model(model)
    _check_lake_meteo(run, lake)
    results = Wrapper.run_muskingum_with_lake(run, lake)

    model.results = results
    logger.info(RUN_FINISHED)
    return results

run_flood(model: CatchmentLike, skip_hydraulic_cells: bool | None = None) -> SimulationResults staticmethod #

Run the flood model.

Runs the conceptual distributed hydrological model with additional validation for river geometry inputs (bankfull depth, river width, river roughness, and flood plain roughness).

Parameters:

Name Type Description Default
model CatchmentLike

The model to run. See :class:FloodModel for what it must carry.

required
skip_hydraulic_cells bool | None

Leave river cells (a positive bankfull_depth) unrouted by the Muskingum pass, because the kinematic-wave model routes them instead. None, the default, derives it from the catchment's own routing_method -- "Kinematic" means yes, anything else no -- and warns, because the hydraulic model that was supposed to take those cells is not part of Hapi. Pass True to state that something downstream takes them and silence the warning, or False to route every cell here.

None

Warns:

Type Description
UserWarning

The skip was derived from routing_method="Kinematic", so the river cells are left unrouted and their discharge is absent from the results.

Raises:

Type Description
ValueError

If meteorological input arrays, parameter arrays, or river geometry arrays have inconsistent dimensions.

Source code in src/hapi/run.py
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
241
242
243
@staticmethod
def run_flood(
    model: CatchmentLike, skip_hydraulic_cells: bool | None = None
) -> SimulationResults:
    """Run the flood model.

    Runs the conceptual distributed hydrological model with
    additional validation for river geometry inputs (bankfull depth,
    river width, river roughness, and flood plain roughness).

    Args:
        model: The model to run. See :class:`FloodModel` for what it must carry.
        skip_hydraulic_cells: Leave river cells (a positive `bankfull_depth`) unrouted
            by the Muskingum pass, because the kinematic-wave model routes them instead.
            `None`, the default, derives it from the catchment's own
            `routing_method` -- `"Kinematic"` means yes, anything else no -- and warns,
            because the hydraulic model that was supposed to take those cells is not part
            of Hapi. Pass `True` to state that something downstream takes them and
            silence the warning, or `False` to route every cell here.

    Warns:
        UserWarning: The skip was derived from `routing_method="Kinematic"`, so the river
            cells are left unrouted and their discharge is absent from the results.

    Raises:
        ValueError: If meteorological input arrays, parameter
            arrays, or river geometry arrays have inconsistent
            dimensions.
    """
    derived = skip_hydraulic_cells is None
    skip = (
        model.routing_method == "Kinematic"
        if derived
        else bool(skip_hydraulic_cells)
    )

    # Every check the old inline block made now happens in the run type: `RiverGeometry`
    # settles that the five rasters share a grid, and `DistributedRun` that the grid is the
    # catchment's and that a requested skip has geometry to identify the river cells with.
    run = DistributedRun.from_model(
        model, with_river_geometry=True, skip_hydraulic_cells=skip
    )

    if skip and derived and run.river_geometry is not None:
        _warn_about_the_unrouted_river_cells(run, run.river_geometry)

    results = Wrapper.run_muskingum(run)
    model.results = results
    logger.info("RRM has finished")
    # SV = SaintVenant()
    # SV.KinematicRaster(model)
    # print("1D model Run has finished")
    return results

run_lumped(model: SupportsQsim, Route: int = 0, routing_fn: Callable[..., Any] | None = None) -> SimulationResults staticmethod #

Run the lumped conceptual model.

Executes a lumped conceptual hydrological model, optionally routing the generated discharge hydrograph. The simulated discharge is stored in model.Qsim as a pandas DataFrame indexed by the simulation date range.

Parameters:

Name Type Description Default
model SupportsQsim

The model to run. See :class:LumpedModelInputs for what it must carry.

required
Route int

Flag to decide whether to route the generated discharge hydrograph. Use 0 for no routing or 1 to enable routing. Defaults to 0.

0
routing_fn Callable[..., Any] | None

Function to route the discharge hydrograph. Required when Route is not 0.

None

Returns:

Type Description
SimulationResults

The run's output, also assigned to model.results. A lumped

SimulationResults

run applies no spatial routing, so the routed fields stay None and

SimulationResults

routing is RoutingKind.LUMPED.

Raises:

Type Description
ValueError

Route is not 0 and no routing function was given.

Source code in src/hapi/run.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
@staticmethod
def run_lumped(
    model: SupportsQsim,
    Route: int = 0,
    routing_fn: Callable[..., Any] | None = None,
) -> SimulationResults:
    """Run the lumped conceptual model.

    Executes a lumped conceptual hydrological model, optionally
    routing the generated discharge hydrograph. The simulated
    discharge is stored in `model.Qsim` as a pandas DataFrame
    indexed by the simulation date range.

    Args:
        model: The model to run. See :class:`LumpedModelInputs` for what it must carry.
        Route: Flag to decide whether to route the generated
            discharge hydrograph. Use 0 for no routing or 1 to
            enable routing. Defaults to 0.
        routing_fn: Function to route the discharge hydrograph.
            Required when `Route` is not 0.

    Returns:
        SimulationResults: The run's output, also assigned to `model.results`. A lumped
        run applies no spatial routing, so the routed fields stay None and
        `routing` is `RoutingKind.LUMPED`.

    Raises:
        ValueError: `Route` is not 0 and no routing function was given.
    """
    if routing_fn is None and Route != 0:
        raise ValueError("routing_fn must be a callable when Route != 0")
    # The calendar belongs to the period, which derives it from the span and the
    # resolution -- this branch used to be written out here for the fourth time.
    ind = model.period.date_index

    run = LumpedRun.from_model(model)
    results = Wrapper.run_lumped(run, Route, routing_fn)

    # The engine puts the lumped total in `results.q_total`; indexing it by the period and
    # putting the frame on the model is this layer's job, not the engine's.
    Qsim = pd.DataFrame(index=ind)
    Qsim["q"] = results.q_total
    model.Qsim = Qsim[:]
    model.results = results
    logger.info("Lumped model run has finished successfully")
    return results

run_maxbas(model: CatchmentLike) -> SimulationResults staticmethod #

Run the FW1 distributed hydrological model.

Validates that all input arrays have consistent dimensions, then executes the FW1 model via the Wrapper. The flow-direction raster is not checked here because MAXBAS never reads it.

Parameters:

Name Type Description Default
model CatchmentLike

The model to run. See :class:DistributedModel for what it must carry.

required

Returns:

Type Description
SimulationResults

The run's output, also assigned to model.results:

- `state_variables`

4D array of state variables.

- `qout`

1D array of calculated discharge at the catchment outlet, summed over every cell.

- `quz`

3D array of distributed discharge for each cell.

- `q_total`, `quz_routed`, `qlz_translated`

3D per-cell fields read by results.save and results.animate. MAXBAS routes each cell straight to the outlet, so a cell of q_total is that cell's contribution to the outlet — np.nansum over the domain reproduces qout. extract_discharge reads the routing off the results and takes the basin-wide sum on this path automatically.

Raises:

Type Description
ValueError

If input data arrays have inconsistent row counts, column counts, or temporal lengths.

Source code in src/hapi/run.py
280
281
282
283
284
285
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
@staticmethod
def run_maxbas(model: CatchmentLike) -> SimulationResults:
    """Run the FW1 distributed hydrological model.

    Validates that all input arrays have consistent dimensions,
    then executes the FW1 model via the Wrapper. The flow-direction
    raster is not checked here because MAXBAS never reads it.

    Args:
        model: The model to run. See :class:`DistributedModel` for what it must carry.

    Returns:
        SimulationResults: The run's output, also assigned to `model.results`:

        - `state_variables`: 4D array of state variables.
        - `qout`: 1D array of calculated discharge at the catchment
            outlet, summed over every cell.
        - `quz`: 3D array of distributed discharge for each cell.
        - `q_total`, `quz_routed`, `qlz_translated`: 3D per-cell fields
            read by `results.save` and `results.animate`. MAXBAS routes each
            cell straight to the outlet, so a cell of `q_total` is that cell's
            *contribution* to the outlet — `np.nansum` over the domain
            reproduces `qout`. `extract_discharge` reads the routing off the
            results and takes the basin-wide sum on this path automatically.

    Raises:
        ValueError: If input data arrays have inconsistent
            row counts, column counts, or temporal lengths.
    """
    run = DistributedRun.from_model(model, needs_flow_direction=False)
    results = Wrapper.run_maxbas(run)

    model.results = results
    logger.info(RUN_FINISHED)
    return results

run_maxbas_with_lake(model: CatchmentLike, lake: LakeType) -> SimulationResults staticmethod #

Run the FW1 distributed model with a lake component.

Validates that all input arrays have consistent dimensions and that the lake meteorological data matches the simulation period, then executes the FW1 model with lake routing via the Wrapper.

Parameters:

Name Type Description Default
model CatchmentLike

The model to run. See :class:DistributedModel for what it must carry.

required
lake Lake

Lake object containing lake configuration and meteorological data. Must have a MeteoData attribute with shape (time_steps, >= 4) where columns are rain, ET, temperature, and the long-term average temperature the wrappers read as column 3.

required

Returns:

Type Description
SimulationResults

The run's output, also assigned to model.results.

Raises:

Type Description
ValueError

If input data arrays have inconsistent dimensions or if the lake meteorological data length does not match the distributed raster data length.

Source code in src/hapi/run.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
@staticmethod
def run_maxbas_with_lake(model: CatchmentLike, lake: LakeType) -> SimulationResults:
    """Run the FW1 distributed model with a lake component.

    Validates that all input arrays have consistent dimensions and
    that the lake meteorological data matches the simulation period,
    then executes the FW1 model with lake routing via the Wrapper.

    Args:
        model: The model to run. See :class:`DistributedModel` for what it must carry.
        lake: Lake object containing lake configuration and
            meteorological data. Must have a `MeteoData` attribute
            with shape `(time_steps, >= 4)` where columns are
            rain, ET, temperature, and the long-term average
            temperature the wrappers read as column 3.

    Returns:
        SimulationResults: The run's output, also assigned to `model.results`.

    Raises:
        ValueError: If input data arrays have inconsistent
            dimensions or if the lake meteorological data length
            does not match the distributed raster data length.
    """
    run = DistributedRun.from_model(model, needs_flow_direction=False)
    _check_lake_meteo(run, lake)

    results = Wrapper.run_maxbas_with_lake(run, lake)
    model.results = results
    return results