CSDMS Basic Model Interface

HydroRaVENS includes a CSDMS Basic Model Interface (BMI) wrapper that enables it to be driven by any BMI-compliant coupling framework and to exchange variables with other BMI models.

Overview

The BMI wrapper exposes HydroRaVENS as a scalar (lumped) model with a single grid of rank 0 and size 1. All variables are scalars representing catchment-integrated quantities.

The wrapper supports two usage modes:

File-driven (standard workflow)

The YAML configuration file points to a CSV containing all forcing data. The framework calls update() repeatedly to step through the record. No set_value() calls are needed.

Online coupled

An upstream model provides forcing each timestep via set_value() before calling update(). The CSV file still provides the initial time series (used for spin-up and as a default if a variable is not overridden).

Installation

The BMI wrapper requires bmipy. Install it with the bmi optional-dependency group:

pip install 'hydroRaVENS[bmi]'

Usage

File-driven

from hydroravens import BmiHydroRaVENS

bmi = BmiHydroRaVENS()
bmi.initialize("config.yml")

while bmi.get_current_time() < bmi.get_end_time():
    bmi.update()

bmi.finalize()

Use update_until() to advance to a specific time without writing the loop yourself:

bmi.update_until(365.0)   # advance one year

Online coupled

import numpy as np
from hydroravens import BmiHydroRaVENS

bmi = BmiHydroRaVENS()
bmi.initialize("config.yml")        # CSV values loaded for spin-up

while bmi.get_current_time() < bmi.get_end_time():
    # Override forcing from an upstream model
    bmi.set_value(
        "atmosphere_water__liquid_equivalent_precipitation_rate",
        np.array([p_from_upstream])
    )
    bmi.set_value("atmosphere__temperature", np.array([t_from_upstream]))
    bmi.update()

    # Pass volumetric discharge [m³ s⁻¹] to a downstream channel or
    # sediment model.  Specific discharge [mm d⁻¹] is also available
    # via "land_surface_water__runoff_volume_flux".
    q_m3s = np.empty(1, dtype=np.float64)
    bmi.get_value("channel_exit_water__volume_flow_rate", q_m3s)
    downstream_model.set_value("channel_exit_water__volume_flow_rate",
                               q_m3s)

bmi.finalize()

Converting specific discharge to volumetric flow

land_surface_water__runoff_volume_flux is area-normalised specific discharge in mm d⁻¹. To convert to volumetric discharge Q [m³ s⁻¹]:

area_km2 = bmi._model.drainage_basin_area__km2
Q_m3s = q_mm_d * 1e-3 * area_km2 * 1e6 / 86400  # mm→m, km²→m², day→s

Exposed Variables

All variables are scalar (grid rank 0, size 1, location node). Types are float64; time unit is d (days).

Input variables

These variables are read from the CSV by default. In online-coupled mode, call set_value() before each update() to override them.

Temperature and ET inputs are declared even when those columns are absent from the CSV. Calling set_value() for an absent column raises KeyError.

CSDMS Standard Name

Units

HydroRaVENS column

atmosphere_water__liquid_equivalent_precipitation_rate

mm d⁻¹

Precipitation [mm/day]

atmosphere__temperature

°C

Mean Temperature [C]

atmosphere__minimum_temperature

°C

Minimum Temperature [C]

atmosphere__maximum_temperature

°C

Maximum Temperature [C]

land_surface_water__potential_evapotranspiration_volume_flux

mm d⁻¹

Evapotranspiration [mm/day]

Output variables

These variables are updated by each call to update() and retrieved via get_value().

CSDMS Standard Name

Units

Source

land_surface_water__runoff_volume_flux

mm d⁻¹

Modelled specific discharge (area-normalised)

channel_exit_water__volume_flow_rate

m³ s⁻¹

Volumetric discharge (specific discharge × catchment area)

snowpack__liquid_equivalent_depth

mm

Snowpack SWE; 0.0 if no snowpack

subsurface_water__depth

mm

Total subsurface storage (all reservoirs)

subsurface_water_reservoir_0__depth

mm

Reservoir 0 storage (shallowest)

subsurface_water_reservoir_1__depthsubsurface_water_reservoir_9__depth

mm

Reservoirs 1–9 storage (deeper); nan for indices ≥ number of configured reservoirs

Note

HydroRaVENS itself places no limit on the number of reservoirs — you can add as many as you like to the reservoirs: block in the YAML configuration. The BMI wrapper caps exposed reservoir outputs at 10 (indices 0–9) because the BMI specification requires variable names to be fixed at import time. If you configure more than 10 reservoirs, initialize() will raise a ValueError with instructions pointing to the four constants in hydroravens/bmi.py that need updating: _OUTPUT_VAR_NAMES, _VAR_UNITS, _RESERVOIR_DEPTH_NAMES, and _BMI_MAX_RESERVOIRS. The total subsurface storage across all reservoirs is always available via subsurface_water__depth regardless of reservoir count.

Grid and Time

Grid ID

0 (all variables share one scalar grid)

Grid rank

0 (scalar — no spatial dimensions)

Grid size

1

Grid type

"scalar"

Time unit

"d" (days)

Timestep

1.0 day (HydroRaVENS is a daily model)

Start time

0.0

End time

Number of days in the input record

Shape, spacing, origin, coordinate, and connectivity methods raise NotImplementedError — these are not defined for rank-0 scalar grids.

Caveats

get_value_ptr is a snapshot, not a live pointer. HydroRaVENS stores scalar state as Python floats rather than numpy arrays. get_value_ptr() therefore returns a fresh length-1 array each call; the array does not update automatically when the model advances. Call get_value() after each update() to retrieve current values.

Spin-up is internal. Spin-up cycles specified in the YAML config run inside initialize(); the BMI time counter starts at 0.0 after spin-up completes.

finalize() does not plot. Calling finalize() discards the internal model object but does not call finalize() on it, which would trigger an NSE print and a plot pop-up unsuitable for headless coupling runs.

Calling update() past the end of the record raises an error. The file-driven loop while bmi.get_current_time() < bmi.get_end_time() terminates correctly. Calling update() after all rows have been consumed will raise a KeyError from the internal pandas DataFrame. Guard against this in custom loops by checking get_current_time() < get_end_time() before each call.

API Reference

class hydroravens.BmiHydroRaVENS[source]

CSDMS Basic Model Interface wrapper for HydroRaVENS.

Wraps a Buckets instance so that HydroRaVENS can participate in a CSDMS-compliant coupling framework.

Two usage modes are supported:

File-driven (standard HydroRaVENS workflow) — the YAML config points to a CSV containing all forcing data; the framework steps through the record by calling update() repeatedly:

from hydroravens import BmiHydroRaVENS
import numpy as np

bmi = BmiHydroRaVENS()
bmi.initialize("config.yml")
while bmi.get_current_time() < bmi.get_end_time():
    bmi.update()
bmi.finalize()

Online coupled — an upstream model provides forcing each step via set_value() before calling update():

bmi.initialize("config.yml")
bmi.set_value(
    "atmosphere_water__liquid_equivalent_precipitation_rate",
    np.array([5.2])
)
bmi.set_value("atmosphere__temperature", np.array([3.1]))
bmi.update()
q = np.empty(1, dtype=np.float64)
bmi.get_value("land_surface_water__runoff_volume_flux", q)
print(f"Discharge: {q[0]:.3f} mm d-1")

Notes

Specific discharge: land_surface_water__runoff_volume_flux is area-normalised specific discharge in mm d⁻¹, not volumetric flux. To convert to volumetric discharge Q [m³ s⁻¹]:

Q_m3s = q_mm_d * area_km2 * 1e3 / 86400

where area_km2 = bmi._model.drainage_basin_area__km2.

get_value_ptr: HydroRaVENS stores scalar state as Python floats, not numpy arrays. get_value_ptr() therefore returns a fresh length-1 array rather than a live pointer into model memory. Values in the returned array do not update when the model advances; call get_value() after each update() call to retrieve current values.

Per-reservoir depths: subsurface_water_reservoir_0__depth through subsurface_water_reservoir_2__depth correspond to reservoirs 0, 1, 2 (shallowest to deepest) in the configuration. Depths for reservoir indices that do not exist in the current configuration are returned as np.nan.

Optional input variables: the five input Standard Names are always declared. Temperature and ET inputs will raise KeyError from set_value() if the corresponding column is absent from the loaded time series.

initialize(config_file: str) None[source]

Load a HydroRaVENS YAML configuration file and prepare the model.

Reads the configuration, loads the input CSV time series, builds the reservoir stack, and runs spin-up cycles. After this call, update() steps through the record one day at a time.

Parameters:

config_file (str) – Path to a HydroRaVENS YAML configuration file.

update() None[source]

Advance the model by one day.

If set_value() was called for any input variable before this step, those values override the CSV values for the current row.

update_until(time: float) None[source]

Advance the model until get_current_time() >= time.

Parameters:

time (float) – Target time [days since start of record].

finalize() None[source]

Release internal resources.

Discards the Buckets instance. Does not call finalize() on the inner model (which would trigger an NSE print and a plot pop-up).

get_component_name() str[source]

Name of the component.

Returns:

The name of the component.

Return type:

str

get_input_item_count() int[source]

Count of a model’s input variables.

Returns:

The number of input variables.

Return type:

int

get_output_item_count() int[source]

Count of a model’s output variables.

Returns:

The number of output variables.

Return type:

int

get_input_var_names()[source]

List of a model’s input variables.

Input variable names must be CSDMS Standard Names, also known as long variable names.

Returns:

The input variables for the model.

Return type:

list of str

Notes

Standard Names enable the CSDMS framework to determine whether an input variable in one model is equivalent to, or compatible with, an output variable in another model. This allows the framework to automatically connect components.

Standard Names do not have to be used within the model.

get_output_var_names()[source]

List of a model’s output variables.

Output variable names must be CSDMS Standard Names, also known as long variable names.

Returns:

The output variables for the model.

Return type:

list of str

get_var_grid(name: str) int[source]

Get grid identifier for the given variable.

Parameters:

name (str) – An input or output variable name, a CSDMS Standard Name.

Returns:

The grid identifier.

Return type:

int

get_var_type(name: str) str[source]

Get data type of the given variable.

Parameters:

name (str) – An input or output variable name, a CSDMS Standard Name.

Returns:

The Python variable type; e.g., str, int, float.

Return type:

str

get_var_units(name: str) str[source]

Get units of the given variable.

Standard unit names, in lower case, should be used, such as meters or seconds. Standard abbreviations, like m for meters, are also supported. For variables with compound units, each unit name is separated by a single space, with exponents other than 1 placed immediately after the name, as in m s-1 for velocity, W m-2 for an energy flux, or km2 for an area.

Parameters:

name (str) – An input or output variable name, a CSDMS Standard Name.

Returns:

The variable units.

Return type:

str

Notes

CSDMS uses the UDUNITS standard from Unidata.

get_var_itemsize(name: str) int[source]

Get memory use for each array element in bytes.

Parameters:

name (str) – An input or output variable name, a CSDMS Standard Name.

Returns:

Item size in bytes.

Return type:

int

get_var_nbytes(name: str) int[source]

Get size, in bytes, of the given variable.

Parameters:

name (str) – An input or output variable name, a CSDMS Standard Name.

Returns:

The size of the variable, counted in bytes.

Return type:

int

get_var_location(name: str) str[source]

Get the grid element type that the a given variable is defined on.

The grid topology can be composed of nodes, edges, and faces.

node

A point that has a coordinate pair or triplet: the most basic element of the topology.

edge

A line or curve bounded by two nodes.

face

A plane or surface enclosed by a set of edges. In a 2D horizontal application one may consider the word “polygon”, but in the hierarchy of elements the word “face” is most common.

Parameters:

name (str) – An input or output variable name, a CSDMS Standard Name.

Returns:

The grid location on which the variable is defined. Must be one of “node”, “edge”, or “face”.

Return type:

str

Notes

CSDMS uses the ugrid conventions to define unstructured grids.

get_start_time() float[source]

Start time of the model.

Model times should be of type float.

Returns:

The model start time.

Return type:

float

get_end_time() float[source]

End time of the model.

Returns:

The maximum model time.

Return type:

float

get_current_time() float[source]

Return the current time of the model.

Returns:

The current model time.

Return type:

float

get_time_step() float[source]

Return the current time step of the model.

The model time step should be of type float.

Returns:

The time step used in model.

Return type:

float

get_time_units() str[source]

Time units of the model.

Returns:

The model time unit; e.g., days or s.

Return type:

str

Notes

CSDMS uses the UDUNITS standard from Unidata.

get_grid_rank(grid_id: int) int[source]

Get number of dimensions of the computational grid.

Parameters:

grid (int) – A grid identifier.

Returns:

Rank of the grid.

Return type:

int

get_grid_size(grid_id: int) int[source]

Get the total number of elements in the computational grid.

Parameters:

grid (int) – A grid identifier.

Returns:

Size of the grid.

Return type:

int

get_grid_type(grid_id: int) str[source]

Get the grid type as a string.

Parameters:

grid (int) – A grid identifier.

Returns:

Type of grid as a string.

Return type:

str

get_value(name: str, dest: ndarray) ndarray[source]

Copy the current scalar value of name into dest and return it.

For input variables, returns the value at the pending row (the value that will be consumed by the next update() call). For output variables, returns the value written by the most recent update() call.

Parameters:
  • name (str) – CSDMS Standard Name.

  • dest (numpy.ndarray) – Pre-allocated length-1 array of dtype float64.

Returns:

dest, filled in place.

Return type:

numpy.ndarray

get_value_ptr(name: str) ndarray[source]

Return a length-1 float64 array containing the current value.

Returns a snapshot, not a live pointer — see class docstring.

get_value_at_indices(name: str, dest: ndarray, inds: ndarray) ndarray[source]

Get value at specific indices (scalar: only index 0 is valid).

set_value(name: str, src: ndarray) None[source]

Override an input variable for the current (next) timestep.

Writes src[0] into the hydrodata DataFrame at the pending row (_timestep_i), replacing the value read from the CSV. The written value is consumed by the next update() call. This is the online-coupling path; in file-driven mode this method need not be called.

Parameters:
  • name (str) – CSDMS Standard Name of an input variable.

  • src (numpy.ndarray) – Length-1 float64 array containing the new value.

Raises:

KeyError – If name is not a recognised input variable, or if the corresponding DataFrame column does not exist in the loaded time series.

set_value_at_indices(name: str, inds: ndarray, src: ndarray) None[source]

Set value at specific indices (scalar: only index 0 is valid).