landlab.components.flow_accum.flow_accum_to_n

Short description.

flow_accum_to_n.py: Implementation a route-to-multiple drainage stack alorithm.

Algorithm for route to multiple (N) flow accumulation. Inspiration for data structures and attempting O(n) efficiency taken from Braun and Willet(2013).

Algorithm constructs drainage area and (optionally) water discharge. Can handle the case in which each node has more than one downstream receiver.

Computationally, for a grid of the same size this algorithm will take about

1.5 x (avg number of downstream nodes per cell)

x (duration of flow_accum_bw for same grid using route-to-one method)

So under route-to-one direction schemes, using the Braun and Willet method is recommended.

If water discharge is calculated, the result assumes steady flow (that is, hydrologic equilibrium).

The main public function is:

a, q, s = flow_accumulation_to_n(r, p)

which takes the following inputs:

r, an (np, q) array of receiver-node IDs, where np is the total number of nodes and q is the maximum number of receivers any node in the grid has. This array would be returned by the flow_routing component.

p, an (np, q) array that identifies the proportion of flow going to each receiver. For each q elements along the np axis, sum(p(i, :)) must equal 1. This array would be returned by the flow_routing component.

It returns Numpy arrays with the drainage area (a) and discharge (q) at each node, along with an array (s) that contains the IDs of the nodes in downstream- to-upstream order.

If you simply want the ordered list by itself, use:

s = make_ordered_node_array_to_n(r, p, b)

Created: KRB Oct 2016 (modified from flow_accumu_bw)

find_drainage_area_and_discharge_to_n(s, r, p, node_cell_area=1.0, runoff=1.0, boundary_nodes=None)[source]

Calculate the drainage area and water discharge at each node.

Parameters:
  • s (ndarray of int) – Ordered (downstream to upstream) array of node IDs

  • r (ndarray size (np, q) where r[i, :] gives all receivers of node i. Each) – node recieves flow fom up to q donors.

  • p (ndarray size (np, q) where p[i, v] give the proportion of flow going) – from node i to the receiver listed in r[i, v].

  • node_cell_area (float or ndarray) – Cell surface areas for each node. If it’s an array, must have same length as s (that is, the number of nodes).

  • runoff (float or ndarray) – Local runoff rate at each cell (in water depth per time). If it’s an array, must have same length as s (that is, the number of nodes). runoff is permitted to be negative, in which case it performs as a transmission loss.

  • boundary_nodes (list, optional) – Array of boundary nodes to have discharge and drainage area set to zero. Default value is None.

Returns:

drainage area and discharge

Return type:

tuple of ndarray

Notes

  • If node_cell_area not given, the output drainage area is equivalent to the number of nodes/cells draining through each point, including the local node itself.

  • Give node_cell_area as a scalar when using a regular raster grid.

  • If runoff is not given, the discharge returned will be the same as drainage area (i.e., drainage area times unit runoff rate).

  • If using an unstructured Landlab grid, make sure that the input argument for node_cell_area is the cell area at each NODE rather than just at each CELL. This means you need to include entries for the perimeter nodes too. They can be zeros.

Examples

>>> import numpy as np
>>> from landlab.components.flow_accum.flow_accum_to_n import (
...     find_drainage_area_and_discharge_to_n,
... )
>>> r = np.array(
...     [
...         [1, 2],
...         [4, 5],
...         [1, 5],
...         [6, 2],
...         [4, -1],
...         [4, -1],
...         [5, 7],
...         [4, 5],
...         [6, 7],
...         [7, 8],
...     ]
... )
>>> p = np.array(
...     [
...         [0.6, 0.4],
...         [0.85, 0.15],
...         [0.65, 0.35],
...         [0.9, 0.1],
...         [1.0, 0.0],
...         [1.0, 0.0],
...         [0.75, 0.25],
...         [0.55, 0.45],
...         [0.8, 0.2],
...         [0.95, 0.05],
...     ]
... )
>>> s = np.array([4, 5, 1, 7, 2, 6, 0, 8, 3, 9])
>>> a, q = find_drainage_area_and_discharge_to_n(s, r, p)
>>> a.round(4)
array([  1.    ,   2.575 ,   1.5   ,   1.    ,  10.    ,   5.2465,
         2.74  ,   2.845 ,   1.05  ,   1.    ])
>>> q.round(4)
array([  1.    ,   2.575 ,   1.5   ,   1.    ,  10.    ,   5.2465,
         2.74  ,   2.845 ,   1.05  ,   1.    ])
find_drainage_area_and_discharge_to_n_lossy(s, r, link_to_receiver, p, loss_function, grid, node_cell_area=1.0, runoff=1.0, boundary_nodes=None)[source]

Calculate the drainage area and water discharge at each node, permitting discharge to fall (or gain) as it moves downstream according to some function. Note that only transmission creates loss, so water sourced locally within a cell is always retained. The loss on each link is recorded in the ‘surface_water__discharge_loss’ link field on the grid; ensure this exists before running the function.

Parameters:
  • s (ndarray of int) – Ordered (downstream to upstream) array of node IDs

  • r (ndarray size (np, q) where r[i, :] gives all receivers of node i. Each) – node receives flow fom up to q donors.

  • link_to_receiver (ndarray size (np, q) where l[i, :] gives all links to receivers of) – node i.

  • p (ndarray size (np, q) where p[i, v] give the proportion of flow going) – from node i to the receiver listed in r[i, v].

  • loss_function (Python function(Qw, nodeID, linkID)) – Function dictating how to modify the discharge as it leaves each node. nodeID is the current node; linkID is the downstream link. Returns a float.

  • grid (Landlab ModelGrid (or None)) – A grid to enable spatially variable parameters to be used in the loss function. If no spatially resolved parameters are needed, this can be a dummy variable, e.g., None.

  • node_cell_area (float or ndarray) – Cell surface areas for each node. If it’s an array, must have same length as s (that is, the number of nodes).

  • runoff (float or ndarray) – Local runoff rate at each cell (in water depth per time). If it’s an array, must have same length as s (that is, the number of nodes).

  • boundary_nodes (list, optional) – Array of boundary nodes to have discharge and drainage area set to zero. Default value is None.

Returns:

drainage area and discharge

Return type:

tuple of ndarray

Notes

  • If node_cell_area not given, the output drainage area is equivalent to the number of nodes/cells draining through each point, including the local node itself.

  • Give node_cell_area as a scalar when using a regular raster grid.

  • If runoff is not given, the discharge returned will be the same as drainage area (i.e., drainage area times unit runoff rate).

  • If using an unstructured Landlab grid, make sure that the input argument for node_cell_area is the cell area at each NODE rather than just at each CELL. This means you need to include entries for the perimeter nodes too. They can be zeros.

  • Loss cannot go negative.

Examples

>>> import numpy as np
>>> from landlab import RasterModelGrid
>>> from landlab.components.flow_accum.flow_accum_to_n import (
...     find_drainage_area_and_discharge_to_n_lossy,
... )
>>> r = np.array([[1, 2], [3, -1], [3, 1], [3, -1]])
>>> p = np.array([[0.5, 0.5], [1.0, 0.0], [0.2, 0.8], [1.0, 0.0]])
>>> s = np.array([3, 1, 2, 0])
>>> l = np.ones_like(r, dtype=int)  # dummy

Make here a grid that contains (too many!) links holding values for loss. We’re only going to use the first 4 links, but illustrates the use of the grid for link input.

>>> mg = RasterModelGrid((3, 3))
>>> _ = mg.add_zeros("node", "surface_water__discharge_loss", dtype=float)
>>> lossy = mg.add_ones("lossy", at="link", dtype=float)
>>> lossy *= 0.5
>>> def lossfunc(Qw, dummyn, linkID, grid):
...     return grid.at_link["lossy"][linkID] * Qw
...
>>> a, q = find_drainage_area_and_discharge_to_n_lossy(s, r, l, p, lossfunc, mg)
>>> a
array([1. , 2.7, 1.5, 4. ])
>>> q
array([1.  , 1.75, 1.25, 2.  ])
>>> np.allclose(mg.at_node["surface_water__discharge_loss"][:3], 0.5 * q[:3])
True

Note by definition no loss is occuring at the outlet node, as there are no nodes downstream.

Final example of total transmission loss:

>>> def lossfunc(Qw, dummyn, dummyl, dummygrid):
...     return Qw - 100.0  # huge loss
...
>>> a, q = find_drainage_area_and_discharge_to_n_lossy(s, r, l, p, lossfunc, mg)
>>> a
array([1. , 2.7, 1.5, 4. ])
>>> q
array([1., 1., 1., 1.])
flow_accumulation_to_n(receiver_nodes, receiver_proportions, node_cell_area=1.0, runoff_rate=1.0, boundary_nodes=None)[source]

Calculate drainage area and (steady) discharge.

Calculates and returns the drainage area and (steady) discharge at each node, along with a downstream-to-upstream ordered list (array) of node IDs.

Examples

>>> import numpy as np
>>> from landlab.components.flow_accum.flow_accum_to_n import flow_accumulation_to_n
>>> r = np.array(
...     [
...         [1, 2],
...         [4, 5],
...         [1, 5],
...         [6, 2],
...         [4, -1],
...         [4, -1],
...         [5, 7],
...         [4, 5],
...         [6, 7],
...         [7, 8],
...     ]
... )
>>> p = np.array(
...     [
...         [0.6, 0.4],
...         [0.85, 0.15],
...         [0.65, 0.35],
...         [0.9, 0.1],
...         [1.0, 0.0],
...         [1.0, 0.0],
...         [0.75, 0.25],
...         [0.55, 0.45],
...         [0.8, 0.2],
...         [0.95, 0.05],
...     ]
... )
>>> a, q, s = flow_accumulation_to_n(r, p)
>>> a.round(4)
array([  1.    ,   2.575 ,   1.5   ,   1.    ,  10.    ,   5.2465,
         2.74  ,   2.845 ,   1.05  ,   1.    ])
>>> q.round(4)
array([  1.    ,   2.575 ,   1.5   ,   1.    ,  10.    ,   5.2465,
         2.74  ,   2.845 ,   1.05  ,   1.    ])
>>> s[0] == 4
True
>>> s[1] == 5
True
>>> s[9] == 9
True
>>> len(set([1, 7]) - set(s[2:4]))
0
>>> len(set([2, 6]) - set(s[4:6]))
0
>>> len(set([0, 3, 8]) - set(s[6:9]))
0
make_ordered_node_array_to_n(receiver_nodes, receiver_proportion, nd=None, delta=None, D=None)[source]

Create an array of node IDs.

Creates and returns an array of node IDs that is arranged in order from downstream to upstream.

The lack of a leading underscore is meant to signal that this operation could be useful outside of this module!

Examples

>>> import numpy as np
>>> from landlab.components.flow_accum.flow_accum_to_n import (
...     make_ordered_node_array_to_n,
... )
>>> r = np.array(
...     [
...         [1, 2],
...         [4, 5],
...         [1, 5],
...         [6, 2],
...         [4, -1],
...         [4, -1],
...         [5, 7],
...         [4, 5],
...         [6, 7],
...         [7, 8],
...     ]
... )
>>> p = np.array(
...     [
...         [0.6, 0.4],
...         [0.85, 0.15],
...         [0.65, 0.35],
...         [0.9, 0.1],
...         [1.0, 0.0],
...         [1.0, 0.0],
...         [0.75, 0.25],
...         [0.55, 0.45],
...         [0.8, 0.2],
...         [0.95, 0.05],
...     ]
... )
>>> s = make_ordered_node_array_to_n(r, p)
>>> s[0] == 4
True
>>> s[1] == 5
True
>>> s[9] == 9
True
>>> len(set([1, 7]) - set(s[2:4]))
0
>>> len(set([2, 6]) - set(s[4:6]))
0
>>> len(set([0, 3, 8]) - set(s[6:9]))
0