Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Flow routing in River Network

This example shows how you can build a basic routing model using NADI.

Here is a random network that is a directed tree, without any bifurcations.

net random_tree(25, 5, seed=10)
net.cairo.network("./output/rm-routing-net.svg")

Results:

Lets do a simple streamflow routing, few assumptions,

  • there is random precipitation of upto 50 units, precipitation only happens 20% of the days
  • the evaporation/interception etc loss are 10% of the precip or 5 units, whichever is higher
  • all the nodes represent basin area of 1 unit
func rand_precip(s) {
  if (random_bool(0.2, seed=s%7+node.INDEX)) {
    v = random(seed=s+node.INDEX) * 50
	int(v * 4) / 4
  } else { 0.0 }
}

func precip_loss(p) {
	min([max([p * 0.1, 5], 0), p], inf)
}

nodes$precip = [rand_precip(i*17) for i in 1:10]
nodes$loss = $precip -> @precip_loss

nodes$runoff = ($precip, $loss) -> func(p, l) {p - l}
leaves$sf = $runoff

nodes.inps = len(inputs._) > 0

nodes<inp>(inps)$sf = (inputs$sf, $runoff) -> @sum

nm $sf

Results:

{
  "16" = Series(len: 10, dtype: Floats) [144.75, 82.75, 138.75, 119.75, 48.25, 131.5, 96.75, 102.25, 111.5, 120.25],
  "7" = Series(len: 10, dtype: Floats) [0, 26.5, 40, 0, 0, 0, 0, 0, 20.25, 17.75],
  "12" = Series(len: 10, dtype: Floats) [37, 0, 0, 9, 16.25, 4.5, 18.75, 0, 0, 0],
  "5" = Series(len: 10, dtype: Floats) [37, 0, 0, 0, 0, 4.5, 18.75, 0, 0, 0],
  "1" = Series(len: 10, dtype: Floats) [37, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  "4" = Series(len: 10, dtype: Floats) [0, 25, 40.75, 74, 16.5, 69.5, 39.75, 27.5, 37.25, 67.75],
  "15" = Series(len: 10, dtype: Floats) [0, 25, 33.75, 74, 16.5, 69.5, 39.75, 27.5, 37.25, 40],
  "6" = Series(len: 10, dtype: Floats) [0, 21.5, 0, 0, 0, 0, 39.75, 0, 1.25, 0],
  "0" = Series(len: 10, dtype: Floats) [0, 0, 0, 43.75, 0, 0, 0, 0, 0, 0],
  "24" = Series(len: 10, dtype: Floats) [0, 3.5, 33.75, 30.25, 0, 69.5, 0, 27.5, 36, 40],
  "18" = Series(len: 10, dtype: Floats) [0, 0, 33.75, 30.25, 0, 44, 0, 27.5, 0, 40],
  "3" = Series(len: 10, dtype: Floats) [0, 0, 33.75, 0, 0, 44, 0, 0, 0, 40],
  "10" = Series(len: 10, dtype: Floats) [69.5, 31.25, 58, 36.75, 15.5, 57.5, 38.25, 61, 54, 34.75],
  "13" = Series(len: 10, dtype: Floats) [38.75, 31.25, 58, 36.75, 3.75, 57.5, 38.25, 41.5, 54, 34.75],
  "22" = Series(len: 10, dtype: Floats) [38.75, 31.25, 36.25, 36.75, 3.75, 57.5, 25.75, 41.5, 54, 0],
  "21" = Series(len: 10, dtype: Floats) [0, 0, 0, 0, 0, 0, 25.75, 0, 0, 0],
  "11" = Series(len: 10, dtype: Floats) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  "2" = Series(len: 10, dtype: Floats) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  "17" = Series(len: 10, dtype: Floats) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  "23" = Series(len: 10, dtype: Floats) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  "8" = Series(len: 10, dtype: Floats) [38.75, 31.25, 36.25, 36.75, 0, 57.5, 0, 41.5, 54, 0],
  "14" = Series(len: 10, dtype: Floats) [38.75, 17.5, 36.25, 36.75, 0, 57.5, 0, 41.5, 39, 0],
  "19" = Series(len: 10, dtype: Floats) [0, 0, 0, 30.5, 0, 18.5, 0, 0, 0, 0],
  "20" = Series(len: 10, dtype: Floats) [17.25, 0, 0, 0, 0, 39, 0, 40.25, 0, 0],
  "9" = Series(len: 10, dtype: Floats) [21.5, 0, 36.25, 0, 0, 0, 0, 1.25, 0, 0]
}

We have the results but let’s visualize it

env.progr = ["▁" ,"▂" ,"▃" ,"▄" ,"▅", "▇"]
max_sf = max(nodes { max([v for v in $sf], 0) }, 0)
max_pr = max(nodes { max([v for v in $precip], 0) }, 0)

func get_bar(p, m) {get(env.progr, int(p * (len(env.progr) - 1) / m))}

nodes.pvis = str_join([get_bar(p, max_pr) for p in $precip])

nodes.svis = str_join([get_bar(p, max_sf) for p in $sf])
nm<inv> [pvis, svis]

Results:

{
  "9" = ["▄▁▅▁▁▁▁▂▁▁", "▂▁▂▁▁▁▁▁▁▁"],
  "20" = ["▃▁▁▁▁▅▁▇▁▁", "▂▁▁▁▁▂▁▂▁▁"],
  "19" = ["▁▁▁▅▁▃▁▁▁▁", "▁▁▁▂▁▂▁▁▁▁"],
  "14" = ["▁▃▁▂▁▁▁▁▅▁", "▂▂▂▂▁▃▁▂▂▁"],
  "8" = ["▁▃▁▁▁▁▁▁▃▁", "▂▂▂▂▁▃▁▂▃▁"],
  "23" = ["▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁"],
  "17" = ["▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁"],
  "2" = ["▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁"],
  "11" = ["▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁"],
  "21" = ["▁▁▁▁▁▁▄▁▁▁", "▁▁▁▁▁▁▂▁▁▁"],
  "22" = ["▁▁▁▁▂▁▁▁▁▁", "▂▂▂▂▁▃▂▂▃▁"],
  "13" = ["▁▁▄▁▁▁▃▁▁▅", "▂▂▃▂▁▃▂▂▃▂"],
  "10" = ["▅▁▁▁▃▁▁▄▁▁", "▃▂▃▂▂▃▂▃▃▂"],
  "3" = ["▁▁▅▁▁▇▁▁▁▇", "▁▁▂▁▁▃▁▁▁▂"],
  "18" = ["▁▁▁▅▁▁▁▄▁▁", "▁▁▂▂▁▃▁▂▁▂"],
  "24" = ["▁▂▁▁▁▄▁▁▅▁", "▁▁▂▂▁▃▁▂▂▂"],
  "0" = ["▁▁▁▇▁▁▁▁▁▁", "▁▁▁▃▁▁▁▁▁▁"],
  "6" = ["▁▄▁▁▁▁▇▁▂▁", "▁▂▁▁▁▁▂▁▁▁"],
  "15" = ["▁▁▁▁▃▁▁▁▁▁", "▁▂▂▄▂▃▂▂▂▂"],
  "4" = ["▁▁▂▁▁▁▁▁▁▄", "▁▂▂▄▂▃▂▂▂▃"],
  "1" = ["▅▁▁▁▁▁▁▁▁▁", "▂▁▁▁▁▁▁▁▁▁"],
  "5" = ["▁▁▁▁▁▂▃▁▁▁", "▂▁▁▁▁▁▂▁▁▁"],
  "12" = ["▁▁▁▂▃▁▁▁▁▁", "▂▁▁▁▂▁▂▁▁▁"],
  "7" = ["▁▄▇▁▁▁▁▁▄▃", "▁▂▂▁▁▁▁▁▂▂"],
  "16" = ["▅▁▁▁▁▁▁▃▁▁", "▇▄▇▅▃▇▄▅▅▅"]
}

The first set of bars is pecipitation, and second set is the streamflow. Let’s draw it with network diagram.

net.svg_save("./output/rm-prec2.svg", label="{pvis} -> {svis}", width=200.0, height=325.0, bgcolor="white")

Results:


Because our precipitation was random, as we move downstream the streamflow starts to get more regular.

We can also see how the streamflow is collected through the routing process.

A better visualization could be done through future plugins, but this is just an example of how you can program things in NADI to do what you want.