The Config File
Configuration File
river-route computations are controlled by a Configs object, built from keyword arguments or read from a
JSON file with Configs.from_json.
All routing runs through Router, which routes Muskingum with static coefficients: each river's k and x are fixed for
the whole run. The procedure it runs is set by the forcing selector and the compute options (dt_routing,
use_subreaches, use_subcycles), and the required config keys depend on which selections you make.
Router takes only a Configs. Network and the RunoffForcing classes take the options they need as ordinary arguments
and each has a from_configs classmethod that reads those same values off a Configs, which is how Router builds
them. examples/config.json below lists every option.
Routing procedure selector
forcing-'channel'(channel routing only, no inflows), or the form of theforcing_fileswhose runoff enters the rivers in addition to routing:'catchment'(already aggregated to catchments, read byCatchmentRunoffForcing),'grid'(a grid with x and y dimensions, read byGridRunoff), or'ecmwf_grib'(ECMWF GRIB files on a reduced gaussian grid, read byECMWFGribReducedGrid). A single value. Default'channel'. Each step's catchment runoff enters its river at a constant rate over the step.
Each river's coefficients are built from the muskingumK and muskingumX columns of the network file.
Compute options
dt_routing, use_subreaches, and use_subcycles settle how each river computes, in two jobs: the first sets each
river's step, and the other two make every coefficient of each river non-negative at it, the condition for valid
routing.
dt_routing- the step each river computes in.'auto'(the default) gives each river its own valid step: the runoff step over the fewest subcycles, equal steps of its own, that keep its coefficients non-negative. A step in seconds, a divisor of the runoff step, is instead the step of every river. Setting it to the runoff step routes each river in one step of each runoff step.use_subreaches-True(the default) splits each river too long for its step into the fewest sub-reaches in series whose coefficients are all non-negative, which keep the river's mean travel time and its diffusion. Withuse_subcyclestoo, a river that no count of equal sub-reaches makes valid is split into the fewest sub-reaches of unequal travel times that each take subcycles of their own.Falseroutes such a river whole at its step, and reports its negative coefficient;dt_routing: 'auto'requires it. Sub-reaches multiply the routing work by the average number of sub-reaches per river, and a river with an x near 0.5 can need very many, so with eitheruse_subreachesoruse_subcyclesa network with any river of x above 0.49 is refused; see the work of sub-reaches for why, and for holding x lower. The state files then hold one value per sub-reach, each river's sub-reaches upstream to downstream, in the order a final state file is written, with each row'sriverIdrepeated for every sub-reach of its river. A state file with one value per river is refused, and so is a run whose sub-reaches change between its forcing files of different time steps.use_subcycles-True(the default) routes each river too short for its step in subcycles of it, the fewest whose coefficients are all non-negative. A river routed in subcycles interpolates its upstream inflow linearly within each runoff step; see the time options. The subcycles of each sub-reach are combined into one step, so they add no routing work.Falseroutes such a river whole at its step, and reports its negative coefficient.dt_routing: 'auto'requires it, since'auto'gives each river its own step as subcycles of the runoff step.
Minimum Required Inputs
Every routing procedure requires the following 2 configuration options:
network_file- path to the network file (parquet)- One of two options for specifying where the routed discharge output is written
discharge_dir- a string path to a directory where outputs are saved based on the names of the inputs. Each output is nameddischarge_<input name>.zarr, ordischarge.zarrfor channel routing, the zarr store that the default writer writes. Givedischarge_filesto name outputs for another writer.discharge_files- list of explicit paths for each output file, one per input file required.
Required config keys by selection
Beyond the always-required keys above, additional keys are required depending on your selector choices.
forcing: channel (channel routing only, no inflows) also requires:
channel_state_init_file- parquet state file to initialize dischargedt_discharge, ordt_routingin seconds - the timestep at which rivers hand their discharge downstreamdt_total- total simulation duration in seconds
forcing: catchment, grid, or ecmwf_grib also requires a water input source:
forcing_filesgrid_weights_filewhenforcingisgridorecmwf_grib. It must not be set forcatchment.
Every step of each forcing file is routed and written, and the runoff time step is read from the forcing files, so
dt_total and dt_discharge must not be set; see the time options. start_datetime is only
read by channel routing, which has no input dates to copy.
Every network file has the columns riverId, nextRiverId, muskingumK, muskingumX, riverIndex, and
upstreamCount; see the File Schemas reference.
The following table lists where each remaining key applies.
| Config key | Description | Required when |
|---|---|---|
| core | ||
network_file |
Network file parquet. | always |
| compute | ||
dt_routing |
'auto', or every river's step |
optional - time docs |
use_subreaches |
Split rivers too long | optional, required by dt_routing: auto |
use_subcycles |
Subcycle rivers too short | optional, required by dt_routing: auto |
| state | ||
channel_state_init_file |
Path to initial channel state | forcing: channel (else optional, default 0) |
channel_state_final_file |
Path to save final channel state | optional |
| output | ||
discharge_dir |
Directory for output files | Option 1 |
discharge_files |
Explicit output paths | Option 2 |
| time | ||
start_datetime |
Channel routing start date | optional, forcing: channel only |
dt_discharge |
Channel output timestep | forcing: channel without dt_routing in seconds |
dt_total |
Channel simulation duration | forcing: channel, refused otherwise |
| input data | ||
forcing_files |
Runoff read as the forcing |
forcing not channel |
grid_weights_file |
Aggregates grids to catchments | forcing a grid |
Optional configs with defaults
The var_ options name the variables of the gridded forcing files that are read. Every other file, read or written,
uses fixed names, such as riverId for the river id.
| Config Key | Description | Default |
|---|---|---|
forcing |
Channel routing, or the form of the forcing files | 'channel' |
dt_routing |
'auto' each river's own valid step, or seconds |
'auto' |
use_subreaches |
Sub-reaches for rivers too long for their step | True |
use_subcycles |
Subcycles for rivers too short for their step | True |
invalid_coefficients |
'raise', 'log', or 'ignore' negative coefficients |
'raise' |
var_runoff |
Runoff variable name in grid forcing_files |
'ro' |
var_x |
X-dimension name in grid forcing files |
'x' |
var_y |
Y-dimension name in grid forcing files |
'y' |
var_t |
Time dimension name in depth grids | 'time' |
accumulation_type |
Is grid runoff 'incremental' or 'cumulative' |
'incremental' |
runoff_processing_mode |
Are runoff 'sequential' or 'ensemble' inputs |
'sequential' |
force_positive_runoff |
Clip negative grid runoff depths to zero | False |
runoff_depth_unit |
Unit of grid runoff depths, else read from the file | None |
as_volumes |
GridRunoff prepares volumes instead of depths |
False |
progress_bar |
Show tqdm progress bar | True |
log_level |
Logger level, defaults to between INFO and WARNING | 'PROGRESS' |
log |
Enable or disable logging | True |
log_stream |
'stdout' or a file path |
'stdout' |
log_format |
Python logging format string | '%(levelname)s - %(asctime)s - %(message)s' |
Validation
Router.route() validates the configs with Configs.validate_routing before it computes anything, and
GridRunoff.from_configs validates them with Configs.validate_runoff before it reads the weight table. Configs are
frozen, so once they pass they are not checked again. Passing one of the two does not pass the other. A GridRunoff
built directly, without a Configs, has nothing to validate and so runs neither. validate_routing refuses runoff
options the forcing does not read unless they are left at their defaults: catchment reads none of them, since its
files have a fixed schema, and ecmwf_grib reads no accumulation_type, since its runoff is always cumulative.
Configs.deep_validate() reads the network file, grid weights, and initial state and checks their columns,
types, and value ranges, that the network is topologically sorted, and that the weight table proportions sum to
1 per river. Nothing calls it for you, because reading those files is the same work routing is about to do. Run it
once on inputs you have not checked before:
rr.Configs.from_json('config.json').deep_validate()
invalid_coefficients controls what happens when a river has a negative Muskingum coefficient at its own
step. Every coefficient is non-negative, and the routing valid, when 2*k*x <= step <= 2*k*(1-x). Outside
that window routing is still stable and still conserves mass, but the river's discharge can dip below zero ahead of a rise (c1 < 0, a river
too long for the step) or alternate from step to step and go negative (c3 < 0, a river too short for it). Negative
discharge is written as routed, never clamped, so the written discharge conserves mass and shows the river that
produced it. A negative coefficient is an error in the routing of its river, so the default 'raise' refuses to
route, saying how many rivers are affected; 'log' logs that error and routes on; 'ignore' routes on silently.
Routing checks
the coefficients with Network.check_validity before it starts, and Network.check_routable refuses, before any
runoff is read, a network the kernels cannot route, such as one with a river split into more than 256 sub-reaches.
Use Network.invalid_mask(dt) to inspect a network before routing it. dt_routing: auto gives each river the
Network.subcycles(dt_runoff) steps of its own that it needs, and use_subreaches routes each river too long for its
step in Network.sub_reaches(dt) sub-reaches inside the kernel, which keep the river's mean travel time and its
travel time variance k**2 * (1 - 2x):
equal sub-reaches with x = 1/2 - N*(1/2 - x) wherever that is not negative, and otherwise, since Muskingum x is
never negative, sub-reaches with x = 0 whose unequal travel times keep the variance. At x = 0.2 two sub-reaches of
0.724 and 0.276 of k make every river too long for any step non-negative. Each sub-reach takes its share of the
river's runoff. Each river too short for a step given in seconds, which sub-reaches cannot fix, is routed in
Network.subcycles(dt) steps of its own. With both, a river neither makes valid, which only a river with x above 1/3
can be, is split into the fewest sub-reaches of unequal travel times that each take subcycles of their own and keep
the river's diffusion. Network.conditioning(dt) gives the Layout of every river: its sub-reaches, and the travel
time, x, and subcycles of each. Network.stabilize(dt) instead rewrites the network in place with those sub-reaches as
rows of their own, which route validly with use_subcycles, and Network.write_stabilized(dt) saves that network as a
network table. Such a network routes with the channel,
grid, and ecmwf_grib forcings, whose runoff is shared among the sub-reaches of each river; catchment forcing
refuses it, since a catchment runoff file has one row per river.
Example Configuration
The general template lists every key.
{
"network_file": "",
"forcing": "channel",
"dt_routing": "auto",
"use_subreaches": true,
"use_subcycles": true,
"invalid_coefficients": "raise",
"channel_state_init_file": "",
"channel_state_final_file": "",
"discharge_dir": "",
"discharge_files": [],
"start_datetime": "1970-01-01",
"dt_discharge": 0,
"dt_total": 0,
"forcing_files": [],
"grid_weights_file": "",
"var_runoff": "ro",
"var_x": "x",
"var_y": "y",
"var_t": "time",
"accumulation_type": "incremental",
"runoff_processing_mode": "sequential",
"force_positive_runoff": false,
"runoff_depth_unit": "",
"as_volumes": false,
"progress_bar": true,
"log_level": "PROGRESS",
"log": true,
"log_stream": "stdout",
"log_format": "%(levelname)s - %(asctime)s - %(message)s"
}