Skip to content

Forcings

The river_route.forcings package holds the RunoffForcing classes, which aggregate runoff to catchments. There is one for each forcing that routes runoff, and RUNOFF_CLASS_FOR_FORCING[configs.forcing] is the class the forcing of a Configs names:

forcing Class Aggregates
catchment CatchmentRunoffForcing nothing: its files are already aggregated to catchments, as volumes or depths
grid GridRunoff grids with x and y dimensions, with a weight table
ecmwf_grib ECMWFGribReducedGrid ECMWF GRIB files on a reduced gaussian grid, read with eccodes, with a weight table

Gridded runoff is aggregated with a grid weight table built with the functions in river_route.forcings.weights. Build a GridRunoff with GridRunoff(grid_weights_file, ...), or with GridRunoff.from_configs(configs) to read those same values off a Configs, which is how a Router builds one. Only from_configs validates, since a directly built GridRunoff has no Configs to check.

Every class subclasses the abstract RunoffForcing, whose to_netcdf writes a catchment runoff array in the format CatchmentRunoffForcing reads. The grid classes precompute that file from their grids with aggregate_to_file, so it can be routed later with forcing catchment. Routing the grids directly is faster, because the aggregation then happens inside the routing kernel, except for cumulative or clipped runoff, which is aggregated before it is routed.

Each class has a generator method that yields one (dates, runoff, source_file) tuple per input, with the runoff in the form routing reads: CatchmentRunoffVolumes, or a GridCellRunoff whose grid cells routing reads as it routes each river. It can be used on its own, and a Router builds the one its forcing names. See Customizing Runoff Inputs.

import river_route as rr

configs = rr.Configs.from_json('config.json')
runoff = rr.GridRunoff.from_configs(configs)
for dates, catchment_runoff, forcing_file in runoff.catchment_reader(configs.forcing_files):
    ...  # each file's catchment runoff volumes, a (river, time) array overwritten by the next file

river_route.forcings.RunoffForcing

Bases: ABC

Base class for the sources of catchment runoff, the runoff volume each catchment adds to its river in each step. Each subclass is built from a Configs with from_configs and provides a generator that yields its runoff in the form routing reads, and every subclass writes catchment runoff to disk with to_netcdf in the one format CatchmentRunoffForcing reads.

from_configs(configs) abstractmethod classmethod

Build this RunoffForcing from the options on a Configs, as a Router does.

generator(forcing_files) abstractmethod

Yield one (dates, runoff, source_file) tuple per input, where runoff is what routing reads: its CatchmentRunoffVolumes, or a GridCellRunoff whose grid cells routing reads as it routes. Each checks its arrays with check.

distribute(network)

Match the runoff to a stabilized network, whose synthetic sub-reaches each need a share of their parent river's runoff. Classes that can split their runoff override this; the others raise when the network has rows they have no runoff for.

to_netcdf(path, dates, catchment_runoff, river_ids, catchment_area, *, as_volumes) staticmethod

Write a catchment runoff array to a netCDF file that CatchmentRunoffForcing reads, routed as forcing_files with forcing catchment.

Parameters:

Name Type Description Default
path PathInput

netCDF file to write

required
dates DatetimeArray

(time,) datetime64 values of the steps

required
catchment_runoff FloatArray

(n_rivers, time) depths (m) or volumes (m³), rows ordered like river_ids

required
river_ids IntArray

(n_rivers,) river id of each row

required
catchment_area FloatArray

(n_rivers,) area of each catchment in m², the factor between depths and volumes

required
as_volumes bool

whether catchment_runoff holds volumes (m³) rather than depths (m), which sets the units written. It is given with the array rather than read from a RunoffForcing's options, since catchment_reader yields volumes whatever as_volumes option a grid runoff was built with.

required

river_route.forcings.BaseGridRunoffForcing dataclass

Bases: RunoffForcing

Prepares catchment runoff for routing from gridded runoff depths and a grid weight table. Each subclass names the weight table columns that locate a grid cell and the runoff dimension each one indexes, with cell_dimensions.

Only grid_weights_file is required. Every option is named the same here as it is on Configs, so from_configs reads each one off a Configs by its own name, which is how a Router builds one.

Reading and indexing the weight table is the same work for every forcing file, so it is done once when the runoff is created and reused for all of them. Each forcing file is read at the grid cells the table touches. Routing reads each river's cells directly as it routes the river, and aggregate sums them onto rivers as area weighted depths or volumes one river at a time, each river's series written straight into its row of a C-order (river, time) array.

cell_dimensions abstractmethod property

Each weight table column that locates a grid cell, and the runoff dimension it indexes.

cumulative abstractmethod property

Whether the runoff values are totals accumulated from the start of each file, differenced into the runoff of each step, rather than the runoff of each step.

from_configs(configs) classmethod

Build this grid runoff from the runoff options on a Configs. The configs are validated for runoff before the weight table is read. Each option is read off the Configs by the field's own name, so a field with no matching option raises AttributeError rather than silently keeping its default.

distribute(network)

Match the weight table to a stabilized network: each river's runoff volume is split between every row with it as parentRiverId, which is the river and the synthetic sub-reaches injected upstream of it, in proportion to their travel times, the share of the river each one is. Does nothing when the table already has a row per network row.

catchment_runoff(runoff_data, thread_pool=None, threads=1, *, as_volumes=None)

Read and aggregate runoff into one reused output buffer, so repeated calls allocate no new output. Reading the runoff still allocates its arrays on every call.

Parameters:

Name Type Description Default
runoff_data PathInput | list[PathInput]

path(s) to gridded forcing files

required
thread_pool Executor | None

optional thread pool to aggregate on, used as given and never shut down here

None
threads int

number of river ranges to aggregate concurrently when thread_pool is given

1
as_volumes bool | None

volumes (m³) rather than depths (m); None follows the as_volumes option

None

Returns:

Name Type Description
tuple tuple[FloatArray, DatetimeArray]

(catchment runoff as a C-order (n_rivers, time) array, its time values). The array is a view of the reused buffer and is overwritten by the next call.

catchment_reader(forcing_files, thread_pool=None, threads=1)

Aggregate gridded forcing files into catchment runoff volumes, one file at a time, with the weight table this instance already holds. Routing does not use this: generator hands routing the grid cells to aggregate as it routes.

Every file is aggregated into one reused C-order buffer: no per-file allocation of the output or copy of it. The yielded array is overwritten by the next file, so a consumer that needs to keep it must copy it.

Parameters:

Name Type Description Default
forcing_files PathList

gridded forcing files to aggregate

required
thread_pool Executor | None

optional thread pool to aggregate on, used as given and never shut down here

None
threads int

number of river ranges to aggregate concurrently when thread_pool is given

1

Yields:

Name Type Description
tuple tuple[DatetimeArray, FloatArray, PathInput]

(dates, catchment_runoff, source_file) per input file

generator(forcing_files)

Read gridded forcing files for routing, which reads each river's grid cells as it routes the river, so no catchment runoff array is built. A file whose catchment runoff must be de-accumulated or clipped at zero needs each river's whole catchment runoff series first, so it is aggregated here and yielded as catchment runoff instead.

Parameters:

Name Type Description Default
forcing_files PathList

gridded forcing files to read

required

Yields:

Name Type Description
tuple RunoffGenerator

(dates, runoff, source_file) per input file, where runoff is a GridCellRunoff, or CatchmentRunoffVolumes when the file was aggregated here

to_dataset(runoff_data, thread_pool=None, threads=1)

Read and aggregate runoff into a catchment runoff dataset that can be saved and routed as forcing_files with forcing catchment.

Parameters:

Name Type Description Default
runoff_data PathInput | list[PathInput]

path(s) to gridded forcing files

required
thread_pool Executor | None

optional thread pool to aggregate on, used as given and never shut down here

None
threads int

number of river ranges to aggregate concurrently when thread_pool is given

1

Returns:

Type Description
Dataset

xr.Dataset: catchment runoff with dimensions (riverId, time). Contains catchment_runoff in meters or m³ and catchment_area in m².

aggregate_to_file(runoff_data, path, thread_pool=None, threads=1)

Precompute the catchment runoff of gridded runoff and write it to a netCDF file that is routed as forcing_files with forcing catchment. Routing the grids directly aggregates inside the routing kernel instead, which is faster than routing a precomputed file.

Parameters:

Name Type Description Default
runoff_data PathInput | list[PathInput]

path(s) to gridded forcing files

required
path PathInput

netCDF file to write

required
thread_pool Executor | None

optional thread pool to aggregate on, used as given and never shut down here

None
threads int

number of river ranges to aggregate concurrently when thread_pool is given

1

read_runoff(runoff_data)

Read runoff at the grid cells the weight table touches.

Parameters:

Name Type Description Default
runoff_data PathInput | list[PathInput]

path(s) to gridded forcing files

required

Returns:

Name Type Description
tuple tuple[FloatArray, DatetimeArray, float]

(runoff as a C-order (time, n_cells) float32 array, time values, factor converting the depth unit to meters)

aggregate(runoff, conversion_factor=1, out=None, thread_pool=None, threads=1, *, as_volumes=None)

Aggregate gridded runoff depths onto rivers as area weighted depths or volumes, one river at a time.

Parameters:

Name Type Description Default
runoff FloatArray

(time, n_cells) runoff depths at the weight table's cells, as returned by read_runoff

required
conversion_factor float

multiplier converting the runoff depth unit to meters

1
out FloatArray | None

optional flat buffer of at least n_rivers * time values, used as the output

None
thread_pool Executor | None

optional thread pool, used as given and never shut down here. The rivers are split into threads ranges of similar work that the same kernel aggregates concurrently. Without a pool one range covers every river.

None
threads int

number of river ranges to aggregate concurrently when thread_pool is given

1
as_volumes bool | None

volumes (m³) rather than depths (m); None follows the as_volumes option

None

Returns:

Name Type Description
FloatArray FloatArray

catchment runoff as a C-order (n_rivers, time) array, a view of the start of out when it is given

GridRunoff dataclass

Bases: BaseGridRunoffForcing

Runoff on a grid with x and y dimensions, such as a regular latitude and longitude grid, whose weight table locates each cell with x_index and y_index.

cell_dimensions property

The weight table columns x_index and y_index, and the runoff dimensions each indexes.

cumulative property

Whether the runoff values are totals accumulated from the start of each file, as accumulation_type says.

CatchmentRunoffForcing

Bases: RunoffForcing

Reads runoff already aggregated to catchments and yields each as CatchmentRunoffVolumes (bases.py), whose rows routing reads in place, a cell per river. It defines no form of runoff of its own. A degenerate Runoff aggregator class which only allows for iterating over files already converted to catchment level volumes or depths.

generator(forcing_files)

Read catchment runoff straight from netCDF files, one at a time.

Parameters:

Name Type Description Default
forcing_files PathList

netCDF files each holding catchment_runoff with dimensions (riverId, time) and, when it holds depths, catchment_area with dimension riverId

required

Yields:

Name Type Description
tuple RunoffGenerator

(dates, catchment_runoff, source_file) per input file, the runoff as CatchmentRunoffVolumes: C-order (n_rivers, time) volumes (m³) with NaN replaced by zero

from_configs(configs) classmethod

Build a CatchmentRunoffForcing. The catchment runoff file schema is fixed, so no option of the Configs is read.

ECMWFGribReducedGrid dataclass

Bases: BaseGridRunoffForcing

ECMWFGribReducedGrid reads runoff depths from ECMWF GRIB files on a reduced gaussian grid with eccodes, the forcing ecmwf_grib. It is a BaseGridRunoffForcing (bases.py) that overrides only read_runoff, so it yields the same GridCellRunoff, or CatchmentRunoffVolumes when a file must be aggregated first, routed with the overloads registered in bases.py. It defines no form of runoff or overload of its own. ReducedGaussianGrid is the grid of those files, read from their metadata, on which reduced_grid_weights (weights.py) builds a weight table.

Runoff from ECMWF GRIB files on a reduced gaussian grid, such as the octahedral O1280 grid of the IFS, with a weight table made by reduced_grid_weights that locates each cell by its cell_index, its position in the values of a GRIB message. This reader is specialized to that format; runoff in any other form must be prepared as catchment or grid forcing.

GRIB files are read with eccodes one message at a time: each message is decoded whole and only the weight table's cells are kept. At the 18,721 cells of the Columbia River basin in a 145 step O1280 forecast that took 0.72 s with memory for one message, where xarray and cfgrib took 5.8 s and built the whole (step, cell) array in 4 GB.

Every message whose shortName is var_runoff is read as one time step, in the order of the files and of their messages, at its validity date and time. Choosing files whose grid, ensemble member, and steps suit the weight table and the routing is left to the caller. A GRIB message has no named dimensions, so var_t is not read: each cell is taken by its position in the message's values. IFS forecast runoff accumulates from the start of the forecast, so it is always differenced into the runoff of each step and has no accumulation_type option.

cumulative property

Always: IFS forecast runoff is the total accumulated from the start of the forecast.

cell_dimensions property

The weight table column cell_index, the position of each cell in the values of a GRIB message. A message has no named dimensions, so the column names itself: read_runoff takes each cell by its position.

read_runoff(runoff_data)

Read runoff at the grid cells the weight table touches from GRIB files, in the order of their messages.

Parameters:

Name Type Description Default
runoff_data PathInput | list[PathInput]

path(s) to GRIB files

required

Returns:

Name Type Description
tuple tuple[FloatArray, DatetimeArray, float]

(runoff as a C-order (time, n_cells) array, validity times, factor converting the depth unit to meters)

ReducedGaussianGrid

Bases: NamedTuple

A global reduced gaussian grid: 2N rows of cells at the gaussian latitudes, north to south, each row's cells evenly spaced in longitude eastward from 0 degrees. Cells are numbered row after row, the order of the values in a GRIB message.

n_cells property

The number of cells of the whole grid, the number of values in a GRIB message on it.

spacing_km property

Side of a square with the mean cell area, the resolution ECMWF quotes, e.g. 9 km for O1280.

from_grib(grib_path) classmethod

Read the grid from the metadata of the first message in a GRIB file.

cell_polygons(bounds=None)

The area each cell represents, as a box of longitude and latitude in EPSG:4326 with longitudes from -180 to 180: halfway in longitude to the cells beside it, and in latitude between the edges of its row. The edges split the sphere into bands whose areas are the gaussian quadrature weights of the rows, so each cell covers the share of the sphere the grid's own quadrature weights its value by.

Parameters:

Name Type Description Default
bounds tuple[float, float, float, float] | None

optional (min x, min y, max x, max y) in degrees; only the cells overlapping it are made. Without it every cell of the world is made.

None

Returns:

Type Description
GeoDataFrame

gpd.GeoDataFrame: one row per cell, in cell order: cell_index (position in a GRIB message's values), x and y (the cell center longitude and latitude), and geometry

river_route.forcings.CatchmentRunoffVolumes

Bases: NamedTuple

Each river's catchment runoff volume (m³) in every runoff step, as a C-order (river, time) array whose rows routing reads in place.

check(river_ids, n_steps)

Raise ValueError unless it holds n_steps steps for river_ids in that order, each river's row contiguous.

cells_and_weights()

The runoff as the routing kernels read it: a cell per river, its own volumes, each weighted by one.

river_route.forcings.GridCellRunoff

Bases: NamedTuple

One file of gridded runoff depths read at the weight table's grid cells, with the weights that turn them into each river's catchment runoff volume as routing routes it.

check(river_ids, n_steps)

Raise ValueError unless the weight table describes river_ids in that order and the runoff has n_steps steps.

cells_and_weights()

The runoff as the routing kernels read it: the depths of the cells, and each river's weights of them.

river_route.forcings.weights

cell_xy_from_regular_grid(dataset, var_x='lon', var_y='lat')

The cell center x and y coordinates of a regular grid.

Parameters:

Name Type Description Default
dataset PathInput

netCDF, or any file xarray opens, holding the grid's 1D x and y coordinate variables

required
var_x str

name of the x coordinate variable

'lon'
var_y str

name of the y coordinate variable

'lat'

Returns:

Name Type Description
tuple tuple[ndarray, ndarray]

(x, y), the 1D coordinate arrays

Raises:

Type Description
KeyError

if either variable is missing

ValueError

if either coordinate is not 1D

voronoi_diagram_from_regular_xy(x, y, crs=4326)

The Voronoi polygon around the center of each cell of a regular grid, with the cell's center and its position in the x and y coordinates.

Parameters:

Name Type Description Default
x ndarray

(n_x,) cell center x coordinates

required
y ndarray

(n_y,) cell center y coordinates

required
crs int

EPSG code of the coordinates

4326

Returns:

Type Description
GeoDataFrame

gpd.GeoDataFrame: one polygon per cell with columns x, y, x_index, and y_index, sorted by x and y

Raises:

Type Description
ValueError

if x or y is not a non-empty 1D array

compute_voronoi_catchment_intersects(voronoi_gdf, catchments_gdf, save_path=None, attributes=None)

The weight table of the intersections between grid cell Voronoi polygons and catchments: the area of each catchment inside each cell and that area's proportion of the catchment.

Parameters:

Name Type Description Default
voronoi_gdf GeoDataFrame

cell polygons with columns x_index, y_index, x, and y, as from voronoi_diagram_from_regular_xy

required
catchments_gdf GeoDataFrame

catchment polygons with a riverId column

required
save_path PathInput | None

optional netCDF file to save the weight table to

None
attributes dict | None

optional attributes added to the saved file

None

Returns:

Type Description
DataFrame

pd.DataFrame: columns riverId, x_index, y_index, x, y, area_sqm, area_sqm_total, and proportion

Raises:

Type Description
KeyError

if a required column is missing

grid_weights(grid_path, catchments_path, *, var_x='lon', var_y='lat', crs=4326, save_voronoi_path=None, save_weights_path=None, network_path=None)

Compute the grid weights for a given grid and catchments.

Parameters:

Name Type Description Default
grid_path PathInput

path to a NetCDF file containing the grid's 1D x and y coordinate variables

required
catchments_path PathInput

path to a GeoParquet file containing the catchment geometries and a riverId column

required
var_x str

x-coordinate variable name in the grid file

'lon'
var_y str

y-coordinate variable name in the grid file

'lat'
crs int

EPSG code for the grid coordinate reference system (default: 4326)

4326
save_voronoi_path PathInput | None

optional path to save the Voronoi polygons as a GeoParquet file

None
save_weights_path PathInput | None

optional path to save the grid weights as a NetCDF file

None
network_path PathInput | None

optional path to a network file whose riverId column order is used to topologically sort the weight table rows. When omitted the row order is spatial (not topological).

None

Returns:

Type Description
DataFrame

pd.DataFrame: a DataFrame containing the grid weights with columns ['riverId', 'x_index', 'y_index', 'x', 'y', 'area_sqm', 'area_sqm_total', 'proportion']. The saved file omits area_sqm_total.

reduced_grid_weights(grib_path, catchments_path, *, save_weights_path=None, network_path=None)

Compute the grid weights of the cells of a reduced gaussian grid GRIB file, read by ECMWFGribReducedGrid: the area of each catchment inside each cell polygon of ReducedGaussianGrid.cell_polygons that overlaps it.

Every part of a cell polygon is a rectangle of longitude and latitude, and stays a rectangle in the cylindrical equal-area projection, so the catchments are projected to it once and cut to each cell with the rectangle clipping of GEOS, whose pieces are measured where they are. A general polygon overlay followed by projecting every piece took 40 s for the 58,358 catchments of the Columbia (39.9 million vertices) on O1280, and this takes 6 s.

Parameters:

Name Type Description Default
grib_path PathInput

GRIB file whose first message's grid is used

required
catchments_path PathInput

GeoParquet file of the catchment polygons with a riverId column, in any CRS

required
save_weights_path PathInput | None

optional path to save the grid weights as a netCDF file

None
network_path PathInput | None

optional path to a network file whose riverId column order is used to topologically sort the weight table rows. When omitted the row order is spatial (not topological).

None

Returns:

Type Description
DataFrame

pd.DataFrame: the grid weights with columns [riverId, cell_index, x, y, area_sqm, area_sqm_total, proportion]