Skip to content

Basics

Overview

river-route routes catchment-scale runoff through a vector river network. All routing runs through the Router class, and the kind of routing it performs is chosen with the forcing config selector:

  • forcing: channel: pure channel routing with no runoff entering the rivers. Routes an existing discharge state forward in time using only Muskingum channel equations. Requires an explicit initial state.
  • forcing: catchment, grid, or ecmwf_grib: routes the runoff volumes or depths of forcing_files, in that form, directly into river channel inlets at each timestep. This is the most common starting point.

This tutorial routes catchment runoff (forcing: catchment).

Vocabulary

  • Region: a named group of catchments and channels forming a complete routing domain.
  • Catchment: a subunit of a watershed. Water enters at one upstream location and exits at exactly one outlet.
  • Depth first search (DFS) order: rivers sorted so that each river comes after every river upstream of it and the rivers upstream of a river are the rows immediately before it, so every river's upstream watershed is one contiguous range of rows ending at that river. The network file must be in DFS order.

Required Files

Three files are needed for a routing run:

  1. Network file (network.parquet) — river network topology and Muskingum parameters.
  2. Catchment runoff (catchment_runoff.nc) — per-catchment runoff time series.
  3. Routed discharge (discharge.zarr) — output path where results will be written.

See the File Schemas reference for field names and formats.

Network File

The network file must contain at minimum these columns:

Column Description
riverId Unique integer ID for each river segment
nextRiverId ID of the downstream segment (-1 at outlets)
muskingumK Muskingum K — travel time (seconds); typically channel length / wave speed
muskingumX Muskingum X — attenuation factor (0 ≤ x ≤ 0.5)
riverIndex Position of the river in DFS order
upstreamCount Number of rivers upstream of the river, not counting itself

Rows must be in DFS order, which riverIndex numbers: a river's upstream watershed is the rows from its riverIndex minus its upstreamCount to its riverIndex. Every file with one entry per river, such as the catchment runoff and channel state files, lists the rivers in this same order, and routing refuses one that does not. See the File Schemas reference for how to check a table.

Config File

Config values are held by a frozen Configs object. Build it from keyword arguments or read it from a JSON file with Configs.from_json, then pass it to Router. A Router takes its options from a Configs and nowhere else, and a Configs is set once when it is built, so an option is changed by building the Configs you want.

{
  "network_file": "/path/to/network.parquet",
  "forcing": "catchment",
  "forcing_files": "/path/to/catchment_runoff.nc",
  "discharge_dir": "/path/to/output/"
}

First Routing Run

import river_route as rr

configs = rr.Configs.from_json('config.json')
rr.Router(configs).route()

Or build the configs directly without a config file:

import river_route as rr

configs = rr.Configs(
    network_file='network.parquet',
    forcing_files=['catchment_runoff.nc', ],
    discharge_dir='./output/',
    forcing='catchment',
)
rr.Router(configs).route()

Or route a config file from the command line with rr route config.json. --threads 4 routes on 4 threads, and rr route - reads the config as JSON from stdin. rr app opens a window that sets up, saves, and runs configs.

A Configs is set once, when it is built, and is frozen afterward. There is no method to copy one with an option changed: build the Configs you want. Use configs.to_json(path) to write the options to a file that Configs.from_json reads back, e.g. to prepare many jobs for a scheduler.

Warm-Starting Channel State

By default, the channel starts at zero discharge. Provide a state file to initialize from a previous run:

{
  "network_file": "network.parquet",
  "forcing": "catchment",
  "forcing_files": "catchment_runoff.nc",
  "discharge_dir": "output/",
  "channel_state_init_file": "state.parquet",
  "channel_state_final_file": "new_state.parquet"
}

The state file is a parquet with the columns riverId and Q, listing the rivers of the network file in the same order. It has one row per sub-reach: one per river, except that each river use_subreaches splits into sub-reaches at the routing step has its riverId repeated for every one of its sub-reaches. A final state file is written this way, so it can start the next run routed at the same step. A state file with one row per river starts a run only when no river is split at its step.

Reading the Output

The routed discharge output is a zarr store with dimensions riverId and time, named for its forcing file:

import xarray as xr

river_of_interest = 123456789
ds = xr.open_zarr('output/discharge_catchment_runoff.zarr')
series = ds['Q'].sel(riverId=river_of_interest).to_pandas()

# Save to CSV
series.to_csv('hydrograph.csv')

# Plot
series.plot()