Network model

Network model of basins, reaches, reservoirs and junctions.

An element’s type fixes which processes it computes (ELEMENT_PROCESSES), and the model picks one method per process (METHOD_CLASSES).

class pyrunoff.utils.model.ElementType(*values)[source]

Bases: StrEnum

Kind of node an element is in the network.

class pyrunoff.utils.model.Process(*values)[source]

Bases: StrEnum

Hydrologic process an element computes with a selectable method.

class pyrunoff.utils.model.Method(*values)[source]

Bases: StrEnum

Calculation method used for a process.

class pyrunoff.utils.model.BasinOutput(input, excess, total_loss, baseflow, direct_runoff, total_outflow)[source]

Bases: object

Results of routing a basin.

Parameters:
class pyrunoff.utils.model.LossResult(*args, **kwargs)[source]

Bases: Protocol

Output every loss method’s route() must return.

class pyrunoff.utils.model.RoutingResult(*args, **kwargs)[source]

Bases: Protocol

Output every reach or reservoir routing method’s route() must return.

Methods may add their own fields (e.g. storage for reservoirs).

class pyrunoff.utils.model.ReachOutput(input, routing_output, total_outflow)[source]

Bases: object

Results of routing a reach.

Parameters:
class pyrunoff.utils.model.ReservoirOutput(input, routing_output, total_outflow)[source]

Bases: object

Results of routing a reservoir.

Parameters:
  • input (DischargeSeries) – Total inflow from upstream elements.

  • routing_output (RoutingResult) – Full output of the routing method (outflow per outlet, storage and elevation).

  • total_outflow (DischargeSeries) – Sum of outflow across all outlets.

class pyrunoff.utils.model.JunctionOutput(input, total_outflow)[source]

Bases: object

Results of routing a junction.

Parameters:
class pyrunoff.utils.model.SinkOutput(input)[source]

Bases: object

Results of routing the sink, which has no outflow.

Parameters:

input (DischargeSeries) – Total inflow from upstream elements.

pyrunoff.utils.model.ELEMENT_PROCESSES: dict[ElementType, dict[Process, tuple[Method, ...]]] = {ElementType.BASIN: {Process.BASEFLOW: (Method.LINEAR_RESERVOIR,), Process.LOSS: (Method.INITIAL_CONSTANT, Method.DEFICIT_CONSTANT), Process.TRANSFORM: (Method.CLARK_UH,)}, ElementType.JUNCTION: {}, ElementType.REACH: {Process.ROUTING: (Method.MUSKINGUM,)}, ElementType.RESERVOIR: {Process.ROUTING: (Method.LEVEL_POOL,)}, ElementType.SINK: {}}

Processes each element type computes, and the methods allowed for each.

pyrunoff.utils.model.METHOD_CLASSES: dict[Method, type] = {Method.CLARK_UH: <class 'pyrunoff.transform.clark_uh.ClarkUh'>, Method.DEFICIT_CONSTANT: <class 'pyrunoff.loss.deficit_and_constant.DeficitConstant'>, Method.INITIAL_CONSTANT: <class 'pyrunoff.loss.initial_and_constant.InitialConstant'>, Method.LEVEL_POOL: <class 'pyrunoff.reservoir.level_pool.LevelPool'>, Method.LINEAR_RESERVOIR: <class 'pyrunoff.baseflow.linear_reservoir.LinearReservoir'>, Method.MUSKINGUM: <class 'pyrunoff.reach.muskingum.Muskingum'>}

Class implementing each method.

pyrunoff.utils.model.ElementParams

Constructor arguments for a method, keyed by element name, e.g. loss_params["B1"] = {"initial_loss": 0.5, "constant_loss": 0.1}.

alias of Mapping[str, Mapping[str, Any]]

class pyrunoff.utils.model.Element(name, type, methods=<factory>)[source]

Bases: object

Model element; identity (equality and hash) is its name and type.

Parameters:
Raises:

ValueError – If a method is invalid or a process has no method.

class pyrunoff.utils.model.Model(elements, upstream_elements, defaults=<factory>)[source]

Bases: object

Network of elements in execution order.

The dictionary form read by from_dict() and written by to_dict() lists each element once:

{
    "defaults": {"<element type>": {"<process>": "<method>"}},
    "elements": {
        "<element>": {
            "type": "<element type>",
            "downstream": "<element>",
            "<process>": "<method>"
        }
    }
}

Every element needs a type. Exactly one element has no downstream; it is the model’s sink and its type must be "sink". No other element may be a sink.

An element’s method for a process is taken from its own entry, otherwise from defaults for its type. Every process of every element must end up with a method; ELEMENT_PROCESSES lists the allowed ones.

Parameters:
classmethod from_dict(data)[source]

Build a model from its dictionary form (see class docstring).

Parameters:

data (dict) – Model in dictionary form.

Returns:

The model, with elements in execution order.

Raises:

ValueError – If there is not exactly one sink, an element has no type, a downstream element is undefined, or a method is invalid or missing.

Return type:

Model

to_dict()[source]

Return the dictionary form; elements list only methods that differ from defaults.

Return type:

dict

route(rainfall, loss_params=None, transform_params=None, baseflow_params=None, reach_params=None, reservoir_params=None)[source]

Route rainfall through the model in execution order.

Each element uses the method given in its Element’s methods for each of its processes. Parameters are keyed by element name and passed as given to the class of the method chosen for that element, so each element’s parameters must be consistent with its chosen method and match that class’s constructor (e.g. initial_loss and constant_loss for initial_constant, max_deficit and constant_loss for deficit_constant; timedeltas for durations).

Parameters:
Returns:

Results keyed by element name: a BasinOutput, ReachOutput, ReservoirOutput, JunctionOutput or SinkOutput depending on the element type.

Raises:
  • TypeError – If a basin’s rainfall is not a RainfallSeries.

  • ValueError – If rainfall or parameters are missing for an element, a basin has upstream elements, or another element has none.

Return type:

dict[str, BasinOutput | ReachOutput | ReservoirOutput | JunctionOutput | SinkOutput]

Read model, parameter and rainfall files for a Model.

pyrunoff.utils.params.TIME_UNITS = {'hours', 'minutes', 'seconds'}

Units accepted for durations, converted to pandas.Timedelta.

pyrunoff.utils.params.load_model(path)[source]

Read a model file; see Model.from_dict() for its form.

Parameters:

path (str | Path) – JSON model file.

Returns:

The model.

Raises:

ValueError – If the model is invalid.

Return type:

Model

pyrunoff.utils.params.load_rainfall(path, model, time_column='timestamp', date_format=None, end=None, end_offset=None)[source]

Read incremental rainfall for each of the model’s basins from a CSV file.

The file has a timestamp column and one column of rainfall depths (inches) per basin, named after the basin. Other columns are ignored.

Parameters:
  • path (str | Path) – CSV file to read.

  • model (Model) – Model whose basins need rainfall.

  • time_column (str) – Name of the timestamp column.

  • date_format (str | None) – strftime format of the timestamps, e.g. "%m/%d/%Y %H:%M"; inferred when None.

  • end (Timestamp | datetime | None) – Optional timestamp at which every series ends; see TimeSeries.fix_end().

  • end_offset (Timedelta | timedelta | None) – Optional end relative to the last timestamp in the file: positive adds zero rainfall (e.g. to let hydrographs recede), negative drops the last values; not combined with end.

Returns:

Rainfall keyed by basin name, ready for Model.route().

Raises:

ValueError – If both end and end_offset are given, the end is before the first timestamp, the timestamp column is missing or does not match date_format, a basin has no column, or a column is invalid.

Return type:

dict[str, RainfallSeries]

pyrunoff.utils.params.load_params(path, model)[source]

Read a parameter file for model; see parse_params().

Curve file paths in the parameter file are relative to its directory.

Parameters:
  • path (str | Path) – JSON parameter file.

  • model (Model) – Model the parameters are for.

Returns:

Parameters keyed by Model.route() argument name.

Raises:

ValueError – If the parameters are invalid; see parse_params().

Return type:

dict[str, Mapping[str, Mapping[str, Any]]]

pyrunoff.utils.params.parse_params(data, model, base_dir='.')[source]

Convert a parameter dictionary into the arguments of Model.route().

The dictionary groups elements by type, and each element’s parameters by process and method:

{
    "basins": {
        "<basin>": {
            "basin_area": {"value": 465.4},
            "<process>": {
                "<method>": {"<param>": {"value": 3384, "units": "seconds"}}
            }
        }
    },
    "reaches": {"<reach>": {"routing": {"<method>": {...}}}},
    "reservoirs": {"<reservoir>": {"routing": {"<method>": {...}}}}
}

A parameter is one of:

  • {"value": v}: the number v.

  • {"value": v, "units": u}: a pandas.Timedelta when u is in TIME_UNITS.

  • {"path": p, "<x>_column_name": ..., "<y>_column_name": ...}: a curve read from the CSV file p; an ElevationStorage for elevation and storage columns, a StorageDischarge for storage and discharge columns.

  • Any other dictionary: a dictionary of parameters, e.g. one curve per outlet.

The model chooses each element’s methods; a process may hold parameters for other methods too, so switching methods in the model needs no change here. Only the chosen method’s parameters are read, but every method’s parameter names are checked against its class.

Element-level parameters (basin_area) are passed to every method of the element whose class takes them, unless the method’s own parameters set them.

Parameters:
  • data (Mapping) – Parameters in the form above.

  • model (Model) – Model the parameters are for; it gives each element’s methods.

  • base_dir (str | Path) – Directory curve file paths are relative to.

Returns:

Parameters keyed by Model.route() argument name, so that model.route(rainfall, **parse_params(data, model)) runs the model.

Raises:

ValueError – If an element is not in the model or has another type; a section, element key, method or parameter name is unknown; a used parameter is malformed; a process has no parameters for the model’s method; or a parameter a method requires is missing.

Return type:

dict[str, Mapping[str, Mapping[str, Any]]]

Topological ordering of a network described by flow paths.

class pyrunoff.utils.network.Network(paths, prevent_split=True)[source]

Bases: object

Represent a directed network described by ordered node paths.

Parameters:
  • paths (list[list[str]]) – Paths whose consecutive nodes define directed edges.

  • prevent_split (bool) – Whether to reject nodes with multiple downstream neighbors.

Variables:
  • sorted_nodes – Nodes in topological execution order.

  • upstream_nodes – Direct upstream parents for each node.

static toposort(paths, prevent_split=True)[source]

Sort nodes topologically and map each node to its direct upstream nodes.

Parameters:
  • paths – Paths represented as ordered lists of node names.

  • prevent_split (bool) – If true, reject nodes with multiple downstream neighbors.

Returns:

A tuple containing the ordered nodes and upstream-node mapping.

Raises:

ValueError – If a node diverges while splitting is disabled or the paths contain a cycle.