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. .. contents:: On this page :local: :depth: 2 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 :meth:`~hydroravens.BmiHydroRaVENS.update` repeatedly to step through the record. No :meth:`~hydroravens.BmiHydroRaVENS.set_value` calls are needed. **Online coupled** An upstream model provides forcing each timestep via :meth:`~hydroravens.BmiHydroRaVENS.set_value` before calling :meth:`~hydroravens.BmiHydroRaVENS.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: .. code-block:: bash pip install 'hydroRaVENS[bmi]' Usage ----- File-driven ~~~~~~~~~~~ .. code-block:: python from hydroravens import BmiHydroRaVENS bmi = BmiHydroRaVENS() bmi.initialize("config.yml") while bmi.get_current_time() < bmi.get_end_time(): bmi.update() bmi.finalize() Use :meth:`~hydroravens.BmiHydroRaVENS.update_until` to advance to a specific time without writing the loop yourself:: bmi.update_until(365.0) # advance one year Online coupled ~~~~~~~~~~~~~~ .. code-block:: python 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⁻¹]: .. code-block:: python 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 :meth:`~hydroravens.BmiHydroRaVENS.set_value` before each :meth:`~hydroravens.BmiHydroRaVENS.update` to override them. Temperature and ET inputs are declared even when those columns are absent from the CSV. Calling :meth:`~hydroravens.BmiHydroRaVENS.set_value` for an absent column raises :exc:`KeyError`. .. list-table:: :widths: 55 15 30 :header-rows: 1 * - 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 :meth:`~hydroravens.BmiHydroRaVENS.update` and retrieved via :meth:`~hydroravens.BmiHydroRaVENS.get_value`. .. list-table:: :widths: 55 15 30 :header-rows: 1 * - 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__depth`` … ``subsurface_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, :meth:`~hydroravens.BmiHydroRaVENS.initialize` will raise a :exc:`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 ------------- .. list-table:: :widths: 40 60 :header-rows: 0 * - 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 :exc:`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. :meth:`~hydroravens.BmiHydroRaVENS.get_value_ptr` therefore returns a fresh length-1 array each call; the array does not update automatically when the model advances. Call :meth:`~hydroravens.BmiHydroRaVENS.get_value` after each :meth:`~hydroravens.BmiHydroRaVENS.update` to retrieve current values. **Spin-up is internal.** Spin-up cycles specified in the YAML config run inside :meth:`~hydroravens.BmiHydroRaVENS.initialize`; the BMI time counter starts at 0.0 after spin-up completes. **finalize() does not plot.** Calling :meth:`~hydroravens.BmiHydroRaVENS.finalize` discards the internal model object but does not call :meth:`~hydroravens.Buckets.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 :meth:`~hydroravens.BmiHydroRaVENS.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 ------------- .. autoclass:: hydroravens.BmiHydroRaVENS :members: initialize, update, update_until, finalize, get_component_name, get_input_item_count, get_output_item_count, get_input_var_names, get_output_var_names, get_var_grid, get_var_type, get_var_units, get_var_itemsize, get_var_nbytes, get_var_location, get_start_time, get_end_time, get_current_time, get_time_step, get_time_units, get_grid_rank, get_grid_size, get_grid_type, get_value, get_value_ptr, get_value_at_indices, set_value, set_value_at_indices :member-order: bysource