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. Noset_value()calls are needed.- Online coupled
An upstream model provides forcing each timestep via
set_value()before callingupdate(). 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 |
|---|---|---|
|
mm d⁻¹ |
|
|
°C |
|
|
°C |
|
|
°C |
|
|
mm d⁻¹ |
|
Output variables
These variables are updated by each call to
update() and retrieved via
get_value().
CSDMS Standard Name |
Units |
Source |
|---|---|---|
|
mm d⁻¹ |
Modelled specific discharge (area-normalised) |
|
m³ s⁻¹ |
Volumetric discharge (specific discharge × catchment area) |
|
mm |
Snowpack SWE; 0.0 if no snowpack |
|
mm |
Total subsurface storage (all reservoirs) |
|
mm |
Reservoir 0 storage (shallowest) |
|
mm |
Reservoirs 1–9 storage (deeper); |
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 |
|
Time unit |
|
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
Bucketsinstance 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 callingupdate():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_fluxis 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; callget_value()after eachupdate()call to retrieve current values.Per-reservoir depths:
subsurface_water_reservoir_0__depththroughsubsurface_water_reservoir_2__depthcorrespond 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 asnp.nan.Optional input variables: the five input Standard Names are always declared. Temperature and ET inputs will raise
KeyErrorfromset_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
Bucketsinstance. Does not callfinalize()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:
- get_input_item_count() int[source]
Count of a model’s input variables.
- Returns:
The number of input variables.
- Return type:
- get_output_item_count() int[source]
Count of a model’s output variables.
- Returns:
The number of output variables.
- Return type:
- 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.
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.
- get_var_units(name: str) str[source]
Get units of the given variable.
Standard unit names, in lower case, should be used, such as
metersorseconds. Standard abbreviations, likemfor 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 inm s-1for velocity,W m-2for an energy flux, orkm2for an area.- Parameters:
name (str) – An input or output variable name, a CSDMS Standard Name.
- Returns:
The variable units.
- Return type:
Notes
CSDMS uses the UDUNITS standard from Unidata.
- 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:
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:
- get_current_time() float[source]
Return the current time of the model.
- Returns:
The current model time.
- Return type:
- 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:
- get_time_units() str[source]
Time units of the model.
- Returns:
The model time unit; e.g., days or s.
- Return type:
Notes
CSDMS uses the UDUNITS standard from Unidata.
- get_grid_size(grid_id: int) int[source]
Get the total number of elements in the computational grid.
- 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 recentupdate()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:
- 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 nextupdate()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.