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.