Code
using Ribasim
@@ -589,45 +589,45 @@ println(p.allocation.allocation_models[1].problem)
Min F_abs_user_demand[UserDemand #3] + F_abs_user_demand[UserDemand #13] + F_abs_user_demand[UserDemand #6] + F_abs_basin[Basin #2] + F_abs_basin[Basin #5] + F_abs_basin[Basin #12]
+Min F_abs_user_demand[UserDemand #3] + F_abs_user_demand[UserDemand #6] + F_abs_user_demand[UserDemand #13] + F_abs_basin[Basin #12] + F_abs_basin[Basin #2] + F_abs_basin[Basin #5]
Subject to
- flow_conservation[Basin #2] : -F[(FlowBoundary #1, Basin #2)] - F[(Basin #5, Basin #2)] + F[(Basin #2, Basin #5)] + F[(Basin #2, UserDemand #3)] + F_basin_in[Basin #2] - F_basin_out[Basin #2] = 0
- flow_conservation[Basin #5] : F[(Basin #5, TabulatedRatingCurve #7)] + F[(Basin #5, UserDemand #6)] + F[(Basin #5, Basin #2)] - F[(Basin #2, Basin #5)] + F_basin_in[Basin #5] - F_basin_out[Basin #5] = 0
flow_conservation[Basin #12] : -F[(TabulatedRatingCurve #7, Basin #12)] + F[(Basin #12, UserDemand #13)] + F_basin_in[Basin #12] - F_basin_out[Basin #12] = 0
+ flow_conservation[Basin #2] : -F[(Basin #5, Basin #2)] + F[(Basin #2, UserDemand #3)] - F[(FlowBoundary #1, Basin #2)] + F[(Basin #2, Basin #5)] + F_basin_in[Basin #2] - F_basin_out[Basin #2] = 0
+ flow_conservation[Basin #5] : F[(Basin #5, Basin #2)] + F[(Basin #5, TabulatedRatingCurve #7)] + F[(Basin #5, UserDemand #6)] - F[(Basin #2, Basin #5)] + F_basin_in[Basin #5] - F_basin_out[Basin #5] = 0
+ abs_positive_user_demand[UserDemand #3] : -F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ -1.5
+ abs_positive_user_demand[UserDemand #6] : -F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 0
+ abs_positive_user_demand[UserDemand #13] : -F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0
+ abs_negative_user_demand[UserDemand #3] : F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 1.5
+ abs_negative_user_demand[UserDemand #6] : F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 0
+ abs_negative_user_demand[UserDemand #13] : F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0
+ abs_positive_basin[Basin #12] : -F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0
+ abs_positive_basin[Basin #2] : -F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0
+ abs_positive_basin[Basin #5] : -F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0
+ abs_negative_basin[Basin #12] : F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0
+ abs_negative_basin[Basin #2] : F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0
+ abs_negative_basin[Basin #5] : F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0
source[(FlowBoundary #1, Basin #2)] : F[(FlowBoundary #1, Basin #2)] ≤ 1
return_flow[UserDemand #13] : F[(UserDemand #13, Terminal #10)] ≤ 0
- fractional_flow[(TabulatedRatingCurve #7, Basin #12)] : -0.4 F[(Basin #5, TabulatedRatingCurve #7)] + F[(TabulatedRatingCurve #7, Basin #12)] ≤ 0
+ fractional_flow[(TabulatedRatingCurve #7, Basin #12)] : F[(TabulatedRatingCurve #7, Basin #12)] - 0.4 F[(Basin #5, TabulatedRatingCurve #7)] ≤ 0
+ basin_outflow[Basin #12] : F_basin_out[Basin #12] ≤ 0
basin_outflow[Basin #2] : F_basin_out[Basin #2] ≤ 0
basin_outflow[Basin #5] : F_basin_out[Basin #5] ≤ 0
- basin_outflow[Basin #12] : F_basin_out[Basin #12] ≤ 0
+ F[(UserDemand #13, Terminal #10)] ≥ 0
+ F[(TabulatedRatingCurve #7, Basin #12)] ≥ 0
+ F[(Basin #5, Basin #2)] ≥ 0
F[(Basin #5, TabulatedRatingCurve #7)] ≥ 0
F[(Basin #5, UserDemand #6)] ≥ 0
- F[(FlowBoundary #1, Basin #2)] ≥ 0
- F[(Basin #5, Basin #2)] ≥ 0
- F[(Basin #2, Basin #5)] ≥ 0
F[(Basin #2, UserDemand #3)] ≥ 0
- F[(TabulatedRatingCurve #7, Basin #12)] ≥ 0
F[(TabulatedRatingCurve #7, Terminal #10)] ≥ 0
+ F[(FlowBoundary #1, Basin #2)] ≥ 0
F[(Basin #12, UserDemand #13)] ≥ 0
- F[(UserDemand #13, Terminal #10)] ≥ 0
+ F[(Basin #2, Basin #5)] ≥ 0
+ F_basin_in[Basin #12] ≥ 0
F_basin_in[Basin #2] ≥ 0
F_basin_in[Basin #5] ≥ 0
- F_basin_in[Basin #12] ≥ 0
+ F_basin_out[Basin #12] ≥ 0
F_basin_out[Basin #2] ≥ 0
F_basin_out[Basin #5] ≥ 0
- F_basin_out[Basin #12] ≥ 0
- abs_positive_user_demand[UserDemand #3] : -F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 0
- abs_positive_user_demand[UserDemand #13] : -F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0
- abs_positive_user_demand[UserDemand #6] : -F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ -1.5
- abs_negative_user_demand[UserDemand #3] : F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 0
- abs_negative_user_demand[UserDemand #13] : F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0
- abs_negative_user_demand[UserDemand #6] : F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 1.5
- abs_positive_basin[Basin #2] : -F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0
- abs_positive_basin[Basin #5] : -F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0
- abs_positive_basin[Basin #12] : -F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0
- abs_negative_basin[Basin #2] : F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0
- abs_negative_basin[Basin #5] : F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0
- abs_negative_basin[Basin #12] : F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0
Here \(p > 0\) is the threshold value which determines the interval \([0,p]\) of the smooth transition between \(0\) and \(1\), see the plot below.
-
+
Code
import numpy as np
diff --git a/core/usage.html b/core/usage.html
index 30d032bca..6b83b7a7f 100644
--- a/core/usage.html
+++ b/core/usage.html
@@ -1738,10 +1738,10 @@ \(\Delta t\) can be supplied.
-
-
-
-
+
+
+
+
@@ -1759,22 +1759,22 @@
-listen_feature_id
-Int
+listen_node_type
+String
-
-sorted per node_id
+known node type
-listen_feature_type
-String
+listen_node_id
+Int
-
-known node type
+sorted per node_id
variable
String
-
-must be “level” or “flow_rate”, sorted per listen_feature_id
+must be “level” or “flow_rate”, sorted per listen_node_id
greater_than
@@ -1886,30 +1886,36 @@ 18 PidControl(optional, default true)
+listen_node_type
+Int
+-
+known node type
+
+
listen_node_id
Int
-
-
-
+
target
Float64
\(m\)
-
-
+
proportional
Float64
\(s^{-1}\)
-
-
+
integral
Float64
\(s^{-2}\)
-
-
+
derivative
Float64
-
@@ -1943,30 +1949,36 @@ sorted per node_id
+listen_node_type
+Int
+-
+known node type
+
+
listen_node_id
Int
-
-
-
+
target
Float64
\(m\)
-
-
+
proportional
Float64
\(s^{-1}\)
-
-
+
integral
Float64
\(s^{-2}\)
-
-
+
derivative
Float64
-
diff --git a/core/validation.html b/core/validation.html
index 55cf077fd..0545fbdba 100644
--- a/core/validation.html
+++ b/core/validation.html
@@ -262,7 +262,7 @@ Validation
1 Connectivity
In the table below, each column shows which node types are allowed to be downstream (or ‘down-control’) of the node type at the top of the column.
-
+
Code
using Ribasim
@@ -546,7 +546,7 @@ 1 Connectivity
2 Neighbor amounts
The table below shows for each node type between which bounds the amount of in- and outneighbors must be, for both flow and control edges.
-
+
Code
= Vector{String}()
diff --git a/python/examples.html b/python/examples.html
index d6b315f20..35d6696c7 100644
--- a/python/examples.html
+++ b/python/examples.html
@@ -237,11 +237,10 @@ flow_in_min On this page
- 1 Basic model with static forcing
- - 2 Update the basic model with transient forcing
- - 3 Model with discrete control
- - 4 Model with PID control
- - 5 Model with allocation (user demand)
- - 6 Model with allocation (basin supply/demand)
+ - 2 Model with discrete control
+ - 3 Model with PID control
+ - 4 Model with allocation (user demand)
+ - 5 Model with allocation (basin supply/demand)
@@ -270,448 +269,259 @@ Examples
1 Basic model with static forcing
-from pathlib import Path
-
-import geopandas as gpd
+import shutil
+from pathlib import Path
+
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
-import ribasim
+import ribasim
+from ribasim.config import Node
+from ribasim.model import Model
+from ribasim.nodes import (
+
+ basin,
+ discrete_control,
+ flow_boundary,
+ fractional_flow,
+ level_boundary,
+ level_demand,
+ linear_resistance,
+ manning_resistance,
+ outlet,
+ pid_control,
+ pump,
+ tabulated_rating_curve,
+ user_demand,
+ )from shapely.geometry import Point
+
+
+= Path("data")
+ datadir =True) shutil.rmtree(datadir, ignore_errors
+
+
+= Model(starttime="2020-01-01 00:00:00", endtime="2021-01-01 00:00:00") model
Setup the basins:
-
-= pd.DataFrame(
- profile ={
- data"node_id": [1, 1, 3, 3, 6, 6, 9, 9],
- "area": [0.01, 1000.0] * 4,
- "level": [0.0, 1.0] * 4,
-
- }
- )
-# Convert steady forcing to m/s
-# 2 mm/d precipitation, 1 mm/d evaporation
-= 24 * 3600
- seconds_in_day = 0.002 / seconds_in_day
- precipitation = 0.001 / seconds_in_day
- evaporation
-= pd.DataFrame(
- static ={
- data"node_id": [0],
- "potential_evaporation": [evaporation],
- "precipitation": [precipitation],
-
- }
- )= static.iloc[[0, 0, 0, 0]]
- static "node_id"] = [1, 3, 6, 9]
- static[
-= ribasim.Basin(profile=profile, static=static) basin
+
+= pd.date_range(model.starttime, model.endtime)
+ time = time.day_of_year.to_numpy()
+ day_of_year = 24 * 60 * 60
+ seconds_per_day = (
+ evaporation -1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day
+ (
+ )= np.random.default_rng(seed=0)
+ rng = (
+ precipitation =-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day
+ rng.lognormal(mean
+ )
+# Convert steady forcing to m/s
+# 2 mm/d precipitation, 1 mm/d evaporation
+
+= [
+ basin_data =[0.01, 1000.0], level=[0.0, 1.0]),
+ basin.Profile(area
+ basin.Time(=pd.date_range(model.starttime, model.endtime),
+ time=0.0,
+ drainage=evaporation,
+ potential_evaporation=0.0,
+ infiltration=precipitation,
+ precipitation=0.0,
+ urban_runoff
+ ),=[1.4]),
+ basin.State(level
+ ]
+1, Point(0.0, 0.0)), basin_data)
+ model.basin.add(Node(3, Point(2.0, 0.0)), basin_data)
+ model.basin.add(Node(6, Point(3.0, 2.0)), basin_data)
+ model.basin.add(Node(9, Point(5.0, 0.0)), basin_data) model.basin.add(Node(
Setup linear resistance:
-
-= ribasim.LinearResistance(
- linear_resistance =pd.DataFrame(
- static={"node_id": [10, 12], "resistance": [5e3, (3600.0 * 24) / 100.0]}
- data
- ) )
+
+
+ model.linear_resistance.add(10, Point(6.0, 0.0)),
+ Node(=[5e3])],
+ [linear_resistance.Static(resistance
+ )
+ model.linear_resistance.add(12, Point(2.0, 1.0)),
+ Node(=[3600.0 * 24.0 / 100.0])],
+ [linear_resistance.Static(resistance )
Setup Manning resistance:
-
-= ribasim.ManningResistance(
- manning_resistance =pd.DataFrame(
- static={
- data"node_id": [2],
- "length": [900.0],
- "manning_n": [0.04],
- "profile_width": [6.0],
- "profile_slope": [3.0],
-
- }
- ) )
+
+
+ model.manning_resistance.add(2, Point(1.0, 0.0)),
+ Node(
+ [
+ manning_resistance.Static(=[900], manning_n=[0.04], profile_width=[6.0], profile_slope=[3.0]
+ length
+ )
+ ], )
Set up a rating curve node:
-
-# Discharge: lose 1% of storage volume per day at storage = 1000.0.
-= 1000.0 * 0.01 / seconds_in_day
- q1000
-= ribasim.TabulatedRatingCurve(
- rating_curve =pd.DataFrame(
- static={
- data"node_id": [4, 4],
- "level": [0.0, 1.0],
- "flow_rate": [0.0, q1000],
-
- }
- ) )
+
+
+ model.tabulated_rating_curve.add(4, Point(3.0, 0.0)),
+ Node(=[0.0, 1.0], flow_rate=[0.0, 10 / 86400])],
+ [tabulated_rating_curve.Static(level )
Setup fractional flows:
-
-= ribasim.FractionalFlow(
- fractional_flow =pd.DataFrame(
- static={
- data"node_id": [5, 8, 13],
- "fraction": [0.3, 0.6, 0.1],
-
- }
- ) )
+
+
+ model.fractional_flow.add(5, Point(3.0, 1.0)), [fractional_flow.Static(fraction=[0.3])]
+ Node(
+ )
+ model.fractional_flow.add(8, Point(4.0, 0.0)), [fractional_flow.Static(fraction=[0.6])]
+ Node(
+ )
+ model.fractional_flow.add(13, Point(3.0, -1.0)),
+ Node(=[0.1])],
+ [fractional_flow.Static(fraction )
Setup pump:
-
-= ribasim.Pump(
- pump =pd.DataFrame(
- static={
- data"node_id": [7],
- "flow_rate": [0.5 / 3600],
-
- }
- ) )
+
+7, Point(4.0, 1.0)), [pump.Static(flow_rate=[0.5 / 3600])]) model.pump.add(Node(
Setup level boundary:
-
-= ribasim.LevelBoundary(
- level_boundary =pd.DataFrame(
- static={
- data"node_id": [11, 17],
- "level": [0.5, 1.5],
-
- }
- ) )
+
+
+ model.level_boundary.add(11, Point(2.0, 2.0)), [level_boundary.Static(level=[0.5])]
+ Node(
+ )
+ model.level_boundary.add(17, Point(6.0, 1.0)), [level_boundary.Static(level=[1.5])]
+ Node( )
Setup flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(
- static={
- data"node_id": [15, 16],
- "flow_rate": [1e-4, 1e-4],
-
- }
- ) )
+
+
+ model.flow_boundary.add(15, Point(3.0, 3.0)), [flow_boundary.Static(flow_rate=[1e-4])]
+ Node(
+ )
+ model.flow_boundary.add(16, Point(0.0, 1.0)), [flow_boundary.Static(flow_rate=[1e-4])]
+ Node( )
Setup terminal:
-
-= ribasim.Terminal(
- terminal =pd.DataFrame(
- static={
- data"node_id": [14],
-
- }
- ) )
-
-Set up the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: Basin,
- (1.0, 0.0), # 2: ManningResistance
- (2.0, 0.0), # 3: Basin
- (3.0, 0.0), # 4: TabulatedRatingCurve
- (3.0, 1.0), # 5: FractionalFlow
- (3.0, 2.0), # 6: Basin
- (4.0, 1.0), # 7: Pump
- (4.0, 0.0), # 8: FractionalFlow
- (5.0, 0.0), # 9: Basin
- (6.0, 0.0), # 10: LinearResistance
- (2.0, 2.0), # 11: LevelBoundary
- (2.0, 1.0), # 12: LinearResistance
- (3.0, -1.0), # 13: FractionalFlow
- (3.0, -2.0), # 14: Terminal
- (3.0, 3.0), # 15: FlowBoundary
- (0.0, 1.0), # 16: FlowBoundary
- (6.0, 1.0), # 17: LevelBoundary
- (
- ]
- )= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= ribasim.Node.node_ids_and_types(
- node_id, node_type
- basin,
- manning_resistance,
- rating_curve,
- pump,
- fractional_flow,
- linear_resistance,
- level_boundary,
- flow_boundary,
- terminal,
- )
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type},
- data=pd.Index(node_id, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
+
+14, Point(3.0, -2.0))) model.terminal.add(Node(
Setup the edges:
-
-= np.array(
- from_id 1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64
- [
- )= np.array(
- to_id 2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64
- [
- )= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": len(from_id) * ["flow"],
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=level_boundary,
- level_boundary=flow_boundary,
- flow_boundary=pump,
- pump=linear_resistance,
- linear_resistance=manning_resistance,
- manning_resistance=rating_curve,
- tabulated_rating_curve=fractional_flow,
- fractional_flow=terminal,
- terminal="2020-01-01 00:00:00",
- starttime="2021-01-01 00:00:00",
- endtime )
+
+1], model.manning_resistance[2], "flow")
+ model.edge.add(model.basin[2], model.basin[3], "flow")
+ model.edge.add(model.manning_resistance[3], model.tabulated_rating_curve[4], "flow")
+ model.edge.add(model.basin[4], model.fractional_flow[5], "flow")
+ model.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[8], "flow")
+ model.edge.add(model.tabulated_rating_curve[5], model.basin[6], "flow")
+ model.edge.add(model.fractional_flow[6], model.pump[7], "flow")
+ model.edge.add(model.basin[8], model.basin[9], "flow")
+ model.edge.add(model.fractional_flow[7], model.basin[9], "flow")
+ model.edge.add(model.pump[9], model.linear_resistance[10], "flow")
+ model.edge.add(model.basin[11], model.linear_resistance[12], "flow")
+ model.edge.add(model.level_boundary[12], model.basin[3], "flow")
+ model.edge.add(model.linear_resistance[4], model.fractional_flow[13], "flow")
+ model.edge.add(model.tabulated_rating_curve[13], model.terminal[14], "flow")
+ model.edge.add(model.fractional_flow[15], model.basin[6], "flow")
+ model.edge.add(model.flow_boundary[16], model.basin[1], "flow")
+ model.edge.add(model.flow_boundary[10], model.level_boundary[17], "flow") model.edge.add(model.linear_resistance[
Let’s take a look at the model:
-
+
model.plot()
Write the model to a TOML and GeoPackage:
-
-= Path("data")
- datadir / "basic/ribasim.toml") model.write(datadir
+
+/ "basic/ribasim.toml") model.write(datadir
PosixPath('data/basic/ribasim.toml')
-
-
-2 Update the basic model with transient forcing
-This assumes you have already created the basic model with static forcing.
-
-import numpy as np
-import pandas as pd
-import ribasim
-import xarray as xr
-
-
-= ribasim.Model(filepath=datadir / "basic/ribasim.toml") model
-
-Can't read from data/basic/database.gpkg:Basin / area
-
-
-
-= pd.date_range(model.starttime, model.endtime)
- time = time.day_of_year.to_numpy()
- day_of_year = 24 * 60 * 60
- seconds_per_day = (
- evaporation -1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day
- (
- )= np.random.default_rng(seed=0)
- rng = (
- precipitation =-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day
- rng.lognormal(mean )
-
-We’ll use xarray to easily broadcast the values.
-
-= (
- timeseries
- pd.DataFrame(={
- data"node_id": 1,
- "time": pd.date_range(model.starttime, model.endtime),
- "drainage": 0.0,
- "potential_evaporation": evaporation,
- "infiltration": 0.0,
- "precipitation": precipitation,
- "urban_runoff": 0.0,
-
- }
- )"time")
- .set_index(
- .to_xarray()
- )
-= model.basin.static.df["node_id"].to_numpy()
- basin_ids = xr.DataArray(
- basin_nodes len(basin_ids)), coords={"node_id": basin_ids}, dims=["node_id"]
- np.ones(
- )= (timeseries * basin_nodes).to_dataframe().reset_index() forcing
-
-
-= pd.DataFrame(
- state ={
- data"node_id": basin_ids,
- "level": 1.4,
-
- } )
-
-
-= forcing
- model.basin.time.df = state model.basin.state.df
-
-
-/ "basic_transient/ribasim.toml") model.write(datadir
-
-PosixPath('data/basic_transient/ribasim.toml')
-
-
-Now run the model with ribasim basic_transient/ribasim.toml
. After running the model, read back the results:
-
-= pd.read_feather(datadir / "basic_transient/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )"level"].plot() df_basin_wide[
+Now run the model with ribasim basic/ribasim.toml
. After running the model, read back the results:
+
+= pd.read_feather(datadir / "basic/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )"level"].plot() df_basin_wide[
-
-= pd.read_feather(datadir / "basic_transient/results/flow.arrow")
- df_flow "edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
- df_flow["flow_m3d"] = df_flow.flow_rate * 86400
- df_flow[= df_flow.pivot_table(index="time", columns="edge", values="flow_m3d").plot()
- ax =(1.3, 1), title="Edge") ax.legend(bbox_to_anchor
+
+= pd.read_feather(datadir / "basic/results/flow.arrow")
+ df_flow "edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
+ df_flow["flow_m3d"] = df_flow.flow_rate * 86400
+ df_flow[= df_flow.pivot_table(index="time", columns="edge", values="flow_m3d").plot()
+ ax =(1.3, 1), title="Edge") ax.legend(bbox_to_anchor
-
-type(df_flow)
-
-pandas.core.frame.DataFrame
-
-
-
-3 Model with discrete control
+
+2 Model with discrete control
The model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve
, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.
-Set up the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: Basin
- (1.0, 1.0), # 2: Pump
- (1.0, -1.0), # 3: Pump
- (2.0, 0.0), # 4: LevelBoundary
- (-1.0, 0.0), # 5: TabulatedRatingCurve
- (-2.0, 0.0), # 6: Terminal
- (1.0, 0.0), # 7: DiscreteControl
- (
- ]
- )
-= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= [
- node_type "Basin",
- "Pump",
- "Pump",
- "LevelBoundary",
- "TabulatedRatingCurve",
- "Terminal",
- "DiscreteControl",
-
- ]
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type},
- data=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)
- from_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)
- to_id
-= 6 * ["flow"] + 2 * ["control"]
- edge_type
-= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={"from_node_id": from_id, "to_node_id": to_id, "edge_type": edge_type},
- data=lines,
- geometry="EPSG:28992",
- crs
- ) )
-
Setup the basins:
-
-= pd.DataFrame(
- profile ={
- data"node_id": [1, 1],
- "area": [1000.0, 1000.0],
- "level": [0.0, 1.0],
-
- }
- )
-= pd.DataFrame(data={"node_id": [1], "level": [20.0]})
- state
-= ribasim.Basin(profile=profile, state=state) basin
+
+= Model(starttime="2020-01-01 00:00:00", endtime="2021-01-01 00:00:00") model
+
+
+
+ model.basin.add(1, Point(0.0, 0.0)),
+ Node(
+ [=[1000.0, 1000.0], level=[0.0, 1.0]),
+ basin.Profile(area=[20.0]),
+ basin.State(level
+ ], )
Setup the discrete control:
-
-= pd.DataFrame(
- condition ={
- data"node_id": 3 * [7],
- "listen_feature_id": 3 * [1],
- "variable": 3 * ["level"],
- "greater_than": [5.0, 10.0, 15.0], # min, setpoint, max
-
- }
- )
-= pd.DataFrame(
- logic ={
- data"node_id": 5 * [7],
- "truth_state": ["FFF", "U**", "T*F", "**D", "TTT"],
- "control_state": ["in", "in", "none", "out", "out"],
-
- }
- )
-= ribasim.DiscreteControl(condition=condition, logic=logic) discrete_control
-
-The above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.
+
+
+ model.discrete_control.add(7, Point(1.0, 0.0)),
+ Node(
+ [
+ discrete_control.Condition(=[1, 1, 1],
+ listen_node_id=["Basin", "Basin", "Basin"],
+ listen_node_type=["level", "level", "level"],
+ variable=[5.0, 10.0, 15.0],
+ greater_than
+ ),
+ discrete_control.Logic(=["FFF", "U**", "T*F", "**D", "TTT"],
+ truth_state=["in", "in", "none", "out", "out"],
+ control_state
+ ),
+ ], )
+
+The above control logic can be summarized as follows:
+
+- If the level gets above the maximum, activate the control state “out” until the setpoint is reached;
+- If the level gets below the minimum, active the control state “in” until the setpoint is reached;
+- Otherwise activate the control state “none”.
+
Setup the pump:
-
-= ribasim.Pump(
- pump =pd.DataFrame(
- static={
- data"node_id": 3 * [2] + 3 * [3],
- "control_state": 2 * ["none", "in", "out"],
- "flow_rate": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],
-
- }
- ) )
+
+
+ model.pump.add(2, Point(1.0, 1.0)),
+ Node(=["none", "in", "out"], flow_rate=[0.0, 2e-3, 0.0])],
+ [pump.Static(control_state
+ )
+ model.pump.add(3, Point(1.0, -1.0)),
+ Node(=["none", "in", "out"], flow_rate=[0.0, 0.0, 2e-3])],
+ [pump.Static(control_state )
The pump data defines the following:
@@ -741,616 +551,426 @@ 3 Model with disc
Setup the level boundary:
-
-= ribasim.LevelBoundary(
- level_boundary =pd.DataFrame(data={"node_id": [4], "level": [10.0]})
- static )
+
+
+ model.level_boundary.add(4, Point(2.0, 0.0)), [level_boundary.Static(level=[10.0])]
+ Node( )
Setup the rating curve:
-
-= ribasim.TabulatedRatingCurve(
- rating_curve =pd.DataFrame(
- static={"node_id": 2 * [5], "level": [2.0, 15.0], "flow_rate": [0.0, 1e-3]}
- data
- ) )
+
+
+ model.tabulated_rating_curve.add(5, Point(-1.0, 0.0)),
+ Node(=[2.0, 15.0], flow_rate=[0.0, 1e-3])],
+ [tabulated_rating_curve.Static(level )
Setup the terminal:
-
-= ribasim.Terminal(static=pd.DataFrame(data={"node_id": [6]})) terminal
-
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=pump,
- pump=level_boundary,
- level_boundary=rating_curve,
- tabulated_rating_curve=terminal,
- terminal=discrete_control,
- discrete_control="2020-01-01 00:00:00",
- starttime="2021-01-01 00:00:00",
- endtime )
+
+6, Point(-2.0, 0.0))) model.terminal.add(Node(
+
+Setup edges:
+
+1], model.pump[3], "flow")
+ model.edge.add(model.basin[3], model.level_boundary[4], "flow")
+ model.edge.add(model.pump[4], model.pump[2], "flow")
+ model.edge.add(model.level_boundary[2], model.basin[1], "flow")
+ model.edge.add(model.pump[1], model.tabulated_rating_curve[5], "flow")
+ model.edge.add(model.basin[5], model.terminal[6], "flow")
+ model.edge.add(model.tabulated_rating_curve[7], model.pump[2], "control")
+ model.edge.add(model.discrete_control[7], model.pump[3], "control") model.edge.add(model.discrete_control[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Listen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.
-
-= Path("data")
- datadir / "level_setpoint_with_minmax/ribasim.toml") model.write(datadir
-
+
+= Path("data")
+ datadir / "level_setpoint_with_minmax/ribasim.toml") model.write(datadir
+
PosixPath('data/level_setpoint_with_minmax/ribasim.toml')
Now run the model with level_setpoint_with_minmax/ribasim.toml
. After running the model, read back the results:
-
-from matplotlib.dates import date2num
-
-= pd.read_feather(datadir / "level_setpoint_with_minmax/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )
-= df_basin_wide["level"].plot()
- ax
-= model.discrete_control.condition.df.greater_than
- greater_than
-
- ax.hlines(
- greater_than,0],
- df_basin.time[max(),
- df_basin.time.=1,
- lw="--",
- ls="k",
- color
- )
-= pd.read_feather(
- df_control / "level_setpoint_with_minmax/results/control.arrow"
- datadir
- )
-= ax.get_ybound()
- y_min, y_max 2], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
- ax.fill_between(df_control.time[:2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
- ax.fill_between(df_control.time[
-
- ax.set_xticks(
- date2num(df_control.time).tolist(),
- df_control.control_state.tolist(),=50,
- rotation
- )
-"min", "setpoint", "max"])
- ax.set_yticks(greater_than, ["level")
- ax.set_ylabel( plt.show()
+
+from matplotlib.dates import date2num
+
+= pd.read_feather(datadir / "level_setpoint_with_minmax/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )
+= df_basin_wide["level"].plot()
+ ax
+= model.discrete_control.condition.df.greater_than
+ greater_than
+
+ ax.hlines(
+ greater_than,0],
+ df_basin.time[max(),
+ df_basin.time.=1,
+ lw="--",
+ ls="k",
+ color
+ )
+= pd.read_feather(
+ df_control / "level_setpoint_with_minmax/results/control.arrow"
+ datadir
+ )
+= ax.get_ybound()
+ y_min, y_max 2], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
+ ax.fill_between(df_control.time[:2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
+ ax.fill_between(df_control.time[
+
+ ax.set_xticks(
+ date2num(df_control.time).tolist(),
+ df_control.control_state.tolist(),=50,
+ rotation
+ )
+"min", "setpoint", "max"])
+ ax.set_yticks(greater_than, ["level")
+ ax.set_ylabel( plt.show()
The highlighted regions show where a pump is active.
-Let’s print an overview of what happened with control:
-
-
- model.print_discrete_control_record(/ "level_setpoint_with_minmax/results/control.arrow"
- datadir )
-
-0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:
- For node ID 1 (Basin): level > 5.0
- For node ID 1 (Basin): level > 10.0
- For node ID 1 (Basin): level > 15.0
-
- This yielded control state "out":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan
-
-1. At 2020-02-08 19:07:33.550000 the control node with ID 7 reached truth state TFF:
- For node ID 1 (Basin): level > 5.0
- For node ID 1 (Basin): level < 10.0
- For node ID 1 (Basin): level < 15.0
-
- This yielded control state "none":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
-
-2. At 2020-07-05 09:06:07.383000 the control node with ID 7 reached truth state FFF:
- For node ID 1 (Basin): level < 5.0
- For node ID 1 (Basin): level < 10.0
- For node ID 1 (Basin): level < 15.0
-
- This yielded control state "in":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
-
-3. At 2020-08-11 06:17:03.985000 the control node with ID 7 reached truth state TTF:
- For node ID 1 (Basin): level > 5.0
- For node ID 1 (Basin): level > 10.0
- For node ID 1 (Basin): level < 15.0
-
- This yielded control state "none":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
-
-
-
-Note that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.
-
-4 Model with PID control
-Set up the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: FlowBoundary
- (1.0, 0.0), # 2: Basin
- (2.0, 0.5), # 3: Pump
- (3.0, 0.0), # 4: LevelBoundary
- (1.5, 1.0), # 5: PidControl
- (2.0, -0.5), # 6: outlet
- (1.5, -1.0), # 7: PidControl
- (
- ]
- )
-= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= [
- node_type "FlowBoundary",
- "Basin",
- "Pump",
- "LevelBoundary",
- "PidControl",
- "Outlet",
- "PidControl",
-
- ]
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type},
- data=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)
- from_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)
- to_id
-= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": 5 * ["flow"] + 2 * ["control"],
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
+
+3 Model with PID control
+Set up the model:
+
+= Model(
+ model ="2020-01-01 00:00:00",
+ starttime="2020-12-01 00:00:00",
+ endtime )
Setup the basins:
-
-= pd.DataFrame(
- profile ={"node_id": [2, 2], "level": [0.0, 1.0], "area": [1000.0, 1000.0]}
- data
- )
-= pd.DataFrame(
- state ={
- data"node_id": [2],
- "level": [6.0],
-
- }
- )
-= ribasim.Basin(profile=profile, state=state) basin
+
+
+ model.basin.add(2, Point(1.0, 0.0)),
+ Node(=[1000.0, 1000.0], level=[0.0, 1.0]), basin.State(level=[6.0])],
+ [basin.Profile(area )
Setup the pump:
-
-= ribasim.Pump(
- pump =pd.DataFrame(
- static={
- data"node_id": [3],
- "flow_rate": [0.0], # Will be overwritten by PID controller
-
- }
- ) )
+
+
+ model.pump.add(3, Point(2.0, 0.5)),
+ Node(=[0.0])], # Will be overwritten by PID controller
+ [pump.Static(flow_rate )
Setup the outlet:
-
-= ribasim.Outlet(
- outlet =pd.DataFrame(
- static={
- data"node_id": [6],
- "flow_rate": [0.0], # Will be overwritten by PID controller
-
- }
- ) )
+
+
+ model.outlet.add(6, Point(2.0, -0.5)),
+ Node(=[0.0])], # Will be overwritten by PID controller
+ [outlet.Static(flow_rate )
Setup flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(data={"node_id": [1], "flow_rate": [1e-3]})
- static )
+
+
+ model.flow_boundary.add(1, Point(0.0, 0.0)),
+ Node(=[1e-3])],
+ [flow_boundary.Static(flow_rate )
Setup flow boundary:
-
-= ribasim.LevelBoundary(
- level_boundary =pd.DataFrame(
- static={
- data"node_id": [4],
- "level": [1.0], # Not relevant
-
- }
- ) )
+
+
+ model.level_boundary.add(4, Point(3.0, 0.0)),
+ Node(=[1])],
+ [level_boundary.Static(level )
Setup PID control:
-
-= ribasim.PidControl(
- pid_control =pd.DataFrame(
- time={
- data"node_id": 4 * [5, 7],
- "time": [
- "2020-01-01 00:00:00",
- "2020-01-01 00:00:00",
- "2020-05-01 00:00:00",
- "2020-05-01 00:00:00",
- "2020-07-01 00:00:00",
- "2020-07-01 00:00:00",
- "2020-12-01 00:00:00",
- "2020-12-01 00:00:00",
-
- ],"listen_node_id": 4 * [2, 2],
- "target": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],
- "proportional": 4 * [-1e-3, 1e-3],
- "integral": 4 * [-1e-7, 1e-7],
- "derivative": 4 * [0.0, 0.0],
-
- }
- ) )
+
+
+ model.pid_control.add(5, Point(1.5, 1.0)),
+ Node(
+ [
+ pid_control.Time(=[
+ time"2020-01-01 00:00:00",
+ "2020-05-01 00:00:00",
+ "2020-07-01 00:00:00",
+ "2020-12-01 00:00:00",
+
+ ],=[2, 2, 2, 2],
+ listen_node_id=["Basin", "Basin", "Basin", "Basin"],
+ listen_node_type=[5.0, 5.0, 7.5, 7.5],
+ target=[-1e-3, 1e-3, 1e-3, 1e-3],
+ proportional=[-1e-7, 1e-7, -1e-7, 1e-7],
+ integral=[0.0, 0.0, 0.0, 0.0],
+ derivative
+ )
+ ],
+ )
+ model.pid_control.add(7, Point(1.5, -1.0)),
+ Node(
+ [
+ pid_control.Time(=[
+ time"2020-01-01 00:00:00",
+ "2020-05-01 00:00:00",
+ "2020-07-01 00:00:00",
+ "2020-12-01 00:00:00",
+
+ ],=[2, 2, 2, 2],
+ listen_node_id=["Basin", "Basin", "Basin", "Basin"],
+ listen_node_type=[5.0, 5.0, 7.5, 7.5],
+ target=[-1e-3, 1e-3, 1e-3, 1e-3],
+ proportional=[-1e-7, 1e-7, -1e-7, 1e-7],
+ integral=[0.0, 0.0, 0.0, 0.0],
+ derivative
+ )
+ ], )
Note that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=flow_boundary,
- flow_boundary=level_boundary,
- level_boundary=pump,
- pump=outlet,
- outlet=pid_control,
- pid_control="2020-01-01 00:00:00",
- starttime="2020-12-01 00:00:00",
- endtime )
+Setup the edges:
+
+1], model.basin[2], "flow")
+ model.edge.add(model.flow_boundary[2], model.pump[3], "flow")
+ model.edge.add(model.basin[3], model.level_boundary[4], "flow")
+ model.edge.add(model.pump[4], model.outlet[6], "flow")
+ model.edge.add(model.level_boundary[6], model.basin[2], "flow")
+ model.edge.add(model.outlet[5], model.pump[3], "control")
+ model.edge.add(model.pid_control[7], model.outlet[6], "control") model.edge.add(model.pid_control[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Write the model to a TOML and GeoPackage:
-
-= Path("data")
- datadir / "pid_control/ribasim.toml") model.write(datadir
-
+
+= Path("data")
+ datadir / "pid_control/ribasim.toml") model.write(datadir
+
PosixPath('data/pid_control/ribasim.toml')
Now run the model with ribasim pid_control/ribasim.toml
. After running the model, read back the results:
-
-from matplotlib.dates import date2num
-
-= pd.read_feather(datadir / "pid_control/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )= df_basin_wide["level"].plot()
- ax "level [m]")
- ax.set_ylabel(
-# Plot target level
-= model.pid_control.time.df.target.to_numpy()[:4]
- level_demands = date2num(model.pid_control.time.df.time)[:4]
- times ="k", ls=":", label="target level")
- ax.plot(times, level_demands, colorpass
+
+from matplotlib.dates import date2num
+
+= pd.read_feather(datadir / "pid_control/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )= df_basin_wide["level"].plot()
+ ax "level [m]")
+ ax.set_ylabel(
+# Plot target level
+= model.pid_control.time.df.target.to_numpy()[:4]
+ level_demands = date2num(model.pid_control.time.df.time)[:4]
+ times ="k", ls=":", label="target level")
+ ax.plot(times, level_demands, colorpass
-
-5 Model with allocation (user demand)
-Setup the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: FlowBoundary
- (1.0, 0.0), # 2: Basin
- (1.0, 1.0), # 3: UserDemand
- (2.0, 0.0), # 4: LinearResistance
- (3.0, 0.0), # 5: Basin
- (3.0, 1.0), # 6: UserDemand
- (4.0, 0.0), # 7: TabulatedRatingCurve
- (4.5, 0.0), # 8: FractionalFlow
- (4.5, 0.5), # 9: FractionalFlow
- (5.0, 0.0), # 10: Terminal
- (4.5, 0.25), # 11: DiscreteControl
- (4.5, 1.0), # 12: Basin
- (5.0, 1.0), # 13: UserDemand
- (
- ]
- )= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= [
- node_type "FlowBoundary",
- "Basin",
- "UserDemand",
- "LinearResistance",
- "Basin",
- "UserDemand",
- "TabulatedRatingCurve",
- "FractionalFlow",
- "FractionalFlow",
- "Terminal",
- "DiscreteControl",
- "Basin",
- "UserDemand",
-
- ]
-# All nodes belong to allocation network id 1
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type, "subnetwork_id": 1},
- data=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array(
- from_id 1, 2, 2, 4, 5, 5, 7, 3, 6, 7, 8, 9, 12, 13, 11, 11],
- [=np.int64,
- dtype
- )= np.array(
- to_id 2, 3, 4, 5, 6, 7, 8, 2, 5, 9, 10, 12, 13, 10, 8, 9],
- [=np.int64,
- dtype
- )# Denote the first edge, 1 => 2, as a source edge for
-# allocation network 1
-= len(from_id) * [None]
- subnetwork_id 0] = 1
- subnetwork_id[= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": (len(from_id) - 2) * ["flow"] + 2 * ["control"],
- "subnetwork_id": subnetwork_id,
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
+
+4 Model with allocation (user demand)
+Setup a model:
+
+= Model(
+ model ="2020-01-01 00:00:00",
+ starttime="2020-01-20 00:00:00",
+ endtime )
Setup the basins:
-
-= pd.DataFrame(
- profile ={
- data"node_id": [2, 2, 5, 5, 12, 12],
- "area": 300_000.0,
- "level": 3 * [0.0, 1.0],
-
- }
- )
-= pd.DataFrame(data={"node_id": [2, 5, 12], "level": 1.0})
- state
-= ribasim.Basin(profile=profile, state=state) basin
+
+= [
+ basin_data =[300_000.0, 300_000.0], level=[0.0, 1.0]),
+ basin.Profile(area=[1.0]),
+ basin.State(level
+ ]
+
+ model.basin.add(2, Point(1.0, 0.0), subnetwork_id=1),
+ Node(
+ basin_data,
+ )
+ model.basin.add(5, Point(3.0, 0.0), subnetwork_id=1),
+ Node(
+ basin_data,
+ )
+ model.basin.add(12, Point(4.5, 1.0), subnetwork_id=1),
+ Node(
+ basin_data, )
Setup the flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(
- static={
- data"node_id": [1],
- "flow_rate": 2.0,
-
- }
- ) )
+
+
+ model.flow_boundary.add(1, Point(0.0, 0.0), subnetwork_id=1), [flow_boundary.Static(flow_rate=[2.0])]
+ Node( )
Setup the linear resistance:
-
-= ribasim.LinearResistance(
- linear_resistance =pd.DataFrame(
- static={
- data"node_id": [4],
- "resistance": 0.06,
-
- }
- ) )
+
+
+ model.linear_resistance.add(4, Point(2.0, 0.0), subnetwork_id=1),
+ Node(=[0.06])],
+ [linear_resistance.Static(resistance )
Setup the tabulated rating curve:
-
-= ribasim.TabulatedRatingCurve(
- tabulated_rating_curve =pd.DataFrame(
- static={
- data"node_id": 7,
- "level": [0.0, 0.5, 1.0],
- "flow_rate": [0.0, 0.0, 2.0],
-
- }
- ) )
+
+
+ model.tabulated_rating_curve.add(7, Point(4.0, 0.0), subnetwork_id=1),
+ Node(=[0.0, 0.5, 1.0], flow_rate=[0.0, 0.0, 2.0])],
+ [tabulated_rating_curve.Static(level )
Setup the fractional flow:
-
-= ribasim.FractionalFlow(
- fractional_flow =pd.DataFrame(
- static={
- data"node_id": [8, 8, 9, 9],
- "fraction": [0.6, 0.9, 0.4, 0.1],
- "control_state": ["divert", "close", "divert", "close"],
-
- }
- ) )
+
+
+ model.fractional_flow.add(8, Point(4.5, 0.0), subnetwork_id=1),
+ Node(=[0.6, 0.9], control_state=["divert", "close"])],
+ [fractional_flow.Static(fraction
+ )
+ model.fractional_flow.add(9, Point(4.5, 0.5), subnetwork_id=1),
+ Node(=[0.4, 0.1], control_state=["divert", "close"])],
+ [fractional_flow.Static(fraction )
Setup the terminal:
-
-= ribasim.Terminal(
- terminal =pd.DataFrame(
- static={
- data"node_id": [10],
-
- }
- ) )
+
+10, Point(5.0, 0.0), subnetwork_id=1)) model.terminal.add(Node(
Setup the discrete control:
-
-= pd.DataFrame(
- condition ={
- data"node_id": [11],
- "listen_feature_id": 5,
- "variable": "level",
- "greater_than": 0.52,
-
- }
- )
-= pd.DataFrame(
- logic ={
- data"node_id": 11,
- "truth_state": ["T", "F"],
- "control_state": ["divert", "close"],
-
- }
- )
-= ribasim.DiscreteControl(condition=condition, logic=logic) discrete_control
+
+
+ model.discrete_control.add(11, Point(4.5, 0.25), subnetwork_id=1),
+ Node(
+ [
+ discrete_control.Condition(=[5],
+ listen_node_id=["Basin"],
+ listen_node_type=["level"],
+ variable=[0.52],
+ greater_than
+ ),
+ discrete_control.Logic(=["T", "F"], control_state=["divert", "close"]
+ truth_state
+ ),
+ ], )
Setup the users:
-
-= ribasim.UserDemand(
- user_demand =pd.DataFrame(
- static={
- data"node_id": [6, 13],
- "demand": [1.5, 1.0],
- "return_factor": 0.0,
- "min_level": -1.0,
- "priority": [1, 3],
-
- }
- ),=pd.DataFrame(
- time={
- data"node_id": [3, 3, 3, 3],
- "demand": [0.0, 1.0, 1.2, 1.2],
- "priority": [1, 1, 2, 2],
- "return_factor": 0.0,
- "min_level": -1.0,
- "time": 2 * ["2020-01-01 00:00:00", "2020-01-20 00:00:00"],
-
- }
- ), )
+
+
+ model.user_demand.add(6, Point(3.0, 1.0), subnetwork_id=1),
+ Node(
+ [
+ user_demand.Static(=[1.5], return_factor=[0.0], min_level=[-1.0], priority=[1]
+ demand
+ )
+ ],
+ )
+ model.user_demand.add(13, Point(5.0, 1.0), subnetwork_id=1),
+ Node(
+ [
+ user_demand.Static(=[1.0], return_factor=[0.0], min_level=[-1.0], priority=[3]
+ demand
+ )
+ ],
+ )
+ model.user_demand.add(3, Point(1.0, 1.0), subnetwork_id=1),
+ Node(
+ [
+ user_demand.Time(=[0.0, 1.0, 1.2, 1.2],
+ demand=[0.0, 0.0, 0.0, 0.0],
+ return_factor=[-1.0, -1.0, -1.0, -1.0],
+ min_level=[1, 1, 2, 2],
+ priority=2 * ["2020-01-01 00:00:00", "2020-01-20 00:00:00"],
+ time
+ )
+ ], )
Setup the allocation:
-
-= ribasim.Allocation(use_allocation=True, timestep=86400) allocation
+
+= ribasim.Allocation(use_allocation=True, timestep=86400) model.allocation
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=flow_boundary,
- flow_boundary=linear_resistance,
- linear_resistance=tabulated_rating_curve,
- tabulated_rating_curve=terminal,
- terminal=user_demand,
- user_demand=discrete_control,
- discrete_control=fractional_flow,
- fractional_flow=allocation,
- allocation="2020-01-01 00:00:00",
- starttime="2020-01-20 00:00:00",
- endtime )
+Setup the edges:
+
+1], model.basin[2], "flow", subnetwork_id=1)
+ model.edge.add(model.flow_boundary[2], model.user_demand[3], "flow")
+ model.edge.add(model.basin[2], model.linear_resistance[4], "flow")
+ model.edge.add(model.basin[4], model.basin[5], "flow")
+ model.edge.add(model.linear_resistance[5], model.user_demand[6], "flow")
+ model.edge.add(model.basin[5], model.tabulated_rating_curve[7], "flow")
+ model.edge.add(model.basin[7], model.fractional_flow[8], "flow")
+ model.edge.add(model.tabulated_rating_curve[3], model.basin[2], "flow")
+ model.edge.add(model.user_demand[6], model.basin[5], "flow")
+ model.edge.add(model.user_demand[7], model.fractional_flow[9], "flow")
+ model.edge.add(model.tabulated_rating_curve[8], model.terminal[10], "flow")
+ model.edge.add(model.fractional_flow[9], model.basin[12], "flow")
+ model.edge.add(model.fractional_flow[12], model.user_demand[13], "flow")
+ model.edge.add(model.basin[13], model.terminal[10], "flow")
+ model.edge.add(model.user_demand[11], model.fractional_flow[8], "control")
+ model.edge.add(model.discrete_control[11], model.fractional_flow[9], "control") model.edge.add(model.discrete_control[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Write the model to a TOML and GeoPackage:
-
-= Path("data")
- datadir / "allocation_example/ribasim.toml") model.write(datadir
-
+
+= Path("data")
+ datadir / "allocation_example/ribasim.toml") model.write(datadir
+
PosixPath('data/allocation_example/ribasim.toml')
Now run the model with ribasim allocation_example/ribasim.toml
. After running the model, read back the results:
-
-import matplotlib.ticker as plticker
-
-= pd.read_feather(datadir / "allocation_example/results/allocation.arrow")
- df_allocation = df_allocation.pivot_table(
- df_allocation_wide ="time",
- index=["node_type", "node_id", "priority"],
- columns=["demand", "allocated", "realized"],
- values
- )= df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]
- df_allocation_wide
-= plt.subplots(1, 3, figsize=(8, 5))
- fig, axs
-"demand"].plot(ax=axs[0], ls=":")
- df_allocation_wide["allocated"].plot(ax=axs[1], ls="--")
- df_allocation_wide["realized"].plot(ax=axs[2])
- df_allocation_wide[
-
- fig.tight_layout()= plticker.MultipleLocator(2)
- loc
-0].set_ylabel("level [m]")
- axs[
-for ax, title in zip(axs, ["Demand", "Allocated", "Abstracted"]):
-
- ax.set_title(title)0.0, 1.6)
- ax.set_ylim( ax.xaxis.set_major_locator(loc)
+
+import matplotlib.ticker as plticker
+
+= pd.read_feather(datadir / "allocation_example/results/allocation.arrow")
+ df_allocation = df_allocation.pivot_table(
+ df_allocation_wide ="time",
+ index=["node_type", "node_id", "priority"],
+ columns=["demand", "allocated", "realized"],
+ values
+ )= df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]
+ df_allocation_wide
+= plt.subplots(1, 3, figsize=(8, 5))
+ fig, axs
+"demand"].plot(ax=axs[0], ls=":")
+ df_allocation_wide["allocated"].plot(ax=axs[1], ls="--")
+ df_allocation_wide["realized"].plot(ax=axs[2])
+ df_allocation_wide[
+
+ fig.tight_layout()= plticker.MultipleLocator(2)
+ loc
+0].set_ylabel("level [m]")
+ axs[
+for ax, title in zip(axs, ["Demand", "Allocated", "Abstracted"]):
+
+ ax.set_title(title)0.0, 1.6)
+ ax.set_ylim( ax.xaxis.set_major_locator(loc)
@@ -1360,202 +980,171 @@ 5 Model with allo
Abstraction behaves somewhat erratically at the start of the simulation. This is because allocation is based on flows computed in the physical layer, and at the start of the simulation these are not known yet.
Although there is a plotted line for abstraction per priority, abstraction is actually accumulated over all priorities per user.
-
-= pd.read_feather(datadir / "allocation_example/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )
-= df_basin_wide["level"].plot()
- ax "Basin levels")
- ax.set_title("level [m]") ax.set_ylabel(
-
+
+= pd.read_feather(datadir / "allocation_example/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )
+= df_basin_wide["level"].plot()
+ ax "Basin levels")
+ ax.set_title("level [m]") ax.set_ylabel(
+
Text(0, 0.5, 'level [m]')
-
-6 Model with allocation (basin supply/demand)
-Setup the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: FlowBoundary
- (1.0, 0.0), # 2: Basin
- (2.0, 0.0), # 3: UserDemand
- (1.0, -1.0), # 4: LevelDemand
- (2.0, -1.0), # 5: Basin
- (
- ]
- )= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= ["FlowBoundary", "Basin", "UserDemand", "LevelDemand", "Basin"]
- node_type
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={
- data"node_type": node_type,
- "subnetwork_id": 5 * [2],
-
- },=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array([1, 2, 4, 3, 4])
- from_id = np.array([2, 3, 2, 5, 5])
- to_id = ["flow", "flow", "control", "flow", "control"]
- edge_type = [2, None, None, None, None]
- subnetwork_id
-= node.geometry_from_connectivity(from_id.tolist(), to_id.tolist())
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": edge_type,
- "subnetwork_id": subnetwork_id,
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
+
+5 Model with allocation (basin supply/demand)
+Setup a model:
+
+= ribasim.Model(
+ model ="2020-01-01 00:00:00",
+ starttime="2020-02-01 00:00:00",
+ endtime )
Setup the basins:
-
-= pd.DataFrame(
- profile ={"node_id": [2, 2, 5, 5], "area": 1e3, "level": [0.0, 1.0, 0.0, 1.0]}
- data
- )= pd.DataFrame(
- static ={
- data"node_id": [5],
- "drainage": 0.0,
- "potential_evaporation": 0.0,
- "infiltration": 0.0,
- "precipitation": 0.0,
- "urban_runoff": 0.0,
-
- }
- )= pd.DataFrame(
- time ={
- data"node_id": 2,
- "time": ["2020-01-01 00:00:00", "2020-01-16 00:00:00"],
- "drainage": 0.0,
- "potential_evaporation": 0.0,
- "infiltration": 0.0,
- "precipitation": [1e-6, 0.0],
- "urban_runoff": 0.0,
-
- },
- )
-= pd.DataFrame(data={"node_id": [2, 5], "level": 0.5})
- state = ribasim.Basin(profile=profile, static=static, time=time, state=state) basin
+
+= [
+ basin_data =[1e3, 1e3], level=[0.0, 1.0]),
+ basin.Profile(area=[0.5]),
+ basin.State(level
+ ]
+ model.basin.add(2, Point(1.0, 0.0)),
+ Node(
+ [*basin_data,
+
+ basin.Time(=["2020-01-01 00:00:00", "2020-01-16 00:00:00"],
+ time=[0.0, 0.0],
+ drainage=[0.0, 0.0],
+ potential_evaporation=[0.0, 0.0],
+ infiltration=[1e-6, 0.0],
+ precipitation=[0.0, 0.0],
+ urban_runoff
+ ),
+ ],
+ )
+ model.basin.add(5, Point(2.0, -1.0)),
+ Node(
+ [*basin_data,
+
+ basin.Static(=[0.0],
+ drainage=[0.0],
+ potential_evaporation=[0.0],
+ infiltration=[0.0],
+ precipitation=[0.0],
+ urban_runoff
+ ),
+ ],
+ )= pd.DataFrame(
+ profile ={"node_id": [2, 2, 5, 5], "area": 1e3, "level": [0.0, 1.0, 0.0, 1.0]}
+ data )
Setup the flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(data={"node_id": [1], "flow_rate": 1e-3})
- static )
+
+
+ model.flow_boundary.add(1, Point(0.0, 0.0)), [flow_boundary.Static(flow_rate=[1e-3])]
+ Node( )
Setup allocation level control:
-
-= ribasim.LevelDemand(
- level_demand =pd.DataFrame(
- static={"node_id": [4], "priority": 1, "min_level": 1.0, "max_level": 1.5}
- data
- ) )
+
+
+ model.level_demand.add(4, Point(1.0, -1.0)),
+ Node(=[1], min_level=[1.0], max_level=[1.5])],
+ [level_demand.Static(priority )
Setup the users:
-
-= ribasim.UserDemand(
- user_demand =pd.DataFrame(
- static={
- data"node_id": [3],
- "priority": [2],
- "demand": [1.5e-3],
- "return_factor": [0.2],
- "min_level": [0.2],
-
- }
- ) )
+
+
+ model.user_demand.add(3, Point(2.0, 0.0)),
+ Node(
+ [
+ user_demand.Static(=[2], demand=[1.5e-3], return_factor=[0.2], min_level=[0.2]
+ priority
+ )
+ ], )
Setup the allocation:
-
-= ribasim.Allocation(use_allocation=True, timestep=1e5) allocation
+
+= ribasim.Allocation(use_allocation=True, timestep=1e5) model.allocation
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(node=node, edge=edge),
- network=basin,
- basin=flow_boundary,
- flow_boundary=level_demand,
- level_demand=user_demand,
- user_demand=allocation,
- allocation="2020-01-01 00:00:00",
- starttime="2020-02-01 00:00:00",
- endtime )
+Setup the edges:
+
+1], model.basin[2], "flow", subnetwork_id=2)
+ model.edge.add(model.flow_boundary[2], model.user_demand[3], "flow")
+ model.edge.add(model.basin[4], model.basin[2], "control")
+ model.edge.add(model.level_demand[3], model.basin[5], "flow")
+ model.edge.add(model.user_demand[4], model.basin[5], "control") model.edge.add(model.level_demand[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Write the model to a TOML and GeoPackage:
-
-/ "level_demand/ribasim.toml") model.write(datadir
-
+
+/ "level_demand/ribasim.toml") model.write(datadir
+
PosixPath('data/level_demand/ribasim.toml')
Now run the model with ribasim level_demand/ribasim.toml
. After running the model, read back the results:
-
-= pd.read_feather(datadir / "level_demand/results/basin.arrow")
- df_basin = df_basin[df_basin.node_id == 2]
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )= df_basin_wide["level"].plot()
- ax = (
- where_allocation - df_basin_wide.index[0]
- df_basin_wide.index % model.allocation.timestep == 0
- ).total_seconds() 0] = False
- where_allocation["level"].plot(
- df_basin_wide[where_allocation][="o",
- style=ax,
- ax
- )"level [m]") ax.set_ylabel(
-
+
+= pd.read_feather(datadir / "level_demand/results/basin.arrow")
+ df_basin = df_basin[df_basin.node_id == 2]
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )= df_basin_wide["level"].plot()
+ ax = (
+ where_allocation - df_basin_wide.index[0]
+ df_basin_wide.index % model.allocation.timestep == 0
+ ).total_seconds() 0] = False
+ where_allocation["level"].plot(
+ df_basin_wide[where_allocation][="o",
+ style=ax,
+ ax
+ )"level [m]") ax.set_ylabel(
+
Text(0, 0.5, 'level [m]')
In the plot above, the line denotes the level of Basin #2 over time and the dots denote the times at which allocation optimization was run, with intervals of \(\Delta t_{\text{alloc}}\). The Basin level is a piecewise linear function of time, with several stages explained below.
-Constants: - \(d\): UserDemand #3 demand, - \(\phi\): Basin #2 precipitation rate, - \(q\): LevelBoundary flow.
-Stages: - In the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \(q + \phi - d\); - In the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \(q + \phi\); - In the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \(\phi\); - In the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \(q + \phi - d\); - At the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \(q - d\). - In the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.
+Constants:
+
+- \(d\): UserDemand #3 demand,
+- \(\phi\): Basin #2 precipitation rate,
+- \(q\): LevelBoundary flow.
+
+Stages:
+
+- In the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \(q + \phi - d\);
+- In the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \(q + \phi\);
+- In the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \(\phi\);
+- In the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \(q + \phi - d\);
+- At the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \(q - d\).
+- In the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.
+
diff --git a/python/examples_files/figure-html/cell-16-output-1.png b/python/examples_files/figure-html/cell-16-output-1.png
index 5494dfd59..c10d22b42 100644
Binary files a/python/examples_files/figure-html/cell-16-output-1.png and b/python/examples_files/figure-html/cell-16-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-26-output-1.png b/python/examples_files/figure-html/cell-19-output-1.png
similarity index 100%
rename from python/examples_files/figure-html/cell-26-output-1.png
rename to python/examples_files/figure-html/cell-19-output-1.png
diff --git a/python/examples_files/figure-html/cell-20-output-1.png b/python/examples_files/figure-html/cell-20-output-1.png
new file mode 100644
index 000000000..0c0a411f7
Binary files /dev/null and b/python/examples_files/figure-html/cell-20-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-27-output-1.png b/python/examples_files/figure-html/cell-27-output-1.png
deleted file mode 100644
index 4a7e40f6c..000000000
Binary files a/python/examples_files/figure-html/cell-27-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-29-output-1.png b/python/examples_files/figure-html/cell-29-output-1.png
new file mode 100644
index 000000000..72ad8364f
Binary files /dev/null and b/python/examples_files/figure-html/cell-29-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-32-output-1.png b/python/examples_files/figure-html/cell-32-output-1.png
new file mode 100644
index 000000000..8e5ebf24c
Binary files /dev/null and b/python/examples_files/figure-html/cell-32-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-38-output-1.png b/python/examples_files/figure-html/cell-38-output-1.png
deleted file mode 100644
index fae390f9d..000000000
Binary files a/python/examples_files/figure-html/cell-38-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-41-output-1.png b/python/examples_files/figure-html/cell-41-output-1.png
index 8e5ebf24c..bf3b22cb6 100644
Binary files a/python/examples_files/figure-html/cell-41-output-1.png and b/python/examples_files/figure-html/cell-41-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-44-output-1.png b/python/examples_files/figure-html/cell-44-output-1.png
new file mode 100644
index 000000000..0da7ebb3e
Binary files /dev/null and b/python/examples_files/figure-html/cell-44-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-52-output-1.png b/python/examples_files/figure-html/cell-52-output-1.png
deleted file mode 100644
index 9a713e59a..000000000
Binary files a/python/examples_files/figure-html/cell-52-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-55-output-1.png b/python/examples_files/figure-html/cell-55-output-1.png
deleted file mode 100644
index 220cc31bf..000000000
Binary files a/python/examples_files/figure-html/cell-55-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-56-output-1.png b/python/examples_files/figure-html/cell-56-output-1.png
new file mode 100644
index 000000000..284be855d
Binary files /dev/null and b/python/examples_files/figure-html/cell-56-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-59-output-1.png b/python/examples_files/figure-html/cell-59-output-1.png
new file mode 100644
index 000000000..efcc16cad
Binary files /dev/null and b/python/examples_files/figure-html/cell-59-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-72-output-2.png b/python/examples_files/figure-html/cell-60-output-2.png
similarity index 100%
rename from python/examples_files/figure-html/cell-72-output-2.png
rename to python/examples_files/figure-html/cell-60-output-2.png
diff --git a/python/examples_files/figure-html/cell-68-output-1.png b/python/examples_files/figure-html/cell-68-output-1.png
index 5d1d3c453..2226321f0 100644
Binary files a/python/examples_files/figure-html/cell-68-output-1.png and b/python/examples_files/figure-html/cell-68-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-71-output-1.png b/python/examples_files/figure-html/cell-71-output-1.png
deleted file mode 100644
index 54374cc99..000000000
Binary files a/python/examples_files/figure-html/cell-71-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-71-output-2.png b/python/examples_files/figure-html/cell-71-output-2.png
new file mode 100644
index 000000000..7bd0a8a26
Binary files /dev/null and b/python/examples_files/figure-html/cell-71-output-2.png differ
diff --git a/python/examples_files/figure-html/cell-81-output-1.png b/python/examples_files/figure-html/cell-81-output-1.png
deleted file mode 100644
index 7f7bd21fb..000000000
Binary files a/python/examples_files/figure-html/cell-81-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-84-output-2.png b/python/examples_files/figure-html/cell-84-output-2.png
deleted file mode 100644
index 015320f77..000000000
Binary files a/python/examples_files/figure-html/cell-84-output-2.png and /dev/null differ
diff --git a/python/reference/Edge.html b/python/reference/Edge.html
deleted file mode 100644
index b3819ec07..000000000
--- a/python/reference/Edge.html
+++ /dev/null
@@ -1,574 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/DiscreteControl.html b/python/reference/EdgeTable.html
similarity index 98%
rename from python/reference/DiscreteControl.html
rename to python/reference/EdgeTable.html
index 3b060a1f9..3bcb476a3 100644
--- a/python/reference/DiscreteControl.html
+++ b/python/reference/EdgeTable.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,10 @@ On this page
-
-1 DiscreteControl
-DiscreteControl()
+
+1 EdgeTable
+EdgeTable(self, **kwargs)
+Defines the connections between nodes.
diff --git a/python/reference/LevelBoundary.html b/python/reference/LevelBoundary.html
deleted file mode 100644
index f1da07f3a..000000000
--- a/python/reference/LevelBoundary.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/LinearResistance.html b/python/reference/LinearResistance.html
deleted file mode 100644
index cb825ff89..000000000
--- a/python/reference/LinearResistance.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Model.html b/python/reference/Model.html
index 2e7f99c73..4c87bfaef 100644
--- a/python/reference/Model.html
+++ b/python/reference/Model.html
@@ -130,10 +130,7 @@
On this page
- - 1 Model
-
- - 1.1 Parameters
-
+ - 1 Model
@@ -146,163 +143,8 @@ On this page
1 Model
Model()
-A full Ribasim model schematisation with all input.
-Ribasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.
-
-1.1 Parameters
-
-
-
-
-
-
-
-
-
-Name
-Type
-Description
-Default
-
-
-
-
-starttime
-datetime.datetime.datetime
-Starting time of the simulation.
-required
-
-
-endtime
-datetime.datetime.datetime
-End time of the simulation.
-required
-
-
-input_dir
-
-The directory of the input files.
-required
-
-
-results_dir
-
-The directory of the results files.
-required
-
-
-network
-
-Class containing the topology (nodes and edges) of the model.
-required
-
-
-results
-
-Results configuration options.
-required
-
-
-solver
-
-Solver configuration options.
-required
-
-
-logging
-
-Logging configuration options.
-required
-
-
-allocation
-
-The allocation configuration.
-required
-
-
-basin
-ribasim.config.Basin
-The waterbodies.
-required
-
-
-fractional_flow
-ribasim.config.FractionalFlow
-Split flows into fractions.
-required
-
-
-level_boundary
-ribasim.config.LevelBoundary
-Boundary condition specifying the water level.
-required
-
-
-flow_boundary
-ribasim.config.FlowBoundary
-Boundary conditions specifying the flow.
-required
-
-
-linear_resistance
-
-Linear flow resistance.
-required
-
-
-manning_resistance
-ribasim.config.ManningResistance
-Flow resistance based on the Manning formula.
-required
-
-
-tabulated_rating_curve
-ribasim.config.TabulatedRatingCurve
-Tabulated rating curve describing flow based on the upstream water level.
-required
-
-
-pump
-ribasim.config.Pump
-Prescribed flow rate from one basin to the other.
-required
-
-
-outlet
-ribasim.config.Outlet
-Prescribed flow rate from one basin to the other.
-required
-
-
-terminal
-ribasim.config.Terminal
-Water sink without state or properties.
-required
-
-
-discrete_control
-ribasim.config.DiscreteControl
-Discrete control logic.
-required
-
-
-pid_control
-ribasim.config.PidControl
-PID controller attempting to set the level of a basin to a desired value using a pump/outlet.
-required
-
-
-user_demand
-ribasim.config.UserDemand
-UserDemand node type with demand and priority.
-required
-
-
-
-
diff --git a/python/reference/Node.html b/python/reference/Node.html
deleted file mode 100644
index dfd46163a..000000000
--- a/python/reference/Node.html
+++ /dev/null
@@ -1,544 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Outlet.html b/python/reference/Outlet.html
deleted file mode 100644
index 9a9631598..000000000
--- a/python/reference/Outlet.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Pump.html b/python/reference/Pump.html
deleted file mode 100644
index b37aaf175..000000000
--- a/python/reference/Pump.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Terminal.html b/python/reference/Terminal.html
deleted file mode 100644
index 7245d0817..000000000
--- a/python/reference/Terminal.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/index.html b/python/reference/index.html
index d2b6b558c..27851c9c8 100644
--- a/python/reference/index.html
+++ b/python/reference/index.html
@@ -176,7 +176,7 @@ On this page
- 1 API Reference
@@ -197,22 +197,18 @@
Model
-A full Ribasim model schematisation with all input.
+
-
-1.2 Network
-The Node and Edge database layers define the network layout.
+
+1.2 Edge
+The Edge database layer.
-Node
-The Ribasim nodes as Point geometries.
-
-
-Edge
+EdgeTable
Defines the connections between nodes.
@@ -224,55 +220,55 @@
-Basin
+nodes.basin
-FractionalFlow
+nodes.fractional_flow
-TabulatedRatingCurve
+nodes.tabulated_rating_curve
-Pump
+nodes.pump
-Outlet
+nodes.outlet
-UserDemand
+nodes.user_demand
-LevelBoundary
+nodes.level_boundary
-FlowBoundary
+nodes.flow_boundary
-LinearResistance
+nodes.linear_resistance
-ManningResistance
+nodes.manning_resistance
-Terminal
+nodes.terminal
-DiscreteControl
+nodes.discrete_control
-PidControl
+nodes.pid_control
diff --git a/python/reference/UserDemand.html b/python/reference/nodes.basin.html
similarity index 98%
rename from python/reference/UserDemand.html
rename to python/reference/nodes.basin.html
index db254cf8b..fcd468753 100644
--- a/python/reference/UserDemand.html
+++ b/python/reference/nodes.basin.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 UserDemand
-UserDemand()
+
+1 nodes.basin
+nodes.basin
diff --git a/python/reference/nodes.discrete_control.html b/python/reference/nodes.discrete_control.html
new file mode 100644
index 000000000..42dec9b66
--- /dev/null
+++ b/python/reference/nodes.discrete_control.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/nodes.flow_boundary.html b/python/reference/nodes.flow_boundary.html
new file mode 100644
index 000000000..43aa532c2
--- /dev/null
+++ b/python/reference/nodes.flow_boundary.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/nodes.fractional_flow.html b/python/reference/nodes.fractional_flow.html
new file mode 100644
index 000000000..d6b21e860
--- /dev/null
+++ b/python/reference/nodes.fractional_flow.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/TabulatedRatingCurve.html b/python/reference/nodes.level_boundary.html
similarity index 98%
rename from python/reference/TabulatedRatingCurve.html
rename to python/reference/nodes.level_boundary.html
index 93f7399c8..e8f621f64 100644
--- a/python/reference/TabulatedRatingCurve.html
+++ b/python/reference/nodes.level_boundary.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 TabulatedRatingCurve
-TabulatedRatingCurve()
+
+1 nodes.level_boundary
+nodes.level_boundary
diff --git a/python/reference/nodes.linear_resistance.html b/python/reference/nodes.linear_resistance.html
new file mode 100644
index 000000000..85be76230
--- /dev/null
+++ b/python/reference/nodes.linear_resistance.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/nodes.manning_resistance.html b/python/reference/nodes.manning_resistance.html
new file mode 100644
index 000000000..e15e3f439
--- /dev/null
+++ b/python/reference/nodes.manning_resistance.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/FlowBoundary.html b/python/reference/nodes.outlet.html
similarity index 98%
rename from python/reference/FlowBoundary.html
rename to python/reference/nodes.outlet.html
index 777ee1d6c..52b971920 100644
--- a/python/reference/FlowBoundary.html
+++ b/python/reference/nodes.outlet.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 FlowBoundary
-FlowBoundary()
+
+1 nodes.outlet
+nodes.outlet
diff --git a/python/reference/ManningResistance.html b/python/reference/nodes.pid_control.html
similarity index 98%
rename from python/reference/ManningResistance.html
rename to python/reference/nodes.pid_control.html
index 16f837416..5d89240ea 100644
--- a/python/reference/ManningResistance.html
+++ b/python/reference/nodes.pid_control.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 ManningResistance
-ManningResistance()
+
+1 nodes.pid_control
+nodes.pid_control
diff --git a/python/reference/PidControl.html b/python/reference/nodes.pump.html
similarity index 98%
rename from python/reference/PidControl.html
rename to python/reference/nodes.pump.html
index afc4f2964..7edf07b93 100644
--- a/python/reference/PidControl.html
+++ b/python/reference/nodes.pump.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 PidControl
-PidControl()
+
+1 nodes.pump
+nodes.pump
diff --git a/python/reference/nodes.tabulated_rating_curve.html b/python/reference/nodes.tabulated_rating_curve.html
new file mode 100644
index 000000000..095cdad9b
--- /dev/null
+++ b/python/reference/nodes.tabulated_rating_curve.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/FractionalFlow.html b/python/reference/nodes.terminal.html
similarity index 98%
rename from python/reference/FractionalFlow.html
rename to python/reference/nodes.terminal.html
index 3e0a24fea..37d83a560 100644
--- a/python/reference/FractionalFlow.html
+++ b/python/reference/nodes.terminal.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 FractionalFlow
-FractionalFlow()
+
+1 nodes.terminal
+nodes.terminal
diff --git a/python/reference/Basin.html b/python/reference/nodes.user_demand.html
similarity index 98%
rename from python/reference/Basin.html
rename to python/reference/nodes.user_demand.html
index 9e5937bdd..e91608107 100644
--- a/python/reference/Basin.html
+++ b/python/reference/nodes.user_demand.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 Basin
-Basin()
+
+1 nodes.user_demand
+nodes.user_demand
diff --git a/python/test-models.html b/python/test-models.html
index ba75a1d23..f1dcc0089 100644
--- a/python/test-models.html
+++ b/python/test-models.html
@@ -227,7 +227,7 @@ Test models
Ribasim developers use the following models in their testbench and in order to test new features.
-
+
Code
import ribasim_testmodels
@@ -249,35 +249,35 @@ Test models
@@ -291,35 +291,35 @@ Test models
@@ -333,140 +333,140 @@ Test models
diff --git a/python/test-models_files/figure-html/cell-2-output-1.png b/python/test-models_files/figure-html/cell-2-output-1.png
index 92d2d521d..2f2c20593 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-1.png and b/python/test-models_files/figure-html/cell-2-output-1.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-10.png b/python/test-models_files/figure-html/cell-2-output-10.png
index 61c772eac..da65e3dd1 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-10.png and b/python/test-models_files/figure-html/cell-2-output-10.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-11.png b/python/test-models_files/figure-html/cell-2-output-11.png
index 7acf51ba9..735392009 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-11.png and b/python/test-models_files/figure-html/cell-2-output-11.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-13.png b/python/test-models_files/figure-html/cell-2-output-13.png
index a14b6dfe3..c9ec49b37 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-13.png and b/python/test-models_files/figure-html/cell-2-output-13.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-14.png b/python/test-models_files/figure-html/cell-2-output-14.png
index 3700441cd..a3793e5e8 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-14.png and b/python/test-models_files/figure-html/cell-2-output-14.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-15.png b/python/test-models_files/figure-html/cell-2-output-15.png
index e5b45280f..4d1c93fd5 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-15.png and b/python/test-models_files/figure-html/cell-2-output-15.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-16.png b/python/test-models_files/figure-html/cell-2-output-16.png
index 51472f2e6..266b60de0 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-16.png and b/python/test-models_files/figure-html/cell-2-output-16.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-17.png b/python/test-models_files/figure-html/cell-2-output-17.png
index 28f13a6b3..a19439a4d 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-17.png and b/python/test-models_files/figure-html/cell-2-output-17.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-18.png b/python/test-models_files/figure-html/cell-2-output-18.png
index 98bba3b7d..c9429ef9d 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-18.png and b/python/test-models_files/figure-html/cell-2-output-18.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-19.png b/python/test-models_files/figure-html/cell-2-output-19.png
index 6feed0dcf..9484a0b7b 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-19.png and b/python/test-models_files/figure-html/cell-2-output-19.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-2.png b/python/test-models_files/figure-html/cell-2-output-2.png
index abbbbe1b4..78ac80a73 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-2.png and b/python/test-models_files/figure-html/cell-2-output-2.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-20.png b/python/test-models_files/figure-html/cell-2-output-20.png
index 48143149e..c5638eb99 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-20.png and b/python/test-models_files/figure-html/cell-2-output-20.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-21.png b/python/test-models_files/figure-html/cell-2-output-21.png
index e23644dbf..d16b5a91e 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-21.png and b/python/test-models_files/figure-html/cell-2-output-21.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-22.png b/python/test-models_files/figure-html/cell-2-output-22.png
index 385f70f3c..34126b746 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-22.png and b/python/test-models_files/figure-html/cell-2-output-22.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-23.png b/python/test-models_files/figure-html/cell-2-output-23.png
index 30bfdf250..2d39d123b 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-23.png and b/python/test-models_files/figure-html/cell-2-output-23.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-24.png b/python/test-models_files/figure-html/cell-2-output-24.png
index c6cd3c340..432e4c960 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-24.png and b/python/test-models_files/figure-html/cell-2-output-24.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-25.png b/python/test-models_files/figure-html/cell-2-output-25.png
index f489ccb5d..fe981e085 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-25.png and b/python/test-models_files/figure-html/cell-2-output-25.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-26.png b/python/test-models_files/figure-html/cell-2-output-26.png
index 59848dc9c..400e8079d 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-26.png and b/python/test-models_files/figure-html/cell-2-output-26.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-27.png b/python/test-models_files/figure-html/cell-2-output-27.png
index ca63191bd..6d22ccd41 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-27.png and b/python/test-models_files/figure-html/cell-2-output-27.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-28.png b/python/test-models_files/figure-html/cell-2-output-28.png
index fba859b58..45ce43b7c 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-28.png and b/python/test-models_files/figure-html/cell-2-output-28.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-29.png b/python/test-models_files/figure-html/cell-2-output-29.png
index 6cd33e1c7..a6b194c71 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-29.png and b/python/test-models_files/figure-html/cell-2-output-29.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-3.png b/python/test-models_files/figure-html/cell-2-output-3.png
index 3164625c9..78dbb8a4e 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-3.png and b/python/test-models_files/figure-html/cell-2-output-3.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-30.png b/python/test-models_files/figure-html/cell-2-output-30.png
index a7ac0d0fa..0216a688a 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-30.png and b/python/test-models_files/figure-html/cell-2-output-30.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-31.png b/python/test-models_files/figure-html/cell-2-output-31.png
index 59aa41129..044dd3909 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-31.png and b/python/test-models_files/figure-html/cell-2-output-31.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-32.png b/python/test-models_files/figure-html/cell-2-output-32.png
index bd21c153c..20e39a650 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-32.png and b/python/test-models_files/figure-html/cell-2-output-32.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-4.png b/python/test-models_files/figure-html/cell-2-output-4.png
index 7f6c4c0cf..d9c7d4249 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-4.png and b/python/test-models_files/figure-html/cell-2-output-4.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-5.png b/python/test-models_files/figure-html/cell-2-output-5.png
index d12bac473..beb111767 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-5.png and b/python/test-models_files/figure-html/cell-2-output-5.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-7.png b/python/test-models_files/figure-html/cell-2-output-7.png
index 24b41b39a..3ca104cf4 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-7.png and b/python/test-models_files/figure-html/cell-2-output-7.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-8.png b/python/test-models_files/figure-html/cell-2-output-8.png
index 9d5652095..d1485e332 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-8.png and b/python/test-models_files/figure-html/cell-2-output-8.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-9.png b/python/test-models_files/figure-html/cell-2-output-9.png
index a2637d6fc..6d8b03039 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-9.png and b/python/test-models_files/figure-html/cell-2-output-9.png differ
diff --git a/search.json b/search.json
index 830341918..3795f71f3 100644
--- a/search.json
+++ b/search.json
@@ -472,7 +472,7 @@
"href": "core/usage.html#discretecontrol-condition",
"title": "Usage",
"section": "17.1 DiscreteControl / condition",
- "text": "17.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_feature_id\nInt\n-\nsorted per node_id\n\n\nlisten_feature_type\nString\n-\nknown node type\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”, sorted per listen_feature_id\n\n\ngreater_than\nFloat64\nvarious\nsorted per variable\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)",
+ "text": "17.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_node_type\nString\n-\nknown node type\n\n\nlisten_node_id\nInt\n-\nsorted per node_id\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”, sorted per listen_node_id\n\n\ngreater_than\nFloat64\nvarious\nsorted per variable\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)",
"crumbs": [
"Julia core",
"Usage"
@@ -494,7 +494,7 @@
"href": "core/usage.html#pidcontrol-time",
"title": "Usage",
"section": "18.1 PidControl / time",
- "text": "18.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntime\nDateTime\n-\nsorted per node_id\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-",
+ "text": "18.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntime\nDateTime\n-\nsorted per node_id\n\n\nlisten_node_type\nInt\n-\nknown node type\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-",
"crumbs": [
"Julia core",
"Usage"
@@ -655,118 +655,113 @@
]
},
{
- "objectID": "python/reference/Outlet.html",
- "href": "python/reference/Outlet.html",
- "title": "1 Outlet",
+ "objectID": "python/reference/Model.html",
+ "href": "python/reference/Model.html",
+ "title": "1 Model",
"section": "",
- "text": "1 Outlet\nOutlet()"
+ "text": "1 Model\nModel()"
},
{
- "objectID": "python/reference/Basin.html",
- "href": "python/reference/Basin.html",
- "title": "1 Basin",
+ "objectID": "python/reference/nodes.pump.html",
+ "href": "python/reference/nodes.pump.html",
+ "title": "1 nodes.pump",
"section": "",
- "text": "1 Basin\nBasin()"
+ "text": "1 nodes.pump\nnodes.pump"
},
{
- "objectID": "python/reference/index.html",
- "href": "python/reference/index.html",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.user_demand.html",
+ "href": "python/reference/nodes.user_demand.html",
+ "title": "1 nodes.user_demand",
"section": "",
- "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.\n\n\n\n\n\n\nThe Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUserDemand\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.user_demand\nnodes.user_demand"
},
{
- "objectID": "python/reference/index.html#model",
- "href": "python/reference/index.html#model",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.fractional_flow.html",
+ "href": "python/reference/nodes.fractional_flow.html",
+ "title": "1 nodes.fractional_flow",
"section": "",
- "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.fractional_flow\nnodes.fractional_flow"
},
{
- "objectID": "python/reference/index.html#network",
- "href": "python/reference/index.html#network",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.terminal.html",
+ "href": "python/reference/nodes.terminal.html",
+ "title": "1 nodes.terminal",
"section": "",
- "text": "The Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.terminal\nnodes.terminal"
},
{
- "objectID": "python/reference/index.html#node-types",
- "href": "python/reference/index.html#node-types",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.level_boundary.html",
+ "href": "python/reference/nodes.level_boundary.html",
+ "title": "1 nodes.level_boundary",
"section": "",
- "text": "Available node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUserDemand\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.level_boundary\nnodes.level_boundary"
+ },
+ {
+ "objectID": "python/reference/nodes.pid_control.html",
+ "href": "python/reference/nodes.pid_control.html",
+ "title": "1 nodes.pid_control",
+ "section": "",
+ "text": "1 nodes.pid_control\nnodes.pid_control"
},
{
- "objectID": "python/reference/PidControl.html",
- "href": "python/reference/PidControl.html",
- "title": "1 PidControl",
+ "objectID": "python/reference/nodes.linear_resistance.html",
+ "href": "python/reference/nodes.linear_resistance.html",
+ "title": "1 nodes.linear_resistance",
"section": "",
- "text": "1 PidControl\nPidControl()"
+ "text": "1 nodes.linear_resistance\nnodes.linear_resistance"
+ },
+ {
+ "objectID": "python/test-models.html",
+ "href": "python/test-models.html",
+ "title": "Test models",
+ "section": "",
+ "text": "Ribasim developers use the following models in their testbench and in order to test new features.\n\n\nCode\nimport ribasim_testmodels\nimport matplotlib.pyplot as plt\n\nfor model_name, model_constructor in ribasim_testmodels.constructors.items():\n if model_name.startswith(\"invalid\"):\n continue\n\n model = model_constructor()\n fig, ax = plt.subplots()\n model.plot(ax)\n ax.set_title(label=model_name, loc=\"left\")\n fig.text(0, 1, model_constructor.__doc__)\n fig.tight_layout()\n plt.show()\n plt.close(fig)",
+ "crumbs": [
+ "Python tooling",
+ "Test models"
+ ]
},
{
- "objectID": "python/reference/ManningResistance.html",
- "href": "python/reference/ManningResistance.html",
- "title": "1 ManningResistance",
+ "objectID": "src/index.html",
+ "href": "src/index.html",
+ "title": "1 API Reference",
"section": "",
- "text": "1 ManningResistance\nManningResistance()"
+ "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
},
{
- "objectID": "python/reference/LevelBoundary.html",
- "href": "python/reference/LevelBoundary.html",
- "title": "1 LevelBoundary",
+ "objectID": "src/index.html#modules",
+ "href": "src/index.html#modules",
+ "title": "1 API Reference",
"section": "",
- "text": "1 LevelBoundary\nLevelBoundary()"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]"
},
{
- "objectID": "python/reference/Node.html",
- "href": "python/reference/Node.html",
- "title": "1 Node",
+ "objectID": "src/index.html#types",
+ "href": "src/index.html#types",
+ "title": "1 API Reference",
"section": "",
- "text": "1 Node\nNode()\nThe Ribasim nodes as Point geometries."
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]"
},
{
- "objectID": "python/reference/Pump.html",
- "href": "python/reference/Pump.html",
- "title": "1 Pump",
+ "objectID": "src/index.html#functions",
+ "href": "src/index.html#functions",
+ "title": "1 API Reference",
"section": "",
- "text": "1 Pump\nPump()"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]"
},
{
- "objectID": "python/index.html",
- "href": "python/index.html",
- "title": "Python tooling",
+ "objectID": "src/index.html#constants",
+ "href": "src/index.html#constants",
+ "title": "1 API Reference",
"section": "",
- "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip:\npip install ribasim\nFor wheel (.whl) downloads, including nightly builds, see the download section. After downloading wheels can be installed by referring to the correct path:\npip install path/to/ribasim-*.whl\nFor documentation please see the examples and API reference.",
- "crumbs": [
- "Python tooling"
- ]
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]"
},
{
- "objectID": "python/examples.html",
- "href": "python/examples.html",
- "title": "Examples",
+ "objectID": "src/index.html#macros",
+ "href": "src/index.html#macros",
+ "title": "1 API Reference",
"section": "",
- "text": "1 Basic model with static forcing\n\nfrom pathlib import Path\n\nimport geopandas as gpd\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport ribasim\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1, 3, 3, 6, 6, 9, 9],\n \"area\": [0.01, 1000.0] * 4,\n \"level\": [0.0, 1.0] * 4,\n }\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\nseconds_in_day = 24 * 3600\nprecipitation = 0.002 / seconds_in_day\nevaporation = 0.001 / seconds_in_day\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [0],\n \"potential_evaporation\": [evaporation],\n \"precipitation\": [precipitation],\n }\n)\nstatic = static.iloc[[0, 0, 0, 0]]\nstatic[\"node_id\"] = [1, 3, 6, 9]\n\nbasin = ribasim.Basin(profile=profile, static=static)\n\nSetup linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\"node_id\": [10, 12], \"resistance\": [5e3, (3600.0 * 24) / 100.0]}\n )\n)\n\nSetup Manning resistance:\n\nmanning_resistance = ribasim.ManningResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [2],\n \"length\": [900.0],\n \"manning_n\": [0.04],\n \"profile_width\": [6.0],\n \"profile_slope\": [3.0],\n }\n )\n)\n\nSet up a rating curve node:\n\n# Discharge: lose 1% of storage volume per day at storage = 1000.0.\nq1000 = 1000.0 * 0.01 / seconds_in_day\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": [4, 4],\n \"level\": [0.0, 1.0],\n \"flow_rate\": [0.0, q1000],\n }\n )\n)\n\nSetup fractional flows:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [5, 8, 13],\n \"fraction\": [0.3, 0.6, 0.1],\n }\n )\n)\n\nSetup pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [7],\n \"flow_rate\": [0.5 / 3600],\n }\n )\n)\n\nSetup level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [11, 17],\n \"level\": [0.5, 1.5],\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [15, 16],\n \"flow_rate\": [1e-4, 1e-4],\n }\n )\n)\n\nSetup terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [14],\n }\n )\n)\n\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin,\n (1.0, 0.0), # 2: ManningResistance\n (2.0, 0.0), # 3: Basin\n (3.0, 0.0), # 4: TabulatedRatingCurve\n (3.0, 1.0), # 5: FractionalFlow\n (3.0, 2.0), # 6: Basin\n (4.0, 1.0), # 7: Pump\n (4.0, 0.0), # 8: FractionalFlow\n (5.0, 0.0), # 9: Basin\n (6.0, 0.0), # 10: LinearResistance\n (2.0, 2.0), # 11: LevelBoundary\n (2.0, 1.0), # 12: LinearResistance\n (3.0, -1.0), # 13: FractionalFlow\n (3.0, -2.0), # 14: Terminal\n (3.0, 3.0), # 15: FlowBoundary\n (0.0, 1.0), # 16: FlowBoundary\n (6.0, 1.0), # 17: LevelBoundary\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_id, node_type = ribasim.Node.node_ids_and_types(\n basin,\n manning_resistance,\n rating_curve,\n pump,\n fractional_flow,\n linear_resistance,\n level_boundary,\n flow_boundary,\n terminal,\n)\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type},\n index=pd.Index(node_id, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64\n)\nto_id = np.array(\n [2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64\n)\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": len(from_id) * [\"flow\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n level_boundary=level_boundary,\n flow_boundary=flow_boundary,\n pump=pump,\n linear_resistance=linear_resistance,\n manning_resistance=manning_resistance,\n tabulated_rating_curve=rating_curve,\n fractional_flow=fractional_flow,\n terminal=terminal,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"basic/ribasim.toml\")\n\nPosixPath('data/basic/ribasim.toml')\n\n\n\n\n2 Update the basic model with transient forcing\nThis assumes you have already created the basic model with static forcing.\n\nimport numpy as np\nimport pandas as pd\nimport ribasim\nimport xarray as xr\n\n\nmodel = ribasim.Model(filepath=datadir / \"basic/ribasim.toml\")\n\nCan't read from data/basic/database.gpkg:Basin / area\n\n\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\nWe’ll use xarray to easily broadcast the values.\n\ntimeseries = (\n pd.DataFrame(\n data={\n \"node_id\": 1,\n \"time\": pd.date_range(model.starttime, model.endtime),\n \"drainage\": 0.0,\n \"potential_evaporation\": evaporation,\n \"infiltration\": 0.0,\n \"precipitation\": precipitation,\n \"urban_runoff\": 0.0,\n }\n )\n .set_index(\"time\")\n .to_xarray()\n)\n\nbasin_ids = model.basin.static.df[\"node_id\"].to_numpy()\nbasin_nodes = xr.DataArray(\n np.ones(len(basin_ids)), coords={\"node_id\": basin_ids}, dims=[\"node_id\"]\n)\nforcing = (timeseries * basin_nodes).to_dataframe().reset_index()\n\n\nstate = pd.DataFrame(\n data={\n \"node_id\": basin_ids,\n \"level\": 1.4,\n }\n)\n\n\nmodel.basin.time.df = forcing\nmodel.basin.state.df = state\n\n\nmodel.write(datadir / \"basic_transient/ribasim.toml\")\n\nPosixPath('data/basic_transient/ribasim.toml')\n\n\nNow run the model with ribasim basic_transient/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"basic_transient/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n\n\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic_transient/results/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow_rate * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n\n\n\n\n\n\n\n\ntype(df_flow)\n\npandas.core.frame.DataFrame\n\n\n\n\n3 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin\n (1.0, 1.0), # 2: Pump\n (1.0, -1.0), # 3: Pump\n (2.0, 0.0), # 4: LevelBoundary\n (-1.0, 0.0), # 5: TabulatedRatingCurve\n (-2.0, 0.0), # 6: Terminal\n (1.0, 0.0), # 7: DiscreteControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"Basin\",\n \"Pump\",\n \"Pump\",\n \"LevelBoundary\",\n \"TabulatedRatingCurve\",\n \"Terminal\",\n \"DiscreteControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)\nto_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)\n\nedge_type = 6 * [\"flow\"] + 2 * [\"control\"]\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\"from_node_id\": from_id, \"to_node_id\": to_id, \"edge_type\": edge_type},\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1],\n \"area\": [1000.0, 1000.0],\n \"level\": [0.0, 1.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [1], \"level\": [20.0]})\n\nbasin = ribasim.Basin(profile=profile, state=state)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": 3 * [7],\n \"listen_feature_id\": 3 * [1],\n \"variable\": 3 * [\"level\"],\n \"greater_than\": [5.0, 10.0, 15.0], # min, setpoint, max\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 5 * [7],\n \"truth_state\": [\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n \"control_state\": [\"in\", \"in\", \"none\", \"out\", \"out\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nThe above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": 3 * [2] + 3 * [3],\n \"control_state\": 2 * [\"none\", \"in\", \"out\"],\n \"flow_rate\": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],\n }\n )\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(data={\"node_id\": [4], \"level\": [10.0]})\n)\n\nSetup the rating curve:\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\"node_id\": 2 * [5], \"level\": [2.0, 15.0], \"flow_rate\": [0.0, 1e-3]}\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(static=pd.DataFrame(data={\"node_id\": [6]}))\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n pump=pump,\n level_boundary=level_boundary,\n tabulated_rating_curve=rating_curve,\n terminal=terminal,\n discrete_control=discrete_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax/ribasim.toml\")\n\nPosixPath('data/level_setpoint_with_minmax/ribasim.toml')\n\n\nNow run the model with level_setpoint_with_minmax/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.df.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\n\n\n\n\nThe highlighted regions show where a pump is active.\nLet’s print an overview of what happened with control:\n\nmodel.print_discrete_control_record(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\n0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level > 15.0\n\n This yielded control state \"out\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan\n\n1. At 2020-02-08 19:07:33.550000 the control node with ID 7 reached truth state TFF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n\n2. At 2020-07-05 09:06:07.383000 the control node with ID 7 reached truth state FFF:\n For node ID 1 (Basin): level < 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"in\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n\n3. At 2020-08-11 06:17:03.985000 the control node with ID 7 reached truth state TTF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n\n\n\nNote that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.\n\n\n4 Model with PID control\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.5), # 3: Pump\n (3.0, 0.0), # 4: LevelBoundary\n (1.5, 1.0), # 5: PidControl\n (2.0, -0.5), # 6: outlet\n (1.5, -1.0), # 7: PidControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"Pump\",\n \"LevelBoundary\",\n \"PidControl\",\n \"Outlet\",\n \"PidControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)\nto_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": 5 * [\"flow\"] + 2 * [\"control\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2], \"level\": [0.0, 1.0], \"area\": [1000.0, 1000.0]}\n)\n\nstate = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"level\": [6.0],\n }\n)\n\nbasin = ribasim.Basin(profile=profile, state=state)\n\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup the outlet:\n\noutlet = ribasim.Outlet(\n static=pd.DataFrame(\n data={\n \"node_id\": [6],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": [1e-3]})\n)\n\nSetup flow boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"level\": [1.0], # Not relevant\n }\n )\n)\n\nSetup PID control:\n\npid_control = ribasim.PidControl(\n time=pd.DataFrame(\n data={\n \"node_id\": 4 * [5, 7],\n \"time\": [\n \"2020-01-01 00:00:00\",\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n \"listen_node_id\": 4 * [2, 2],\n \"target\": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],\n \"proportional\": 4 * [-1e-3, 1e-3],\n \"integral\": 4 * [-1e-7, 1e-7],\n \"derivative\": 4 * [0.0, 0.0],\n }\n )\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n flow_boundary=flow_boundary,\n level_boundary=level_boundary,\n pump=pump,\n outlet=outlet,\n pid_control=pid_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control/ribasim.toml\")\n\nPosixPath('data/pid_control/ribasim.toml')\n\n\nNow run the model with ribasim pid_control/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\nlevel_demands = model.pid_control.time.df.target.to_numpy()[:4]\ntimes = date2num(model.pid_control.time.df.time)[:4]\nax.plot(times, level_demands, color=\"k\", ls=\":\", label=\"target level\")\npass\n\n\n\n\n\n\n\n\n\n\n5 Model with allocation (user demand)\nSetup the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (1.0, 1.0), # 3: UserDemand\n (2.0, 0.0), # 4: LinearResistance\n (3.0, 0.0), # 5: Basin\n (3.0, 1.0), # 6: UserDemand\n (4.0, 0.0), # 7: TabulatedRatingCurve\n (4.5, 0.0), # 8: FractionalFlow\n (4.5, 0.5), # 9: FractionalFlow\n (5.0, 0.0), # 10: Terminal\n (4.5, 0.25), # 11: DiscreteControl\n (4.5, 1.0), # 12: Basin\n (5.0, 1.0), # 13: UserDemand\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"UserDemand\",\n \"LinearResistance\",\n \"Basin\",\n \"UserDemand\",\n \"TabulatedRatingCurve\",\n \"FractionalFlow\",\n \"FractionalFlow\",\n \"Terminal\",\n \"DiscreteControl\",\n \"Basin\",\n \"UserDemand\",\n]\n\n# All nodes belong to allocation network id 1\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type, \"subnetwork_id\": 1},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 2, 4, 5, 5, 7, 3, 6, 7, 8, 9, 12, 13, 11, 11],\n dtype=np.int64,\n)\nto_id = np.array(\n [2, 3, 4, 5, 6, 7, 8, 2, 5, 9, 10, 12, 13, 10, 8, 9],\n dtype=np.int64,\n)\n# Denote the first edge, 1 => 2, as a source edge for\n# allocation network 1\nsubnetwork_id = len(from_id) * [None]\nsubnetwork_id[0] = 1\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": (len(from_id) - 2) * [\"flow\"] + 2 * [\"control\"],\n \"subnetwork_id\": subnetwork_id,\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [2, 2, 5, 5, 12, 12],\n \"area\": 300_000.0,\n \"level\": 3 * [0.0, 1.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [2, 5, 12], \"level\": 1.0})\n\nbasin = ribasim.Basin(profile=profile, state=state)\n\nSetup the flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [1],\n \"flow_rate\": 2.0,\n }\n )\n)\n\nSetup the linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"resistance\": 0.06,\n }\n )\n)\n\nSetup the tabulated rating curve:\n\ntabulated_rating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": 7,\n \"level\": [0.0, 0.5, 1.0],\n \"flow_rate\": [0.0, 0.0, 2.0],\n }\n )\n)\n\nSetup the fractional flow:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [8, 8, 9, 9],\n \"fraction\": [0.6, 0.9, 0.4, 0.1],\n \"control_state\": [\"divert\", \"close\", \"divert\", \"close\"],\n }\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [10],\n }\n )\n)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": [11],\n \"listen_feature_id\": 5,\n \"variable\": \"level\",\n \"greater_than\": 0.52,\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 11,\n \"truth_state\": [\"T\", \"F\"],\n \"control_state\": [\"divert\", \"close\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nSetup the users:\n\nuser_demand = ribasim.UserDemand(\n static=pd.DataFrame(\n data={\n \"node_id\": [6, 13],\n \"demand\": [1.5, 1.0],\n \"return_factor\": 0.0,\n \"min_level\": -1.0,\n \"priority\": [1, 3],\n }\n ),\n time=pd.DataFrame(\n data={\n \"node_id\": [3, 3, 3, 3],\n \"demand\": [0.0, 1.0, 1.2, 1.2],\n \"priority\": [1, 1, 2, 2],\n \"return_factor\": 0.0,\n \"min_level\": -1.0,\n \"time\": 2 * [\"2020-01-01 00:00:00\", \"2020-01-20 00:00:00\"],\n }\n ),\n)\n\nSetup the allocation:\n\nallocation = ribasim.Allocation(use_allocation=True, timestep=86400)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n flow_boundary=flow_boundary,\n linear_resistance=linear_resistance,\n tabulated_rating_curve=tabulated_rating_curve,\n terminal=terminal,\n user_demand=user_demand,\n discrete_control=discrete_control,\n fractional_flow=fractional_flow,\n allocation=allocation,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-01-20 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"allocation_example/ribasim.toml\")\n\nPosixPath('data/allocation_example/ribasim.toml')\n\n\nNow run the model with ribasim allocation_example/ribasim.toml. After running the model, read back the results:\n\nimport matplotlib.ticker as plticker\n\ndf_allocation = pd.read_feather(datadir / \"allocation_example/results/allocation.arrow\")\ndf_allocation_wide = df_allocation.pivot_table(\n index=\"time\",\n columns=[\"node_type\", \"node_id\", \"priority\"],\n values=[\"demand\", \"allocated\", \"realized\"],\n)\ndf_allocation_wide = df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]\n\nfig, axs = plt.subplots(1, 3, figsize=(8, 5))\n\ndf_allocation_wide[\"demand\"].plot(ax=axs[0], ls=\":\")\ndf_allocation_wide[\"allocated\"].plot(ax=axs[1], ls=\"--\")\ndf_allocation_wide[\"realized\"].plot(ax=axs[2])\n\nfig.tight_layout()\nloc = plticker.MultipleLocator(2)\n\naxs[0].set_ylabel(\"level [m]\")\n\nfor ax, title in zip(axs, [\"Demand\", \"Allocated\", \"Abstracted\"]):\n ax.set_title(title)\n ax.set_ylim(0.0, 1.6)\n ax.xaxis.set_major_locator(loc)\n\n\n\n\n\n\n\n\nSome things to note about this plot:\n\nAbstraction behaves somewhat erratically at the start of the simulation. This is because allocation is based on flows computed in the physical layer, and at the start of the simulation these are not known yet.\nAlthough there is a plotted line for abstraction per priority, abstraction is actually accumulated over all priorities per user.\n\n\ndf_basin = pd.read_feather(datadir / \"allocation_example/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\nax.set_title(\"Basin levels\")\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\n\n\n6 Model with allocation (basin supply/demand)\nSetup the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.0), # 3: UserDemand\n (1.0, -1.0), # 4: LevelDemand\n (2.0, -1.0), # 5: Basin\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\"FlowBoundary\", \"Basin\", \"UserDemand\", \"LevelDemand\", \"Basin\"]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\n \"node_type\": node_type,\n \"subnetwork_id\": 5 * [2],\n },\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 4, 3, 4])\nto_id = np.array([2, 3, 2, 5, 5])\nedge_type = [\"flow\", \"flow\", \"control\", \"flow\", \"control\"]\nsubnetwork_id = [2, None, None, None, None]\n\nlines = node.geometry_from_connectivity(from_id.tolist(), to_id.tolist())\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": edge_type,\n \"subnetwork_id\": subnetwork_id,\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2, 5, 5], \"area\": 1e3, \"level\": [0.0, 1.0, 0.0, 1.0]}\n)\nstatic = pd.DataFrame(\n data={\n \"node_id\": [5],\n \"drainage\": 0.0,\n \"potential_evaporation\": 0.0,\n \"infiltration\": 0.0,\n \"precipitation\": 0.0,\n \"urban_runoff\": 0.0,\n }\n)\ntime = pd.DataFrame(\n data={\n \"node_id\": 2,\n \"time\": [\"2020-01-01 00:00:00\", \"2020-01-16 00:00:00\"],\n \"drainage\": 0.0,\n \"potential_evaporation\": 0.0,\n \"infiltration\": 0.0,\n \"precipitation\": [1e-6, 0.0],\n \"urban_runoff\": 0.0,\n },\n)\n\nstate = pd.DataFrame(data={\"node_id\": [2, 5], \"level\": 0.5})\nbasin = ribasim.Basin(profile=profile, static=static, time=time, state=state)\n\nSetup the flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": 1e-3})\n)\n\nSetup allocation level control:\n\nlevel_demand = ribasim.LevelDemand(\n static=pd.DataFrame(\n data={\"node_id\": [4], \"priority\": 1, \"min_level\": 1.0, \"max_level\": 1.5}\n )\n)\n\nSetup the users:\n\nuser_demand = ribasim.UserDemand(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"priority\": [2],\n \"demand\": [1.5e-3],\n \"return_factor\": [0.2],\n \"min_level\": [0.2],\n }\n )\n)\n\nSetup the allocation:\n\nallocation = ribasim.Allocation(use_allocation=True, timestep=1e5)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(node=node, edge=edge),\n basin=basin,\n flow_boundary=flow_boundary,\n level_demand=level_demand,\n user_demand=user_demand,\n allocation=allocation,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-02-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\nmodel.write(datadir / \"level_demand/ribasim.toml\")\n\nPosixPath('data/level_demand/ribasim.toml')\n\n\nNow run the model with ribasim level_demand/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"level_demand/results/basin.arrow\")\ndf_basin = df_basin[df_basin.node_id == 2]\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nwhere_allocation = (\n df_basin_wide.index - df_basin_wide.index[0]\n).total_seconds() % model.allocation.timestep == 0\nwhere_allocation[0] = False\ndf_basin_wide[where_allocation][\"level\"].plot(\n style=\"o\",\n ax=ax,\n)\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\nIn the plot above, the line denotes the level of Basin #2 over time and the dots denote the times at which allocation optimization was run, with intervals of \\(\\Delta t_{\\text{alloc}}\\). The Basin level is a piecewise linear function of time, with several stages explained below.\nConstants: - \\(d\\): UserDemand #3 demand, - \\(\\phi\\): Basin #2 precipitation rate, - \\(q\\): LevelBoundary flow.\nStages: - In the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \\(q + \\phi - d\\); - In the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \\(q + \\phi\\); - In the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \\(\\phi\\); - In the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \\(q + \\phi - d\\); - At the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \\(q - d\\). - In the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.",
- "crumbs": [
- "Python tooling",
- "Examples"
- ]
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
},
{
"objectID": "build/index.html#modules",
@@ -835,134 +830,118 @@
]
},
{
- "objectID": "src/index.html",
- "href": "src/index.html",
- "title": "1 API Reference",
- "section": "",
- "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
- },
- {
- "objectID": "src/index.html#modules",
- "href": "src/index.html#modules",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]"
- },
- {
- "objectID": "src/index.html#types",
- "href": "src/index.html#types",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]"
- },
- {
- "objectID": "src/index.html#functions",
- "href": "src/index.html#functions",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]"
- },
- {
- "objectID": "src/index.html#constants",
- "href": "src/index.html#constants",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]"
- },
- {
- "objectID": "src/index.html#macros",
- "href": "src/index.html#macros",
- "title": "1 API Reference",
+ "objectID": "python/examples.html",
+ "href": "python/examples.html",
+ "title": "Examples",
"section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
+ "text": "1 Basic model with static forcing\n\nimport shutil\nfrom pathlib import Path\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport ribasim\nfrom ribasim.config import Node\nfrom ribasim.model import Model\nfrom ribasim.nodes import (\n basin,\n discrete_control,\n flow_boundary,\n fractional_flow,\n level_boundary,\n level_demand,\n linear_resistance,\n manning_resistance,\n outlet,\n pid_control,\n pump,\n tabulated_rating_curve,\n user_demand,\n)\nfrom shapely.geometry import Point\n\n\ndatadir = Path(\"data\")\nshutil.rmtree(datadir, ignore_errors=True)\n\n\nmodel = Model(starttime=\"2020-01-01 00:00:00\", endtime=\"2021-01-01 00:00:00\")\n\nSetup the basins:\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\n\nbasin_data = [\n basin.Profile(area=[0.01, 1000.0], level=[0.0, 1.0]),\n basin.Time(\n time=pd.date_range(model.starttime, model.endtime),\n drainage=0.0,\n potential_evaporation=evaporation,\n infiltration=0.0,\n precipitation=precipitation,\n urban_runoff=0.0,\n ),\n basin.State(level=[1.4]),\n]\n\nmodel.basin.add(Node(1, Point(0.0, 0.0)), basin_data)\nmodel.basin.add(Node(3, Point(2.0, 0.0)), basin_data)\nmodel.basin.add(Node(6, Point(3.0, 2.0)), basin_data)\nmodel.basin.add(Node(9, Point(5.0, 0.0)), basin_data)\n\nSetup linear resistance:\n\nmodel.linear_resistance.add(\n Node(10, Point(6.0, 0.0)),\n [linear_resistance.Static(resistance=[5e3])],\n)\nmodel.linear_resistance.add(\n Node(12, Point(2.0, 1.0)),\n [linear_resistance.Static(resistance=[3600.0 * 24.0 / 100.0])],\n)\n\nSetup Manning resistance:\n\nmodel.manning_resistance.add(\n Node(2, Point(1.0, 0.0)),\n [\n manning_resistance.Static(\n length=[900], manning_n=[0.04], profile_width=[6.0], profile_slope=[3.0]\n )\n ],\n)\n\nSet up a rating curve node:\n\nmodel.tabulated_rating_curve.add(\n Node(4, Point(3.0, 0.0)),\n [tabulated_rating_curve.Static(level=[0.0, 1.0], flow_rate=[0.0, 10 / 86400])],\n)\n\nSetup fractional flows:\n\nmodel.fractional_flow.add(\n Node(5, Point(3.0, 1.0)), [fractional_flow.Static(fraction=[0.3])]\n)\nmodel.fractional_flow.add(\n Node(8, Point(4.0, 0.0)), [fractional_flow.Static(fraction=[0.6])]\n)\nmodel.fractional_flow.add(\n Node(13, Point(3.0, -1.0)),\n [fractional_flow.Static(fraction=[0.1])],\n)\n\nSetup pump:\n\nmodel.pump.add(Node(7, Point(4.0, 1.0)), [pump.Static(flow_rate=[0.5 / 3600])])\n\nSetup level boundary:\n\nmodel.level_boundary.add(\n Node(11, Point(2.0, 2.0)), [level_boundary.Static(level=[0.5])]\n)\nmodel.level_boundary.add(\n Node(17, Point(6.0, 1.0)), [level_boundary.Static(level=[1.5])]\n)\n\nSetup flow boundary:\n\nmodel.flow_boundary.add(\n Node(15, Point(3.0, 3.0)), [flow_boundary.Static(flow_rate=[1e-4])]\n)\nmodel.flow_boundary.add(\n Node(16, Point(0.0, 1.0)), [flow_boundary.Static(flow_rate=[1e-4])]\n)\n\nSetup terminal:\n\nmodel.terminal.add(Node(14, Point(3.0, -2.0)))\n\nSetup the edges:\n\nmodel.edge.add(model.basin[1], model.manning_resistance[2], \"flow\")\nmodel.edge.add(model.manning_resistance[2], model.basin[3], \"flow\")\nmodel.edge.add(model.basin[3], model.tabulated_rating_curve[4], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[5], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[8], \"flow\")\nmodel.edge.add(model.fractional_flow[5], model.basin[6], \"flow\")\nmodel.edge.add(model.basin[6], model.pump[7], \"flow\")\nmodel.edge.add(model.fractional_flow[8], model.basin[9], \"flow\")\nmodel.edge.add(model.pump[7], model.basin[9], \"flow\")\nmodel.edge.add(model.basin[9], model.linear_resistance[10], \"flow\")\nmodel.edge.add(model.level_boundary[11], model.linear_resistance[12], \"flow\")\nmodel.edge.add(model.linear_resistance[12], model.basin[3], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[13], \"flow\")\nmodel.edge.add(model.fractional_flow[13], model.terminal[14], \"flow\")\nmodel.edge.add(model.flow_boundary[15], model.basin[6], \"flow\")\nmodel.edge.add(model.flow_boundary[16], model.basin[1], \"flow\")\nmodel.edge.add(model.linear_resistance[10], model.level_boundary[17], \"flow\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\nmodel.write(datadir / \"basic/ribasim.toml\")\n\nPosixPath('data/basic/ribasim.toml')\n\n\nNow run the model with ribasim basic/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"basic/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n\n\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic/results/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow_rate * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n\n\n\n\n\n\n\n\n\n2 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSetup the basins:\n\nmodel = Model(starttime=\"2020-01-01 00:00:00\", endtime=\"2021-01-01 00:00:00\")\n\n\nmodel.basin.add(\n Node(1, Point(0.0, 0.0)),\n [\n basin.Profile(area=[1000.0, 1000.0], level=[0.0, 1.0]),\n basin.State(level=[20.0]),\n ],\n)\n\nSetup the discrete control:\n\nmodel.discrete_control.add(\n Node(7, Point(1.0, 0.0)),\n [\n discrete_control.Condition(\n listen_node_id=[1, 1, 1],\n listen_node_type=[\"Basin\", \"Basin\", \"Basin\"],\n variable=[\"level\", \"level\", \"level\"],\n greater_than=[5.0, 10.0, 15.0],\n ),\n discrete_control.Logic(\n truth_state=[\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n control_state=[\"in\", \"in\", \"none\", \"out\", \"out\"],\n ),\n ],\n)\n\nThe above control logic can be summarized as follows:\n\nIf the level gets above the maximum, activate the control state “out” until the setpoint is reached;\nIf the level gets below the minimum, active the control state “in” until the setpoint is reached;\nOtherwise activate the control state “none”.\n\nSetup the pump:\n\nmodel.pump.add(\n Node(2, Point(1.0, 1.0)),\n [pump.Static(control_state=[\"none\", \"in\", \"out\"], flow_rate=[0.0, 2e-3, 0.0])],\n)\nmodel.pump.add(\n Node(3, Point(1.0, -1.0)),\n [pump.Static(control_state=[\"none\", \"in\", \"out\"], flow_rate=[0.0, 0.0, 2e-3])],\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nmodel.level_boundary.add(\n Node(4, Point(2.0, 0.0)), [level_boundary.Static(level=[10.0])]\n)\n\nSetup the rating curve:\n\nmodel.tabulated_rating_curve.add(\n Node(5, Point(-1.0, 0.0)),\n [tabulated_rating_curve.Static(level=[2.0, 15.0], flow_rate=[0.0, 1e-3])],\n)\n\nSetup the terminal:\n\nmodel.terminal.add(Node(6, Point(-2.0, 0.0)))\n\nSetup edges:\n\nmodel.edge.add(model.basin[1], model.pump[3], \"flow\")\nmodel.edge.add(model.pump[3], model.level_boundary[4], \"flow\")\nmodel.edge.add(model.level_boundary[4], model.pump[2], \"flow\")\nmodel.edge.add(model.pump[2], model.basin[1], \"flow\")\nmodel.edge.add(model.basin[1], model.tabulated_rating_curve[5], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[5], model.terminal[6], \"flow\")\nmodel.edge.add(model.discrete_control[7], model.pump[2], \"control\")\nmodel.edge.add(model.discrete_control[7], model.pump[3], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax/ribasim.toml\")\n\nPosixPath('data/level_setpoint_with_minmax/ribasim.toml')\n\n\nNow run the model with level_setpoint_with_minmax/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.df.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\n\n\n\n\nThe highlighted regions show where a pump is active.\n\n\n3 Model with PID control\nSet up the model:\n\nmodel = Model(\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nSetup the basins:\n\nmodel.basin.add(\n Node(2, Point(1.0, 0.0)),\n [basin.Profile(area=[1000.0, 1000.0], level=[0.0, 1.0]), basin.State(level=[6.0])],\n)\n\nSetup the pump:\n\nmodel.pump.add(\n Node(3, Point(2.0, 0.5)),\n [pump.Static(flow_rate=[0.0])], # Will be overwritten by PID controller\n)\n\nSetup the outlet:\n\nmodel.outlet.add(\n Node(6, Point(2.0, -0.5)),\n [outlet.Static(flow_rate=[0.0])], # Will be overwritten by PID controller\n)\n\nSetup flow boundary:\n\nmodel.flow_boundary.add(\n Node(1, Point(0.0, 0.0)),\n [flow_boundary.Static(flow_rate=[1e-3])],\n)\n\nSetup flow boundary:\n\nmodel.level_boundary.add(\n Node(4, Point(3.0, 0.0)),\n [level_boundary.Static(level=[1])],\n)\n\nSetup PID control:\n\nmodel.pid_control.add(\n Node(5, Point(1.5, 1.0)),\n [\n pid_control.Time(\n time=[\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n listen_node_id=[2, 2, 2, 2],\n listen_node_type=[\"Basin\", \"Basin\", \"Basin\", \"Basin\"],\n target=[5.0, 5.0, 7.5, 7.5],\n proportional=[-1e-3, 1e-3, 1e-3, 1e-3],\n integral=[-1e-7, 1e-7, -1e-7, 1e-7],\n derivative=[0.0, 0.0, 0.0, 0.0],\n )\n ],\n)\nmodel.pid_control.add(\n Node(7, Point(1.5, -1.0)),\n [\n pid_control.Time(\n time=[\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n listen_node_id=[2, 2, 2, 2],\n listen_node_type=[\"Basin\", \"Basin\", \"Basin\", \"Basin\"],\n target=[5.0, 5.0, 7.5, 7.5],\n proportional=[-1e-3, 1e-3, 1e-3, 1e-3],\n integral=[-1e-7, 1e-7, -1e-7, 1e-7],\n derivative=[0.0, 0.0, 0.0, 0.0],\n )\n ],\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup the edges:\n\nmodel.edge.add(model.flow_boundary[1], model.basin[2], \"flow\")\nmodel.edge.add(model.basin[2], model.pump[3], \"flow\")\nmodel.edge.add(model.pump[3], model.level_boundary[4], \"flow\")\nmodel.edge.add(model.level_boundary[4], model.outlet[6], \"flow\")\nmodel.edge.add(model.outlet[6], model.basin[2], \"flow\")\nmodel.edge.add(model.pid_control[5], model.pump[3], \"control\")\nmodel.edge.add(model.pid_control[7], model.outlet[6], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control/ribasim.toml\")\n\nPosixPath('data/pid_control/ribasim.toml')\n\n\nNow run the model with ribasim pid_control/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\nlevel_demands = model.pid_control.time.df.target.to_numpy()[:4]\ntimes = date2num(model.pid_control.time.df.time)[:4]\nax.plot(times, level_demands, color=\"k\", ls=\":\", label=\"target level\")\npass\n\n\n\n\n\n\n\n\n\n\n4 Model with allocation (user demand)\nSetup a model:\n\nmodel = Model(\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-01-20 00:00:00\",\n)\n\nSetup the basins:\n\nbasin_data = [\n basin.Profile(area=[300_000.0, 300_000.0], level=[0.0, 1.0]),\n basin.State(level=[1.0]),\n]\n\nmodel.basin.add(\n Node(2, Point(1.0, 0.0), subnetwork_id=1),\n basin_data,\n)\nmodel.basin.add(\n Node(5, Point(3.0, 0.0), subnetwork_id=1),\n basin_data,\n)\nmodel.basin.add(\n Node(12, Point(4.5, 1.0), subnetwork_id=1),\n basin_data,\n)\n\nSetup the flow boundary:\n\nmodel.flow_boundary.add(\n Node(1, Point(0.0, 0.0), subnetwork_id=1), [flow_boundary.Static(flow_rate=[2.0])]\n)\n\nSetup the linear resistance:\n\nmodel.linear_resistance.add(\n Node(4, Point(2.0, 0.0), subnetwork_id=1),\n [linear_resistance.Static(resistance=[0.06])],\n)\n\nSetup the tabulated rating curve:\n\nmodel.tabulated_rating_curve.add(\n Node(7, Point(4.0, 0.0), subnetwork_id=1),\n [tabulated_rating_curve.Static(level=[0.0, 0.5, 1.0], flow_rate=[0.0, 0.0, 2.0])],\n)\n\nSetup the fractional flow:\n\nmodel.fractional_flow.add(\n Node(8, Point(4.5, 0.0), subnetwork_id=1),\n [fractional_flow.Static(fraction=[0.6, 0.9], control_state=[\"divert\", \"close\"])],\n)\nmodel.fractional_flow.add(\n Node(9, Point(4.5, 0.5), subnetwork_id=1),\n [fractional_flow.Static(fraction=[0.4, 0.1], control_state=[\"divert\", \"close\"])],\n)\n\nSetup the terminal:\n\nmodel.terminal.add(Node(10, Point(5.0, 0.0), subnetwork_id=1))\n\nSetup the discrete control:\n\nmodel.discrete_control.add(\n Node(11, Point(4.5, 0.25), subnetwork_id=1),\n [\n discrete_control.Condition(\n listen_node_id=[5],\n listen_node_type=[\"Basin\"],\n variable=[\"level\"],\n greater_than=[0.52],\n ),\n discrete_control.Logic(\n truth_state=[\"T\", \"F\"], control_state=[\"divert\", \"close\"]\n ),\n ],\n)\n\nSetup the users:\n\nmodel.user_demand.add(\n Node(6, Point(3.0, 1.0), subnetwork_id=1),\n [\n user_demand.Static(\n demand=[1.5], return_factor=[0.0], min_level=[-1.0], priority=[1]\n )\n ],\n)\nmodel.user_demand.add(\n Node(13, Point(5.0, 1.0), subnetwork_id=1),\n [\n user_demand.Static(\n demand=[1.0], return_factor=[0.0], min_level=[-1.0], priority=[3]\n )\n ],\n)\nmodel.user_demand.add(\n Node(3, Point(1.0, 1.0), subnetwork_id=1),\n [\n user_demand.Time(\n demand=[0.0, 1.0, 1.2, 1.2],\n return_factor=[0.0, 0.0, 0.0, 0.0],\n min_level=[-1.0, -1.0, -1.0, -1.0],\n priority=[1, 1, 2, 2],\n time=2 * [\"2020-01-01 00:00:00\", \"2020-01-20 00:00:00\"],\n )\n ],\n)\n\nSetup the allocation:\n\nmodel.allocation = ribasim.Allocation(use_allocation=True, timestep=86400)\n\nSetup the edges:\n\nmodel.edge.add(model.flow_boundary[1], model.basin[2], \"flow\", subnetwork_id=1)\nmodel.edge.add(model.basin[2], model.user_demand[3], \"flow\")\nmodel.edge.add(model.basin[2], model.linear_resistance[4], \"flow\")\nmodel.edge.add(model.linear_resistance[4], model.basin[5], \"flow\")\nmodel.edge.add(model.basin[5], model.user_demand[6], \"flow\")\nmodel.edge.add(model.basin[5], model.tabulated_rating_curve[7], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[7], model.fractional_flow[8], \"flow\")\nmodel.edge.add(model.user_demand[3], model.basin[2], \"flow\")\nmodel.edge.add(model.user_demand[6], model.basin[5], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[7], model.fractional_flow[9], \"flow\")\nmodel.edge.add(model.fractional_flow[8], model.terminal[10], \"flow\")\nmodel.edge.add(model.fractional_flow[9], model.basin[12], \"flow\")\nmodel.edge.add(model.basin[12], model.user_demand[13], \"flow\")\nmodel.edge.add(model.user_demand[13], model.terminal[10], \"flow\")\nmodel.edge.add(model.discrete_control[11], model.fractional_flow[8], \"control\")\nmodel.edge.add(model.discrete_control[11], model.fractional_flow[9], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"allocation_example/ribasim.toml\")\n\nPosixPath('data/allocation_example/ribasim.toml')\n\n\nNow run the model with ribasim allocation_example/ribasim.toml. After running the model, read back the results:\n\nimport matplotlib.ticker as plticker\n\ndf_allocation = pd.read_feather(datadir / \"allocation_example/results/allocation.arrow\")\ndf_allocation_wide = df_allocation.pivot_table(\n index=\"time\",\n columns=[\"node_type\", \"node_id\", \"priority\"],\n values=[\"demand\", \"allocated\", \"realized\"],\n)\ndf_allocation_wide = df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]\n\nfig, axs = plt.subplots(1, 3, figsize=(8, 5))\n\ndf_allocation_wide[\"demand\"].plot(ax=axs[0], ls=\":\")\ndf_allocation_wide[\"allocated\"].plot(ax=axs[1], ls=\"--\")\ndf_allocation_wide[\"realized\"].plot(ax=axs[2])\n\nfig.tight_layout()\nloc = plticker.MultipleLocator(2)\n\naxs[0].set_ylabel(\"level [m]\")\n\nfor ax, title in zip(axs, [\"Demand\", \"Allocated\", \"Abstracted\"]):\n ax.set_title(title)\n ax.set_ylim(0.0, 1.6)\n ax.xaxis.set_major_locator(loc)\n\n\n\n\n\n\n\n\nSome things to note about this plot:\n\nAbstraction behaves somewhat erratically at the start of the simulation. This is because allocation is based on flows computed in the physical layer, and at the start of the simulation these are not known yet.\nAlthough there is a plotted line for abstraction per priority, abstraction is actually accumulated over all priorities per user.\n\n\ndf_basin = pd.read_feather(datadir / \"allocation_example/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\nax.set_title(\"Basin levels\")\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\n\n\n5 Model with allocation (basin supply/demand)\nSetup a model:\n\nmodel = ribasim.Model(\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-02-01 00:00:00\",\n)\n\nSetup the basins:\n\nbasin_data = [\n basin.Profile(area=[1e3, 1e3], level=[0.0, 1.0]),\n basin.State(level=[0.5]),\n]\nmodel.basin.add(\n Node(2, Point(1.0, 0.0)),\n [\n *basin_data,\n basin.Time(\n time=[\"2020-01-01 00:00:00\", \"2020-01-16 00:00:00\"],\n drainage=[0.0, 0.0],\n potential_evaporation=[0.0, 0.0],\n infiltration=[0.0, 0.0],\n precipitation=[1e-6, 0.0],\n urban_runoff=[0.0, 0.0],\n ),\n ],\n)\nmodel.basin.add(\n Node(5, Point(2.0, -1.0)),\n [\n *basin_data,\n basin.Static(\n drainage=[0.0],\n potential_evaporation=[0.0],\n infiltration=[0.0],\n precipitation=[0.0],\n urban_runoff=[0.0],\n ),\n ],\n)\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2, 5, 5], \"area\": 1e3, \"level\": [0.0, 1.0, 0.0, 1.0]}\n)\n\nSetup the flow boundary:\n\nmodel.flow_boundary.add(\n Node(1, Point(0.0, 0.0)), [flow_boundary.Static(flow_rate=[1e-3])]\n)\n\nSetup allocation level control:\n\nmodel.level_demand.add(\n Node(4, Point(1.0, -1.0)),\n [level_demand.Static(priority=[1], min_level=[1.0], max_level=[1.5])],\n)\n\nSetup the users:\n\nmodel.user_demand.add(\n Node(3, Point(2.0, 0.0)),\n [\n user_demand.Static(\n priority=[2], demand=[1.5e-3], return_factor=[0.2], min_level=[0.2]\n )\n ],\n)\n\nSetup the allocation:\n\nmodel.allocation = ribasim.Allocation(use_allocation=True, timestep=1e5)\n\nSetup the edges:\n\nmodel.edge.add(model.flow_boundary[1], model.basin[2], \"flow\", subnetwork_id=2)\nmodel.edge.add(model.basin[2], model.user_demand[3], \"flow\")\nmodel.edge.add(model.level_demand[4], model.basin[2], \"control\")\nmodel.edge.add(model.user_demand[3], model.basin[5], \"flow\")\nmodel.edge.add(model.level_demand[4], model.basin[5], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\nmodel.write(datadir / \"level_demand/ribasim.toml\")\n\nPosixPath('data/level_demand/ribasim.toml')\n\n\nNow run the model with ribasim level_demand/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"level_demand/results/basin.arrow\")\ndf_basin = df_basin[df_basin.node_id == 2]\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nwhere_allocation = (\n df_basin_wide.index - df_basin_wide.index[0]\n).total_seconds() % model.allocation.timestep == 0\nwhere_allocation[0] = False\ndf_basin_wide[where_allocation][\"level\"].plot(\n style=\"o\",\n ax=ax,\n)\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\nIn the plot above, the line denotes the level of Basin #2 over time and the dots denote the times at which allocation optimization was run, with intervals of \\(\\Delta t_{\\text{alloc}}\\). The Basin level is a piecewise linear function of time, with several stages explained below.\nConstants:\n\n\\(d\\): UserDemand #3 demand,\n\\(\\phi\\): Basin #2 precipitation rate,\n\\(q\\): LevelBoundary flow.\n\nStages:\n\nIn the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \\(q + \\phi - d\\);\nIn the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \\(q + \\phi\\);\nIn the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \\(\\phi\\);\nIn the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \\(q + \\phi - d\\);\nAt the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \\(q - d\\).\nIn the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.",
+ "crumbs": [
+ "Python tooling",
+ "Examples"
+ ]
},
{
- "objectID": "python/test-models.html",
- "href": "python/test-models.html",
- "title": "Test models",
+ "objectID": "python/index.html",
+ "href": "python/index.html",
+ "title": "Python tooling",
"section": "",
- "text": "Ribasim developers use the following models in their testbench and in order to test new features.\n\n\nCode\nimport ribasim_testmodels\nimport matplotlib.pyplot as plt\n\nfor model_name, model_constructor in ribasim_testmodels.constructors.items():\n if model_name.startswith(\"invalid\"):\n continue\n\n model = model_constructor()\n fig, ax = plt.subplots()\n model.plot(ax)\n ax.set_title(label=model_name, loc=\"left\")\n fig.text(0, 1, model_constructor.__doc__)\n fig.tight_layout()\n plt.show()\n plt.close(fig)",
+ "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip:\npip install ribasim\nFor wheel (.whl) downloads, including nightly builds, see the download section. After downloading wheels can be installed by referring to the correct path:\npip install path/to/ribasim-*.whl\nFor documentation please see the examples and API reference.",
"crumbs": [
- "Python tooling",
- "Test models"
+ "Python tooling"
]
},
{
- "objectID": "python/reference/Terminal.html",
- "href": "python/reference/Terminal.html",
- "title": "1 Terminal",
+ "objectID": "python/reference/nodes.manning_resistance.html",
+ "href": "python/reference/nodes.manning_resistance.html",
+ "title": "1 nodes.manning_resistance",
"section": "",
- "text": "1 Terminal\nTerminal()"
+ "text": "1 nodes.manning_resistance\nnodes.manning_resistance"
},
{
- "objectID": "python/reference/FlowBoundary.html",
- "href": "python/reference/FlowBoundary.html",
- "title": "1 FlowBoundary",
+ "objectID": "python/reference/nodes.flow_boundary.html",
+ "href": "python/reference/nodes.flow_boundary.html",
+ "title": "1 nodes.flow_boundary",
"section": "",
- "text": "1 FlowBoundary\nFlowBoundary()"
+ "text": "1 nodes.flow_boundary\nnodes.flow_boundary"
},
{
- "objectID": "python/reference/UserDemand.html",
- "href": "python/reference/UserDemand.html",
- "title": "1 UserDemand",
+ "objectID": "python/reference/nodes.basin.html",
+ "href": "python/reference/nodes.basin.html",
+ "title": "1 nodes.basin",
"section": "",
- "text": "1 UserDemand\nUserDemand()"
+ "text": "1 nodes.basin\nnodes.basin"
},
{
- "objectID": "python/reference/FractionalFlow.html",
- "href": "python/reference/FractionalFlow.html",
- "title": "1 FractionalFlow",
+ "objectID": "python/reference/nodes.outlet.html",
+ "href": "python/reference/nodes.outlet.html",
+ "title": "1 nodes.outlet",
"section": "",
- "text": "1 FractionalFlow\nFractionalFlow()"
+ "text": "1 nodes.outlet\nnodes.outlet"
},
{
- "objectID": "python/reference/LinearResistance.html",
- "href": "python/reference/LinearResistance.html",
- "title": "1 LinearResistance",
+ "objectID": "python/reference/nodes.discrete_control.html",
+ "href": "python/reference/nodes.discrete_control.html",
+ "title": "1 nodes.discrete_control",
"section": "",
- "text": "1 LinearResistance\nLinearResistance()"
+ "text": "1 nodes.discrete_control\nnodes.discrete_control"
},
{
- "objectID": "python/reference/TabulatedRatingCurve.html",
- "href": "python/reference/TabulatedRatingCurve.html",
- "title": "1 TabulatedRatingCurve",
+ "objectID": "python/reference/index.html",
+ "href": "python/reference/index.html",
+ "title": "1 API Reference",
"section": "",
- "text": "1 TabulatedRatingCurve\nTabulatedRatingCurve()"
+ "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\n\n\n\n\n\n\n\nThe Edge database layer.\n\n\n\nEdgeTable\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nnodes.basin\n\n\n\nnodes.fractional_flow\n\n\n\nnodes.tabulated_rating_curve\n\n\n\nnodes.pump\n\n\n\nnodes.outlet\n\n\n\nnodes.user_demand\n\n\n\nnodes.level_boundary\n\n\n\nnodes.flow_boundary\n\n\n\nnodes.linear_resistance\n\n\n\nnodes.manning_resistance\n\n\n\nnodes.terminal\n\n\n\nnodes.discrete_control\n\n\n\nnodes.pid_control",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/DiscreteControl.html",
- "href": "python/reference/DiscreteControl.html",
- "title": "1 DiscreteControl",
+ "objectID": "python/reference/index.html#model",
+ "href": "python/reference/index.html#model",
+ "title": "1 API Reference",
"section": "",
- "text": "1 DiscreteControl\nDiscreteControl()"
+ "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/Edge.html",
- "href": "python/reference/Edge.html",
- "title": "1 Edge",
+ "objectID": "python/reference/index.html#edge",
+ "href": "python/reference/index.html#edge",
+ "title": "1 API Reference",
"section": "",
- "text": "Edge()\nDefines the connections between nodes.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.pandas.DataFrame\nTable describing the flow connections.\nrequired"
+ "text": "The Edge database layer.\n\n\n\nEdgeTable\nDefines the connections between nodes.",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/Edge.html#parameters",
- "href": "python/reference/Edge.html#parameters",
- "title": "1 Edge",
+ "objectID": "python/reference/index.html#node-types",
+ "href": "python/reference/index.html#node-types",
+ "title": "1 API Reference",
"section": "",
- "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.pandas.DataFrame\nTable describing the flow connections.\nrequired"
+ "text": "Available node types to model different situations.\n\n\n\nnodes.basin\n\n\n\nnodes.fractional_flow\n\n\n\nnodes.tabulated_rating_curve\n\n\n\nnodes.pump\n\n\n\nnodes.outlet\n\n\n\nnodes.user_demand\n\n\n\nnodes.level_boundary\n\n\n\nnodes.flow_boundary\n\n\n\nnodes.linear_resistance\n\n\n\nnodes.manning_resistance\n\n\n\nnodes.terminal\n\n\n\nnodes.discrete_control\n\n\n\nnodes.pid_control",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/Model.html",
- "href": "python/reference/Model.html",
- "title": "1 Model",
+ "objectID": "python/reference/EdgeTable.html",
+ "href": "python/reference/EdgeTable.html",
+ "title": "1 EdgeTable",
"section": "",
- "text": "Model()\nA full Ribasim model schematisation with all input.\nRibasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime.datetime\nEnd time of the simulation.\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nribasim.config.Basin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nribasim.config.FractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nribasim.config.LevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nribasim.config.FlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nribasim.config.ManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nribasim.config.TabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nribasim.config.Pump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nribasim.config.Outlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nribasim.config.Terminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nribasim.config.DiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nribasim.config.PidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser_demand\nribasim.config.UserDemand\nUserDemand node type with demand and priority.\nrequired"
+ "text": "1 EdgeTable\nEdgeTable(self, **kwargs)\nDefines the connections between nodes."
},
{
- "objectID": "python/reference/Model.html#parameters",
- "href": "python/reference/Model.html#parameters",
- "title": "1 Model",
+ "objectID": "python/reference/nodes.tabulated_rating_curve.html",
+ "href": "python/reference/nodes.tabulated_rating_curve.html",
+ "title": "1 nodes.tabulated_rating_curve",
"section": "",
- "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime.datetime\nEnd time of the simulation.\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nribasim.config.Basin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nribasim.config.FractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nribasim.config.LevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nribasim.config.FlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nribasim.config.ManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nribasim.config.TabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nribasim.config.Pump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nribasim.config.Outlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nribasim.config.Terminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nribasim.config.DiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nribasim.config.PidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser_demand\nribasim.config.UserDemand\nUserDemand node type with demand and priority.\nrequired"
+ "text": "1 nodes.tabulated_rating_curve\nnodes.tabulated_rating_curve"
},
{
"objectID": "core/allocation.html",
@@ -1057,7 +1036,7 @@
"href": "core/allocation.html#example",
"title": "Allocation",
"section": "5.1 Example",
- "text": "5.1 Example\nThe following is an example of an optimization problem for the example shown here:\n\n\nCode\nusing Ribasim\nusing Ribasim: NodeID\nusing SQLite\nusing ComponentArrays: ComponentVector\n\ntoml_path = normpath(@__DIR__, \"../../generated_testmodels/allocation_example/ribasim.toml\")\np = Ribasim.Model(toml_path).integrator.p\nu = ComponentVector(; storage = zeros(length(p.basin.node_id)))\n\nallocation_model = p.allocation.allocation_models[1]\nt = 0.0\npriority_idx = 1\n\nRibasim.set_flow!(p.graph, NodeID(:FlowBoundary, 1), NodeID(:Basin, 2), 1.0)\n\nRibasim.adjust_source_capacities!(allocation_model, p, priority_idx)\nRibasim.adjust_edge_capacities!(allocation_model, p, priority_idx)\nRibasim.set_objective_priority!(allocation_model, p, u, t, priority_idx)\n\nprintln(p.allocation.allocation_models[1].problem)\n\n\nMin F_abs_user_demand[UserDemand #3] + F_abs_user_demand[UserDemand #13] + F_abs_user_demand[UserDemand #6] + F_abs_basin[Basin #2] + F_abs_basin[Basin #5] + F_abs_basin[Basin #12]\nSubject to\n flow_conservation[Basin #2] : -F[(FlowBoundary #1, Basin #2)] - F[(Basin #5, Basin #2)] + F[(Basin #2, Basin #5)] + F[(Basin #2, UserDemand #3)] + F_basin_in[Basin #2] - F_basin_out[Basin #2] = 0\n flow_conservation[Basin #5] : F[(Basin #5, TabulatedRatingCurve #7)] + F[(Basin #5, UserDemand #6)] + F[(Basin #5, Basin #2)] - F[(Basin #2, Basin #5)] + F_basin_in[Basin #5] - F_basin_out[Basin #5] = 0\n flow_conservation[Basin #12] : -F[(TabulatedRatingCurve #7, Basin #12)] + F[(Basin #12, UserDemand #13)] + F_basin_in[Basin #12] - F_basin_out[Basin #12] = 0\n source[(FlowBoundary #1, Basin #2)] : F[(FlowBoundary #1, Basin #2)] ≤ 1\n return_flow[UserDemand #13] : F[(UserDemand #13, Terminal #10)] ≤ 0\n fractional_flow[(TabulatedRatingCurve #7, Basin #12)] : -0.4 F[(Basin #5, TabulatedRatingCurve #7)] + F[(TabulatedRatingCurve #7, Basin #12)] ≤ 0\n basin_outflow[Basin #2] : F_basin_out[Basin #2] ≤ 0\n basin_outflow[Basin #5] : F_basin_out[Basin #5] ≤ 0\n basin_outflow[Basin #12] : F_basin_out[Basin #12] ≤ 0\n F[(Basin #5, TabulatedRatingCurve #7)] ≥ 0\n F[(Basin #5, UserDemand #6)] ≥ 0\n F[(FlowBoundary #1, Basin #2)] ≥ 0\n F[(Basin #5, Basin #2)] ≥ 0\n F[(Basin #2, Basin #5)] ≥ 0\n F[(Basin #2, UserDemand #3)] ≥ 0\n F[(TabulatedRatingCurve #7, Basin #12)] ≥ 0\n F[(TabulatedRatingCurve #7, Terminal #10)] ≥ 0\n F[(Basin #12, UserDemand #13)] ≥ 0\n F[(UserDemand #13, Terminal #10)] ≥ 0\n F_basin_in[Basin #2] ≥ 0\n F_basin_in[Basin #5] ≥ 0\n F_basin_in[Basin #12] ≥ 0\n F_basin_out[Basin #2] ≥ 0\n F_basin_out[Basin #5] ≥ 0\n F_basin_out[Basin #12] ≥ 0\n abs_positive_user_demand[UserDemand #3] : -F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 0\n abs_positive_user_demand[UserDemand #13] : -F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_positive_user_demand[UserDemand #6] : -F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ -1.5\n abs_negative_user_demand[UserDemand #3] : F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 0\n abs_negative_user_demand[UserDemand #13] : F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_negative_user_demand[UserDemand #6] : F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 1.5\n abs_positive_basin[Basin #2] : -F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_positive_basin[Basin #5] : -F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n abs_positive_basin[Basin #12] : -F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0\n abs_negative_basin[Basin #2] : F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_negative_basin[Basin #5] : F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n abs_negative_basin[Basin #12] : F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0",
+ "text": "5.1 Example\nThe following is an example of an optimization problem for the example shown here:\n\n\nCode\nusing Ribasim\nusing Ribasim: NodeID\nusing SQLite\nusing ComponentArrays: ComponentVector\n\ntoml_path = normpath(@__DIR__, \"../../generated_testmodels/allocation_example/ribasim.toml\")\np = Ribasim.Model(toml_path).integrator.p\nu = ComponentVector(; storage = zeros(length(p.basin.node_id)))\n\nallocation_model = p.allocation.allocation_models[1]\nt = 0.0\npriority_idx = 1\n\nRibasim.set_flow!(p.graph, NodeID(:FlowBoundary, 1), NodeID(:Basin, 2), 1.0)\n\nRibasim.adjust_source_capacities!(allocation_model, p, priority_idx)\nRibasim.adjust_edge_capacities!(allocation_model, p, priority_idx)\nRibasim.set_objective_priority!(allocation_model, p, u, t, priority_idx)\n\nprintln(p.allocation.allocation_models[1].problem)\n\n\nMin F_abs_user_demand[UserDemand #3] + F_abs_user_demand[UserDemand #6] + F_abs_user_demand[UserDemand #13] + F_abs_basin[Basin #12] + F_abs_basin[Basin #2] + F_abs_basin[Basin #5]\nSubject to\n flow_conservation[Basin #12] : -F[(TabulatedRatingCurve #7, Basin #12)] + F[(Basin #12, UserDemand #13)] + F_basin_in[Basin #12] - F_basin_out[Basin #12] = 0\n flow_conservation[Basin #2] : -F[(Basin #5, Basin #2)] + F[(Basin #2, UserDemand #3)] - F[(FlowBoundary #1, Basin #2)] + F[(Basin #2, Basin #5)] + F_basin_in[Basin #2] - F_basin_out[Basin #2] = 0\n flow_conservation[Basin #5] : F[(Basin #5, Basin #2)] + F[(Basin #5, TabulatedRatingCurve #7)] + F[(Basin #5, UserDemand #6)] - F[(Basin #2, Basin #5)] + F_basin_in[Basin #5] - F_basin_out[Basin #5] = 0\n abs_positive_user_demand[UserDemand #3] : -F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ -1.5\n abs_positive_user_demand[UserDemand #6] : -F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 0\n abs_positive_user_demand[UserDemand #13] : -F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_negative_user_demand[UserDemand #3] : F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 1.5\n abs_negative_user_demand[UserDemand #6] : F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 0\n abs_negative_user_demand[UserDemand #13] : F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_positive_basin[Basin #12] : -F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0\n abs_positive_basin[Basin #2] : -F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_positive_basin[Basin #5] : -F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n abs_negative_basin[Basin #12] : F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0\n abs_negative_basin[Basin #2] : F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_negative_basin[Basin #5] : F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n source[(FlowBoundary #1, Basin #2)] : F[(FlowBoundary #1, Basin #2)] ≤ 1\n return_flow[UserDemand #13] : F[(UserDemand #13, Terminal #10)] ≤ 0\n fractional_flow[(TabulatedRatingCurve #7, Basin #12)] : F[(TabulatedRatingCurve #7, Basin #12)] - 0.4 F[(Basin #5, TabulatedRatingCurve #7)] ≤ 0\n basin_outflow[Basin #12] : F_basin_out[Basin #12] ≤ 0\n basin_outflow[Basin #2] : F_basin_out[Basin #2] ≤ 0\n basin_outflow[Basin #5] : F_basin_out[Basin #5] ≤ 0\n F[(UserDemand #13, Terminal #10)] ≥ 0\n F[(TabulatedRatingCurve #7, Basin #12)] ≥ 0\n F[(Basin #5, Basin #2)] ≥ 0\n F[(Basin #5, TabulatedRatingCurve #7)] ≥ 0\n F[(Basin #5, UserDemand #6)] ≥ 0\n F[(Basin #2, UserDemand #3)] ≥ 0\n F[(TabulatedRatingCurve #7, Terminal #10)] ≥ 0\n F[(FlowBoundary #1, Basin #2)] ≥ 0\n F[(Basin #12, UserDemand #13)] ≥ 0\n F[(Basin #2, Basin #5)] ≥ 0\n F_basin_in[Basin #12] ≥ 0\n F_basin_in[Basin #2] ≥ 0\n F_basin_in[Basin #5] ≥ 0\n F_basin_out[Basin #12] ≥ 0\n F_basin_out[Basin #2] ≥ 0\n F_basin_out[Basin #5] ≥ 0",
"crumbs": [
"Julia core",
"Allocation"
Here \(p > 0\) is the threshold value which determines the interval \([0,p]\) of the smooth transition between \(0\) and \(1\), see the plot below.
-Code
import numpy as np
diff --git a/core/usage.html b/core/usage.html
index 30d032bca..6b83b7a7f 100644
--- a/core/usage.html
+++ b/core/usage.html
@@ -1738,10 +1738,10 @@ \(\Delta t\) can be supplied.
-
-
-
-
+
+
+
+
@@ -1759,22 +1759,22 @@
-listen_feature_id
-Int
+listen_node_type
+String
-
-sorted per node_id
+known node type
-listen_feature_type
-String
+listen_node_id
+Int
-
-known node type
+sorted per node_id
variable
String
-
-must be “level” or “flow_rate”, sorted per listen_feature_id
+must be “level” or “flow_rate”, sorted per listen_node_id
greater_than
@@ -1886,30 +1886,36 @@ 18 PidControl(optional, default true)
+listen_node_type
+Int
+-
+known node type
+
+
listen_node_id
Int
-
-
-
+
target
Float64
\(m\)
-
-
+
proportional
Float64
\(s^{-1}\)
-
-
+
integral
Float64
\(s^{-2}\)
-
-
+
derivative
Float64
-
@@ -1943,30 +1949,36 @@ sorted per node_id
+listen_node_type
+Int
+-
+known node type
+
+
listen_node_id
Int
-
-
-
+
target
Float64
\(m\)
-
-
+
proportional
Float64
\(s^{-1}\)
-
-
+
integral
Float64
\(s^{-2}\)
-
-
+
derivative
Float64
-
diff --git a/core/validation.html b/core/validation.html
index 55cf077fd..0545fbdba 100644
--- a/core/validation.html
+++ b/core/validation.html
@@ -262,7 +262,7 @@ Validation
1 Connectivity
In the table below, each column shows which node types are allowed to be downstream (or ‘down-control’) of the node type at the top of the column.
-
+
Code
using Ribasim
@@ -546,7 +546,7 @@ 1 Connectivity
2 Neighbor amounts
The table below shows for each node type between which bounds the amount of in- and outneighbors must be, for both flow and control edges.
-
+
Code
= Vector{String}()
diff --git a/python/examples.html b/python/examples.html
index d6b315f20..35d6696c7 100644
--- a/python/examples.html
+++ b/python/examples.html
@@ -237,11 +237,10 @@ flow_in_min On this page
- 1 Basic model with static forcing
- - 2 Update the basic model with transient forcing
- - 3 Model with discrete control
- - 4 Model with PID control
- - 5 Model with allocation (user demand)
- - 6 Model with allocation (basin supply/demand)
+ - 2 Model with discrete control
+ - 3 Model with PID control
+ - 4 Model with allocation (user demand)
+ - 5 Model with allocation (basin supply/demand)
@@ -270,448 +269,259 @@ Examples
1 Basic model with static forcing
-from pathlib import Path
-
-import geopandas as gpd
+import shutil
+from pathlib import Path
+
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
-import ribasim
+import ribasim
+from ribasim.config import Node
+from ribasim.model import Model
+from ribasim.nodes import (
+
+ basin,
+ discrete_control,
+ flow_boundary,
+ fractional_flow,
+ level_boundary,
+ level_demand,
+ linear_resistance,
+ manning_resistance,
+ outlet,
+ pid_control,
+ pump,
+ tabulated_rating_curve,
+ user_demand,
+ )from shapely.geometry import Point
+
+
+= Path("data")
+ datadir =True) shutil.rmtree(datadir, ignore_errors
+
+
+= Model(starttime="2020-01-01 00:00:00", endtime="2021-01-01 00:00:00") model
Setup the basins:
-
-= pd.DataFrame(
- profile ={
- data"node_id": [1, 1, 3, 3, 6, 6, 9, 9],
- "area": [0.01, 1000.0] * 4,
- "level": [0.0, 1.0] * 4,
-
- }
- )
-# Convert steady forcing to m/s
-# 2 mm/d precipitation, 1 mm/d evaporation
-= 24 * 3600
- seconds_in_day = 0.002 / seconds_in_day
- precipitation = 0.001 / seconds_in_day
- evaporation
-= pd.DataFrame(
- static ={
- data"node_id": [0],
- "potential_evaporation": [evaporation],
- "precipitation": [precipitation],
-
- }
- )= static.iloc[[0, 0, 0, 0]]
- static "node_id"] = [1, 3, 6, 9]
- static[
-= ribasim.Basin(profile=profile, static=static) basin
+
+= pd.date_range(model.starttime, model.endtime)
+ time = time.day_of_year.to_numpy()
+ day_of_year = 24 * 60 * 60
+ seconds_per_day = (
+ evaporation -1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day
+ (
+ )= np.random.default_rng(seed=0)
+ rng = (
+ precipitation =-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day
+ rng.lognormal(mean
+ )
+# Convert steady forcing to m/s
+# 2 mm/d precipitation, 1 mm/d evaporation
+
+= [
+ basin_data =[0.01, 1000.0], level=[0.0, 1.0]),
+ basin.Profile(area
+ basin.Time(=pd.date_range(model.starttime, model.endtime),
+ time=0.0,
+ drainage=evaporation,
+ potential_evaporation=0.0,
+ infiltration=precipitation,
+ precipitation=0.0,
+ urban_runoff
+ ),=[1.4]),
+ basin.State(level
+ ]
+1, Point(0.0, 0.0)), basin_data)
+ model.basin.add(Node(3, Point(2.0, 0.0)), basin_data)
+ model.basin.add(Node(6, Point(3.0, 2.0)), basin_data)
+ model.basin.add(Node(9, Point(5.0, 0.0)), basin_data) model.basin.add(Node(
Setup linear resistance:
-
-= ribasim.LinearResistance(
- linear_resistance =pd.DataFrame(
- static={"node_id": [10, 12], "resistance": [5e3, (3600.0 * 24) / 100.0]}
- data
- ) )
+
+
+ model.linear_resistance.add(10, Point(6.0, 0.0)),
+ Node(=[5e3])],
+ [linear_resistance.Static(resistance
+ )
+ model.linear_resistance.add(12, Point(2.0, 1.0)),
+ Node(=[3600.0 * 24.0 / 100.0])],
+ [linear_resistance.Static(resistance )
Setup Manning resistance:
-
-= ribasim.ManningResistance(
- manning_resistance =pd.DataFrame(
- static={
- data"node_id": [2],
- "length": [900.0],
- "manning_n": [0.04],
- "profile_width": [6.0],
- "profile_slope": [3.0],
-
- }
- ) )
+
+
+ model.manning_resistance.add(2, Point(1.0, 0.0)),
+ Node(
+ [
+ manning_resistance.Static(=[900], manning_n=[0.04], profile_width=[6.0], profile_slope=[3.0]
+ length
+ )
+ ], )
Set up a rating curve node:
-
-# Discharge: lose 1% of storage volume per day at storage = 1000.0.
-= 1000.0 * 0.01 / seconds_in_day
- q1000
-= ribasim.TabulatedRatingCurve(
- rating_curve =pd.DataFrame(
- static={
- data"node_id": [4, 4],
- "level": [0.0, 1.0],
- "flow_rate": [0.0, q1000],
-
- }
- ) )
+
+
+ model.tabulated_rating_curve.add(4, Point(3.0, 0.0)),
+ Node(=[0.0, 1.0], flow_rate=[0.0, 10 / 86400])],
+ [tabulated_rating_curve.Static(level )
Setup fractional flows:
-
-= ribasim.FractionalFlow(
- fractional_flow =pd.DataFrame(
- static={
- data"node_id": [5, 8, 13],
- "fraction": [0.3, 0.6, 0.1],
-
- }
- ) )
+
+
+ model.fractional_flow.add(5, Point(3.0, 1.0)), [fractional_flow.Static(fraction=[0.3])]
+ Node(
+ )
+ model.fractional_flow.add(8, Point(4.0, 0.0)), [fractional_flow.Static(fraction=[0.6])]
+ Node(
+ )
+ model.fractional_flow.add(13, Point(3.0, -1.0)),
+ Node(=[0.1])],
+ [fractional_flow.Static(fraction )
Setup pump:
-
-= ribasim.Pump(
- pump =pd.DataFrame(
- static={
- data"node_id": [7],
- "flow_rate": [0.5 / 3600],
-
- }
- ) )
+
+7, Point(4.0, 1.0)), [pump.Static(flow_rate=[0.5 / 3600])]) model.pump.add(Node(
Setup level boundary:
-
-= ribasim.LevelBoundary(
- level_boundary =pd.DataFrame(
- static={
- data"node_id": [11, 17],
- "level": [0.5, 1.5],
-
- }
- ) )
+
+
+ model.level_boundary.add(11, Point(2.0, 2.0)), [level_boundary.Static(level=[0.5])]
+ Node(
+ )
+ model.level_boundary.add(17, Point(6.0, 1.0)), [level_boundary.Static(level=[1.5])]
+ Node( )
Setup flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(
- static={
- data"node_id": [15, 16],
- "flow_rate": [1e-4, 1e-4],
-
- }
- ) )
+
+
+ model.flow_boundary.add(15, Point(3.0, 3.0)), [flow_boundary.Static(flow_rate=[1e-4])]
+ Node(
+ )
+ model.flow_boundary.add(16, Point(0.0, 1.0)), [flow_boundary.Static(flow_rate=[1e-4])]
+ Node( )
Setup terminal:
-
-= ribasim.Terminal(
- terminal =pd.DataFrame(
- static={
- data"node_id": [14],
-
- }
- ) )
-
-Set up the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: Basin,
- (1.0, 0.0), # 2: ManningResistance
- (2.0, 0.0), # 3: Basin
- (3.0, 0.0), # 4: TabulatedRatingCurve
- (3.0, 1.0), # 5: FractionalFlow
- (3.0, 2.0), # 6: Basin
- (4.0, 1.0), # 7: Pump
- (4.0, 0.0), # 8: FractionalFlow
- (5.0, 0.0), # 9: Basin
- (6.0, 0.0), # 10: LinearResistance
- (2.0, 2.0), # 11: LevelBoundary
- (2.0, 1.0), # 12: LinearResistance
- (3.0, -1.0), # 13: FractionalFlow
- (3.0, -2.0), # 14: Terminal
- (3.0, 3.0), # 15: FlowBoundary
- (0.0, 1.0), # 16: FlowBoundary
- (6.0, 1.0), # 17: LevelBoundary
- (
- ]
- )= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= ribasim.Node.node_ids_and_types(
- node_id, node_type
- basin,
- manning_resistance,
- rating_curve,
- pump,
- fractional_flow,
- linear_resistance,
- level_boundary,
- flow_boundary,
- terminal,
- )
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type},
- data=pd.Index(node_id, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
+
+14, Point(3.0, -2.0))) model.terminal.add(Node(
Setup the edges:
-
-= np.array(
- from_id 1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64
- [
- )= np.array(
- to_id 2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64
- [
- )= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": len(from_id) * ["flow"],
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=level_boundary,
- level_boundary=flow_boundary,
- flow_boundary=pump,
- pump=linear_resistance,
- linear_resistance=manning_resistance,
- manning_resistance=rating_curve,
- tabulated_rating_curve=fractional_flow,
- fractional_flow=terminal,
- terminal="2020-01-01 00:00:00",
- starttime="2021-01-01 00:00:00",
- endtime )
+
+1], model.manning_resistance[2], "flow")
+ model.edge.add(model.basin[2], model.basin[3], "flow")
+ model.edge.add(model.manning_resistance[3], model.tabulated_rating_curve[4], "flow")
+ model.edge.add(model.basin[4], model.fractional_flow[5], "flow")
+ model.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[8], "flow")
+ model.edge.add(model.tabulated_rating_curve[5], model.basin[6], "flow")
+ model.edge.add(model.fractional_flow[6], model.pump[7], "flow")
+ model.edge.add(model.basin[8], model.basin[9], "flow")
+ model.edge.add(model.fractional_flow[7], model.basin[9], "flow")
+ model.edge.add(model.pump[9], model.linear_resistance[10], "flow")
+ model.edge.add(model.basin[11], model.linear_resistance[12], "flow")
+ model.edge.add(model.level_boundary[12], model.basin[3], "flow")
+ model.edge.add(model.linear_resistance[4], model.fractional_flow[13], "flow")
+ model.edge.add(model.tabulated_rating_curve[13], model.terminal[14], "flow")
+ model.edge.add(model.fractional_flow[15], model.basin[6], "flow")
+ model.edge.add(model.flow_boundary[16], model.basin[1], "flow")
+ model.edge.add(model.flow_boundary[10], model.level_boundary[17], "flow") model.edge.add(model.linear_resistance[
Let’s take a look at the model:
-
+
model.plot()
Write the model to a TOML and GeoPackage:
-
-= Path("data")
- datadir / "basic/ribasim.toml") model.write(datadir
+
+/ "basic/ribasim.toml") model.write(datadir
PosixPath('data/basic/ribasim.toml')
-
-
-2 Update the basic model with transient forcing
-This assumes you have already created the basic model with static forcing.
-
-import numpy as np
-import pandas as pd
-import ribasim
-import xarray as xr
-
-
-= ribasim.Model(filepath=datadir / "basic/ribasim.toml") model
-
-Can't read from data/basic/database.gpkg:Basin / area
-
-
-
-= pd.date_range(model.starttime, model.endtime)
- time = time.day_of_year.to_numpy()
- day_of_year = 24 * 60 * 60
- seconds_per_day = (
- evaporation -1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day
- (
- )= np.random.default_rng(seed=0)
- rng = (
- precipitation =-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day
- rng.lognormal(mean )
-
-We’ll use xarray to easily broadcast the values.
-
-= (
- timeseries
- pd.DataFrame(={
- data"node_id": 1,
- "time": pd.date_range(model.starttime, model.endtime),
- "drainage": 0.0,
- "potential_evaporation": evaporation,
- "infiltration": 0.0,
- "precipitation": precipitation,
- "urban_runoff": 0.0,
-
- }
- )"time")
- .set_index(
- .to_xarray()
- )
-= model.basin.static.df["node_id"].to_numpy()
- basin_ids = xr.DataArray(
- basin_nodes len(basin_ids)), coords={"node_id": basin_ids}, dims=["node_id"]
- np.ones(
- )= (timeseries * basin_nodes).to_dataframe().reset_index() forcing
-
-
-= pd.DataFrame(
- state ={
- data"node_id": basin_ids,
- "level": 1.4,
-
- } )
-
-
-= forcing
- model.basin.time.df = state model.basin.state.df
-
-
-/ "basic_transient/ribasim.toml") model.write(datadir
-
-PosixPath('data/basic_transient/ribasim.toml')
-
-
-Now run the model with ribasim basic_transient/ribasim.toml
. After running the model, read back the results:
-
-= pd.read_feather(datadir / "basic_transient/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )"level"].plot() df_basin_wide[
+Now run the model with ribasim basic/ribasim.toml
. After running the model, read back the results:
+
+= pd.read_feather(datadir / "basic/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )"level"].plot() df_basin_wide[
-
-= pd.read_feather(datadir / "basic_transient/results/flow.arrow")
- df_flow "edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
- df_flow["flow_m3d"] = df_flow.flow_rate * 86400
- df_flow[= df_flow.pivot_table(index="time", columns="edge", values="flow_m3d").plot()
- ax =(1.3, 1), title="Edge") ax.legend(bbox_to_anchor
+
+= pd.read_feather(datadir / "basic/results/flow.arrow")
+ df_flow "edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
+ df_flow["flow_m3d"] = df_flow.flow_rate * 86400
+ df_flow[= df_flow.pivot_table(index="time", columns="edge", values="flow_m3d").plot()
+ ax =(1.3, 1), title="Edge") ax.legend(bbox_to_anchor
-
-type(df_flow)
-
-pandas.core.frame.DataFrame
-
-
-
-3 Model with discrete control
+
+2 Model with discrete control
The model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve
, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.
-Set up the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: Basin
- (1.0, 1.0), # 2: Pump
- (1.0, -1.0), # 3: Pump
- (2.0, 0.0), # 4: LevelBoundary
- (-1.0, 0.0), # 5: TabulatedRatingCurve
- (-2.0, 0.0), # 6: Terminal
- (1.0, 0.0), # 7: DiscreteControl
- (
- ]
- )
-= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= [
- node_type "Basin",
- "Pump",
- "Pump",
- "LevelBoundary",
- "TabulatedRatingCurve",
- "Terminal",
- "DiscreteControl",
-
- ]
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type},
- data=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)
- from_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)
- to_id
-= 6 * ["flow"] + 2 * ["control"]
- edge_type
-= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={"from_node_id": from_id, "to_node_id": to_id, "edge_type": edge_type},
- data=lines,
- geometry="EPSG:28992",
- crs
- ) )
-
Setup the basins:
-
-= pd.DataFrame(
- profile ={
- data"node_id": [1, 1],
- "area": [1000.0, 1000.0],
- "level": [0.0, 1.0],
-
- }
- )
-= pd.DataFrame(data={"node_id": [1], "level": [20.0]})
- state
-= ribasim.Basin(profile=profile, state=state) basin
+
+= Model(starttime="2020-01-01 00:00:00", endtime="2021-01-01 00:00:00") model
+
+
+
+ model.basin.add(1, Point(0.0, 0.0)),
+ Node(
+ [=[1000.0, 1000.0], level=[0.0, 1.0]),
+ basin.Profile(area=[20.0]),
+ basin.State(level
+ ], )
Setup the discrete control:
-
-= pd.DataFrame(
- condition ={
- data"node_id": 3 * [7],
- "listen_feature_id": 3 * [1],
- "variable": 3 * ["level"],
- "greater_than": [5.0, 10.0, 15.0], # min, setpoint, max
-
- }
- )
-= pd.DataFrame(
- logic ={
- data"node_id": 5 * [7],
- "truth_state": ["FFF", "U**", "T*F", "**D", "TTT"],
- "control_state": ["in", "in", "none", "out", "out"],
-
- }
- )
-= ribasim.DiscreteControl(condition=condition, logic=logic) discrete_control
-
-The above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.
+
+
+ model.discrete_control.add(7, Point(1.0, 0.0)),
+ Node(
+ [
+ discrete_control.Condition(=[1, 1, 1],
+ listen_node_id=["Basin", "Basin", "Basin"],
+ listen_node_type=["level", "level", "level"],
+ variable=[5.0, 10.0, 15.0],
+ greater_than
+ ),
+ discrete_control.Logic(=["FFF", "U**", "T*F", "**D", "TTT"],
+ truth_state=["in", "in", "none", "out", "out"],
+ control_state
+ ),
+ ], )
+
+The above control logic can be summarized as follows:
+
+- If the level gets above the maximum, activate the control state “out” until the setpoint is reached;
+- If the level gets below the minimum, active the control state “in” until the setpoint is reached;
+- Otherwise activate the control state “none”.
+
Setup the pump:
-
-= ribasim.Pump(
- pump =pd.DataFrame(
- static={
- data"node_id": 3 * [2] + 3 * [3],
- "control_state": 2 * ["none", "in", "out"],
- "flow_rate": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],
-
- }
- ) )
+
+
+ model.pump.add(2, Point(1.0, 1.0)),
+ Node(=["none", "in", "out"], flow_rate=[0.0, 2e-3, 0.0])],
+ [pump.Static(control_state
+ )
+ model.pump.add(3, Point(1.0, -1.0)),
+ Node(=["none", "in", "out"], flow_rate=[0.0, 0.0, 2e-3])],
+ [pump.Static(control_state )
The pump data defines the following:
@@ -741,616 +551,426 @@ 3 Model with disc
Setup the level boundary:
-
-= ribasim.LevelBoundary(
- level_boundary =pd.DataFrame(data={"node_id": [4], "level": [10.0]})
- static )
+
+
+ model.level_boundary.add(4, Point(2.0, 0.0)), [level_boundary.Static(level=[10.0])]
+ Node( )
Setup the rating curve:
-
-= ribasim.TabulatedRatingCurve(
- rating_curve =pd.DataFrame(
- static={"node_id": 2 * [5], "level": [2.0, 15.0], "flow_rate": [0.0, 1e-3]}
- data
- ) )
+
+
+ model.tabulated_rating_curve.add(5, Point(-1.0, 0.0)),
+ Node(=[2.0, 15.0], flow_rate=[0.0, 1e-3])],
+ [tabulated_rating_curve.Static(level )
Setup the terminal:
-
-= ribasim.Terminal(static=pd.DataFrame(data={"node_id": [6]})) terminal
-
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=pump,
- pump=level_boundary,
- level_boundary=rating_curve,
- tabulated_rating_curve=terminal,
- terminal=discrete_control,
- discrete_control="2020-01-01 00:00:00",
- starttime="2021-01-01 00:00:00",
- endtime )
+
+6, Point(-2.0, 0.0))) model.terminal.add(Node(
+
+Setup edges:
+
+1], model.pump[3], "flow")
+ model.edge.add(model.basin[3], model.level_boundary[4], "flow")
+ model.edge.add(model.pump[4], model.pump[2], "flow")
+ model.edge.add(model.level_boundary[2], model.basin[1], "flow")
+ model.edge.add(model.pump[1], model.tabulated_rating_curve[5], "flow")
+ model.edge.add(model.basin[5], model.terminal[6], "flow")
+ model.edge.add(model.tabulated_rating_curve[7], model.pump[2], "control")
+ model.edge.add(model.discrete_control[7], model.pump[3], "control") model.edge.add(model.discrete_control[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Listen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.
-
-= Path("data")
- datadir / "level_setpoint_with_minmax/ribasim.toml") model.write(datadir
-
+
+= Path("data")
+ datadir / "level_setpoint_with_minmax/ribasim.toml") model.write(datadir
+
PosixPath('data/level_setpoint_with_minmax/ribasim.toml')
Now run the model with level_setpoint_with_minmax/ribasim.toml
. After running the model, read back the results:
-
-from matplotlib.dates import date2num
-
-= pd.read_feather(datadir / "level_setpoint_with_minmax/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )
-= df_basin_wide["level"].plot()
- ax
-= model.discrete_control.condition.df.greater_than
- greater_than
-
- ax.hlines(
- greater_than,0],
- df_basin.time[max(),
- df_basin.time.=1,
- lw="--",
- ls="k",
- color
- )
-= pd.read_feather(
- df_control / "level_setpoint_with_minmax/results/control.arrow"
- datadir
- )
-= ax.get_ybound()
- y_min, y_max 2], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
- ax.fill_between(df_control.time[:2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
- ax.fill_between(df_control.time[
-
- ax.set_xticks(
- date2num(df_control.time).tolist(),
- df_control.control_state.tolist(),=50,
- rotation
- )
-"min", "setpoint", "max"])
- ax.set_yticks(greater_than, ["level")
- ax.set_ylabel( plt.show()
+
+from matplotlib.dates import date2num
+
+= pd.read_feather(datadir / "level_setpoint_with_minmax/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )
+= df_basin_wide["level"].plot()
+ ax
+= model.discrete_control.condition.df.greater_than
+ greater_than
+
+ ax.hlines(
+ greater_than,0],
+ df_basin.time[max(),
+ df_basin.time.=1,
+ lw="--",
+ ls="k",
+ color
+ )
+= pd.read_feather(
+ df_control / "level_setpoint_with_minmax/results/control.arrow"
+ datadir
+ )
+= ax.get_ybound()
+ y_min, y_max 2], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
+ ax.fill_between(df_control.time[:2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color="C0")
+ ax.fill_between(df_control.time[
+
+ ax.set_xticks(
+ date2num(df_control.time).tolist(),
+ df_control.control_state.tolist(),=50,
+ rotation
+ )
+"min", "setpoint", "max"])
+ ax.set_yticks(greater_than, ["level")
+ ax.set_ylabel( plt.show()
The highlighted regions show where a pump is active.
-Let’s print an overview of what happened with control:
-
-
- model.print_discrete_control_record(/ "level_setpoint_with_minmax/results/control.arrow"
- datadir )
-
-0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:
- For node ID 1 (Basin): level > 5.0
- For node ID 1 (Basin): level > 10.0
- For node ID 1 (Basin): level > 15.0
-
- This yielded control state "out":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan
-
-1. At 2020-02-08 19:07:33.550000 the control node with ID 7 reached truth state TFF:
- For node ID 1 (Basin): level > 5.0
- For node ID 1 (Basin): level < 10.0
- For node ID 1 (Basin): level < 15.0
-
- This yielded control state "none":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
-
-2. At 2020-07-05 09:06:07.383000 the control node with ID 7 reached truth state FFF:
- For node ID 1 (Basin): level < 5.0
- For node ID 1 (Basin): level < 10.0
- For node ID 1 (Basin): level < 15.0
-
- This yielded control state "in":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
-
-3. At 2020-08-11 06:17:03.985000 the control node with ID 7 reached truth state TTF:
- For node ID 1 (Basin): level > 5.0
- For node ID 1 (Basin): level > 10.0
- For node ID 1 (Basin): level < 15.0
-
- This yielded control state "none":
- For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
- For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan
-
-
-
-Note that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.
-
-4 Model with PID control
-Set up the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: FlowBoundary
- (1.0, 0.0), # 2: Basin
- (2.0, 0.5), # 3: Pump
- (3.0, 0.0), # 4: LevelBoundary
- (1.5, 1.0), # 5: PidControl
- (2.0, -0.5), # 6: outlet
- (1.5, -1.0), # 7: PidControl
- (
- ]
- )
-= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= [
- node_type "FlowBoundary",
- "Basin",
- "Pump",
- "LevelBoundary",
- "PidControl",
- "Outlet",
- "PidControl",
-
- ]
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type},
- data=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)
- from_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)
- to_id
-= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": 5 * ["flow"] + 2 * ["control"],
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
+
+3 Model with PID control
+Set up the model:
+
+= Model(
+ model ="2020-01-01 00:00:00",
+ starttime="2020-12-01 00:00:00",
+ endtime )
Setup the basins:
-
-= pd.DataFrame(
- profile ={"node_id": [2, 2], "level": [0.0, 1.0], "area": [1000.0, 1000.0]}
- data
- )
-= pd.DataFrame(
- state ={
- data"node_id": [2],
- "level": [6.0],
-
- }
- )
-= ribasim.Basin(profile=profile, state=state) basin
+
+
+ model.basin.add(2, Point(1.0, 0.0)),
+ Node(=[1000.0, 1000.0], level=[0.0, 1.0]), basin.State(level=[6.0])],
+ [basin.Profile(area )
Setup the pump:
-
-= ribasim.Pump(
- pump =pd.DataFrame(
- static={
- data"node_id": [3],
- "flow_rate": [0.0], # Will be overwritten by PID controller
-
- }
- ) )
+
+
+ model.pump.add(3, Point(2.0, 0.5)),
+ Node(=[0.0])], # Will be overwritten by PID controller
+ [pump.Static(flow_rate )
Setup the outlet:
-
-= ribasim.Outlet(
- outlet =pd.DataFrame(
- static={
- data"node_id": [6],
- "flow_rate": [0.0], # Will be overwritten by PID controller
-
- }
- ) )
+
+
+ model.outlet.add(6, Point(2.0, -0.5)),
+ Node(=[0.0])], # Will be overwritten by PID controller
+ [outlet.Static(flow_rate )
Setup flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(data={"node_id": [1], "flow_rate": [1e-3]})
- static )
+
+
+ model.flow_boundary.add(1, Point(0.0, 0.0)),
+ Node(=[1e-3])],
+ [flow_boundary.Static(flow_rate )
Setup flow boundary:
-
-= ribasim.LevelBoundary(
- level_boundary =pd.DataFrame(
- static={
- data"node_id": [4],
- "level": [1.0], # Not relevant
-
- }
- ) )
+
+
+ model.level_boundary.add(4, Point(3.0, 0.0)),
+ Node(=[1])],
+ [level_boundary.Static(level )
Setup PID control:
-
-= ribasim.PidControl(
- pid_control =pd.DataFrame(
- time={
- data"node_id": 4 * [5, 7],
- "time": [
- "2020-01-01 00:00:00",
- "2020-01-01 00:00:00",
- "2020-05-01 00:00:00",
- "2020-05-01 00:00:00",
- "2020-07-01 00:00:00",
- "2020-07-01 00:00:00",
- "2020-12-01 00:00:00",
- "2020-12-01 00:00:00",
-
- ],"listen_node_id": 4 * [2, 2],
- "target": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],
- "proportional": 4 * [-1e-3, 1e-3],
- "integral": 4 * [-1e-7, 1e-7],
- "derivative": 4 * [0.0, 0.0],
-
- }
- ) )
+
+
+ model.pid_control.add(5, Point(1.5, 1.0)),
+ Node(
+ [
+ pid_control.Time(=[
+ time"2020-01-01 00:00:00",
+ "2020-05-01 00:00:00",
+ "2020-07-01 00:00:00",
+ "2020-12-01 00:00:00",
+
+ ],=[2, 2, 2, 2],
+ listen_node_id=["Basin", "Basin", "Basin", "Basin"],
+ listen_node_type=[5.0, 5.0, 7.5, 7.5],
+ target=[-1e-3, 1e-3, 1e-3, 1e-3],
+ proportional=[-1e-7, 1e-7, -1e-7, 1e-7],
+ integral=[0.0, 0.0, 0.0, 0.0],
+ derivative
+ )
+ ],
+ )
+ model.pid_control.add(7, Point(1.5, -1.0)),
+ Node(
+ [
+ pid_control.Time(=[
+ time"2020-01-01 00:00:00",
+ "2020-05-01 00:00:00",
+ "2020-07-01 00:00:00",
+ "2020-12-01 00:00:00",
+
+ ],=[2, 2, 2, 2],
+ listen_node_id=["Basin", "Basin", "Basin", "Basin"],
+ listen_node_type=[5.0, 5.0, 7.5, 7.5],
+ target=[-1e-3, 1e-3, 1e-3, 1e-3],
+ proportional=[-1e-7, 1e-7, -1e-7, 1e-7],
+ integral=[0.0, 0.0, 0.0, 0.0],
+ derivative
+ )
+ ], )
Note that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=flow_boundary,
- flow_boundary=level_boundary,
- level_boundary=pump,
- pump=outlet,
- outlet=pid_control,
- pid_control="2020-01-01 00:00:00",
- starttime="2020-12-01 00:00:00",
- endtime )
+Setup the edges:
+
+1], model.basin[2], "flow")
+ model.edge.add(model.flow_boundary[2], model.pump[3], "flow")
+ model.edge.add(model.basin[3], model.level_boundary[4], "flow")
+ model.edge.add(model.pump[4], model.outlet[6], "flow")
+ model.edge.add(model.level_boundary[6], model.basin[2], "flow")
+ model.edge.add(model.outlet[5], model.pump[3], "control")
+ model.edge.add(model.pid_control[7], model.outlet[6], "control") model.edge.add(model.pid_control[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Write the model to a TOML and GeoPackage:
-
-= Path("data")
- datadir / "pid_control/ribasim.toml") model.write(datadir
-
+
+= Path("data")
+ datadir / "pid_control/ribasim.toml") model.write(datadir
+
PosixPath('data/pid_control/ribasim.toml')
Now run the model with ribasim pid_control/ribasim.toml
. After running the model, read back the results:
-
-from matplotlib.dates import date2num
-
-= pd.read_feather(datadir / "pid_control/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )= df_basin_wide["level"].plot()
- ax "level [m]")
- ax.set_ylabel(
-# Plot target level
-= model.pid_control.time.df.target.to_numpy()[:4]
- level_demands = date2num(model.pid_control.time.df.time)[:4]
- times ="k", ls=":", label="target level")
- ax.plot(times, level_demands, colorpass
+
+from matplotlib.dates import date2num
+
+= pd.read_feather(datadir / "pid_control/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )= df_basin_wide["level"].plot()
+ ax "level [m]")
+ ax.set_ylabel(
+# Plot target level
+= model.pid_control.time.df.target.to_numpy()[:4]
+ level_demands = date2num(model.pid_control.time.df.time)[:4]
+ times ="k", ls=":", label="target level")
+ ax.plot(times, level_demands, colorpass
-
-5 Model with allocation (user demand)
-Setup the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: FlowBoundary
- (1.0, 0.0), # 2: Basin
- (1.0, 1.0), # 3: UserDemand
- (2.0, 0.0), # 4: LinearResistance
- (3.0, 0.0), # 5: Basin
- (3.0, 1.0), # 6: UserDemand
- (4.0, 0.0), # 7: TabulatedRatingCurve
- (4.5, 0.0), # 8: FractionalFlow
- (4.5, 0.5), # 9: FractionalFlow
- (5.0, 0.0), # 10: Terminal
- (4.5, 0.25), # 11: DiscreteControl
- (4.5, 1.0), # 12: Basin
- (5.0, 1.0), # 13: UserDemand
- (
- ]
- )= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= [
- node_type "FlowBoundary",
- "Basin",
- "UserDemand",
- "LinearResistance",
- "Basin",
- "UserDemand",
- "TabulatedRatingCurve",
- "FractionalFlow",
- "FractionalFlow",
- "Terminal",
- "DiscreteControl",
- "Basin",
- "UserDemand",
-
- ]
-# All nodes belong to allocation network id 1
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={"node_type": node_type, "subnetwork_id": 1},
- data=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array(
- from_id 1, 2, 2, 4, 5, 5, 7, 3, 6, 7, 8, 9, 12, 13, 11, 11],
- [=np.int64,
- dtype
- )= np.array(
- to_id 2, 3, 4, 5, 6, 7, 8, 2, 5, 9, 10, 12, 13, 10, 8, 9],
- [=np.int64,
- dtype
- )# Denote the first edge, 1 => 2, as a source edge for
-# allocation network 1
-= len(from_id) * [None]
- subnetwork_id 0] = 1
- subnetwork_id[= node.geometry_from_connectivity(from_id, to_id)
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": (len(from_id) - 2) * ["flow"] + 2 * ["control"],
- "subnetwork_id": subnetwork_id,
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
+
+4 Model with allocation (user demand)
+Setup a model:
+
+= Model(
+ model ="2020-01-01 00:00:00",
+ starttime="2020-01-20 00:00:00",
+ endtime )
Setup the basins:
-
-= pd.DataFrame(
- profile ={
- data"node_id": [2, 2, 5, 5, 12, 12],
- "area": 300_000.0,
- "level": 3 * [0.0, 1.0],
-
- }
- )
-= pd.DataFrame(data={"node_id": [2, 5, 12], "level": 1.0})
- state
-= ribasim.Basin(profile=profile, state=state) basin
+
+= [
+ basin_data =[300_000.0, 300_000.0], level=[0.0, 1.0]),
+ basin.Profile(area=[1.0]),
+ basin.State(level
+ ]
+
+ model.basin.add(2, Point(1.0, 0.0), subnetwork_id=1),
+ Node(
+ basin_data,
+ )
+ model.basin.add(5, Point(3.0, 0.0), subnetwork_id=1),
+ Node(
+ basin_data,
+ )
+ model.basin.add(12, Point(4.5, 1.0), subnetwork_id=1),
+ Node(
+ basin_data, )
Setup the flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(
- static={
- data"node_id": [1],
- "flow_rate": 2.0,
-
- }
- ) )
+
+
+ model.flow_boundary.add(1, Point(0.0, 0.0), subnetwork_id=1), [flow_boundary.Static(flow_rate=[2.0])]
+ Node( )
Setup the linear resistance:
-
-= ribasim.LinearResistance(
- linear_resistance =pd.DataFrame(
- static={
- data"node_id": [4],
- "resistance": 0.06,
-
- }
- ) )
+
+
+ model.linear_resistance.add(4, Point(2.0, 0.0), subnetwork_id=1),
+ Node(=[0.06])],
+ [linear_resistance.Static(resistance )
Setup the tabulated rating curve:
-
-= ribasim.TabulatedRatingCurve(
- tabulated_rating_curve =pd.DataFrame(
- static={
- data"node_id": 7,
- "level": [0.0, 0.5, 1.0],
- "flow_rate": [0.0, 0.0, 2.0],
-
- }
- ) )
+
+
+ model.tabulated_rating_curve.add(7, Point(4.0, 0.0), subnetwork_id=1),
+ Node(=[0.0, 0.5, 1.0], flow_rate=[0.0, 0.0, 2.0])],
+ [tabulated_rating_curve.Static(level )
Setup the fractional flow:
-
-= ribasim.FractionalFlow(
- fractional_flow =pd.DataFrame(
- static={
- data"node_id": [8, 8, 9, 9],
- "fraction": [0.6, 0.9, 0.4, 0.1],
- "control_state": ["divert", "close", "divert", "close"],
-
- }
- ) )
+
+
+ model.fractional_flow.add(8, Point(4.5, 0.0), subnetwork_id=1),
+ Node(=[0.6, 0.9], control_state=["divert", "close"])],
+ [fractional_flow.Static(fraction
+ )
+ model.fractional_flow.add(9, Point(4.5, 0.5), subnetwork_id=1),
+ Node(=[0.4, 0.1], control_state=["divert", "close"])],
+ [fractional_flow.Static(fraction )
Setup the terminal:
-
-= ribasim.Terminal(
- terminal =pd.DataFrame(
- static={
- data"node_id": [10],
-
- }
- ) )
+
+10, Point(5.0, 0.0), subnetwork_id=1)) model.terminal.add(Node(
Setup the discrete control:
-
-= pd.DataFrame(
- condition ={
- data"node_id": [11],
- "listen_feature_id": 5,
- "variable": "level",
- "greater_than": 0.52,
-
- }
- )
-= pd.DataFrame(
- logic ={
- data"node_id": 11,
- "truth_state": ["T", "F"],
- "control_state": ["divert", "close"],
-
- }
- )
-= ribasim.DiscreteControl(condition=condition, logic=logic) discrete_control
+
+
+ model.discrete_control.add(11, Point(4.5, 0.25), subnetwork_id=1),
+ Node(
+ [
+ discrete_control.Condition(=[5],
+ listen_node_id=["Basin"],
+ listen_node_type=["level"],
+ variable=[0.52],
+ greater_than
+ ),
+ discrete_control.Logic(=["T", "F"], control_state=["divert", "close"]
+ truth_state
+ ),
+ ], )
Setup the users:
-
-= ribasim.UserDemand(
- user_demand =pd.DataFrame(
- static={
- data"node_id": [6, 13],
- "demand": [1.5, 1.0],
- "return_factor": 0.0,
- "min_level": -1.0,
- "priority": [1, 3],
-
- }
- ),=pd.DataFrame(
- time={
- data"node_id": [3, 3, 3, 3],
- "demand": [0.0, 1.0, 1.2, 1.2],
- "priority": [1, 1, 2, 2],
- "return_factor": 0.0,
- "min_level": -1.0,
- "time": 2 * ["2020-01-01 00:00:00", "2020-01-20 00:00:00"],
-
- }
- ), )
+
+
+ model.user_demand.add(6, Point(3.0, 1.0), subnetwork_id=1),
+ Node(
+ [
+ user_demand.Static(=[1.5], return_factor=[0.0], min_level=[-1.0], priority=[1]
+ demand
+ )
+ ],
+ )
+ model.user_demand.add(13, Point(5.0, 1.0), subnetwork_id=1),
+ Node(
+ [
+ user_demand.Static(=[1.0], return_factor=[0.0], min_level=[-1.0], priority=[3]
+ demand
+ )
+ ],
+ )
+ model.user_demand.add(3, Point(1.0, 1.0), subnetwork_id=1),
+ Node(
+ [
+ user_demand.Time(=[0.0, 1.0, 1.2, 1.2],
+ demand=[0.0, 0.0, 0.0, 0.0],
+ return_factor=[-1.0, -1.0, -1.0, -1.0],
+ min_level=[1, 1, 2, 2],
+ priority=2 * ["2020-01-01 00:00:00", "2020-01-20 00:00:00"],
+ time
+ )
+ ], )
Setup the allocation:
-
-= ribasim.Allocation(use_allocation=True, timestep=86400) allocation
+
+= ribasim.Allocation(use_allocation=True, timestep=86400) model.allocation
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(
- network=node,
- node=edge,
- edge
- ),=basin,
- basin=flow_boundary,
- flow_boundary=linear_resistance,
- linear_resistance=tabulated_rating_curve,
- tabulated_rating_curve=terminal,
- terminal=user_demand,
- user_demand=discrete_control,
- discrete_control=fractional_flow,
- fractional_flow=allocation,
- allocation="2020-01-01 00:00:00",
- starttime="2020-01-20 00:00:00",
- endtime )
+Setup the edges:
+
+1], model.basin[2], "flow", subnetwork_id=1)
+ model.edge.add(model.flow_boundary[2], model.user_demand[3], "flow")
+ model.edge.add(model.basin[2], model.linear_resistance[4], "flow")
+ model.edge.add(model.basin[4], model.basin[5], "flow")
+ model.edge.add(model.linear_resistance[5], model.user_demand[6], "flow")
+ model.edge.add(model.basin[5], model.tabulated_rating_curve[7], "flow")
+ model.edge.add(model.basin[7], model.fractional_flow[8], "flow")
+ model.edge.add(model.tabulated_rating_curve[3], model.basin[2], "flow")
+ model.edge.add(model.user_demand[6], model.basin[5], "flow")
+ model.edge.add(model.user_demand[7], model.fractional_flow[9], "flow")
+ model.edge.add(model.tabulated_rating_curve[8], model.terminal[10], "flow")
+ model.edge.add(model.fractional_flow[9], model.basin[12], "flow")
+ model.edge.add(model.fractional_flow[12], model.user_demand[13], "flow")
+ model.edge.add(model.basin[13], model.terminal[10], "flow")
+ model.edge.add(model.user_demand[11], model.fractional_flow[8], "control")
+ model.edge.add(model.discrete_control[11], model.fractional_flow[9], "control") model.edge.add(model.discrete_control[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Write the model to a TOML and GeoPackage:
-
-= Path("data")
- datadir / "allocation_example/ribasim.toml") model.write(datadir
-
+
+= Path("data")
+ datadir / "allocation_example/ribasim.toml") model.write(datadir
+
PosixPath('data/allocation_example/ribasim.toml')
Now run the model with ribasim allocation_example/ribasim.toml
. After running the model, read back the results:
-
-import matplotlib.ticker as plticker
-
-= pd.read_feather(datadir / "allocation_example/results/allocation.arrow")
- df_allocation = df_allocation.pivot_table(
- df_allocation_wide ="time",
- index=["node_type", "node_id", "priority"],
- columns=["demand", "allocated", "realized"],
- values
- )= df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]
- df_allocation_wide
-= plt.subplots(1, 3, figsize=(8, 5))
- fig, axs
-"demand"].plot(ax=axs[0], ls=":")
- df_allocation_wide["allocated"].plot(ax=axs[1], ls="--")
- df_allocation_wide["realized"].plot(ax=axs[2])
- df_allocation_wide[
-
- fig.tight_layout()= plticker.MultipleLocator(2)
- loc
-0].set_ylabel("level [m]")
- axs[
-for ax, title in zip(axs, ["Demand", "Allocated", "Abstracted"]):
-
- ax.set_title(title)0.0, 1.6)
- ax.set_ylim( ax.xaxis.set_major_locator(loc)
+
+import matplotlib.ticker as plticker
+
+= pd.read_feather(datadir / "allocation_example/results/allocation.arrow")
+ df_allocation = df_allocation.pivot_table(
+ df_allocation_wide ="time",
+ index=["node_type", "node_id", "priority"],
+ columns=["demand", "allocated", "realized"],
+ values
+ )= df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]
+ df_allocation_wide
+= plt.subplots(1, 3, figsize=(8, 5))
+ fig, axs
+"demand"].plot(ax=axs[0], ls=":")
+ df_allocation_wide["allocated"].plot(ax=axs[1], ls="--")
+ df_allocation_wide["realized"].plot(ax=axs[2])
+ df_allocation_wide[
+
+ fig.tight_layout()= plticker.MultipleLocator(2)
+ loc
+0].set_ylabel("level [m]")
+ axs[
+for ax, title in zip(axs, ["Demand", "Allocated", "Abstracted"]):
+
+ ax.set_title(title)0.0, 1.6)
+ ax.set_ylim( ax.xaxis.set_major_locator(loc)
@@ -1360,202 +980,171 @@ 5 Model with allo
Abstraction behaves somewhat erratically at the start of the simulation. This is because allocation is based on flows computed in the physical layer, and at the start of the simulation these are not known yet.
Although there is a plotted line for abstraction per priority, abstraction is actually accumulated over all priorities per user.
-
-= pd.read_feather(datadir / "allocation_example/results/basin.arrow")
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )
-= df_basin_wide["level"].plot()
- ax "Basin levels")
- ax.set_title("level [m]") ax.set_ylabel(
-
+
+= pd.read_feather(datadir / "allocation_example/results/basin.arrow")
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )
+= df_basin_wide["level"].plot()
+ ax "Basin levels")
+ ax.set_title("level [m]") ax.set_ylabel(
+
Text(0, 0.5, 'level [m]')
-
-6 Model with allocation (basin supply/demand)
-Setup the nodes:
-
-= np.array(
- xy
- [0.0, 0.0), # 1: FlowBoundary
- (1.0, 0.0), # 2: Basin
- (2.0, 0.0), # 3: UserDemand
- (1.0, -1.0), # 4: LevelDemand
- (2.0, -1.0), # 5: Basin
- (
- ]
- )= gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])
- node_xy
-= ["FlowBoundary", "Basin", "UserDemand", "LevelDemand", "Basin"]
- node_type
-# Make sure the feature id starts at 1: explicitly give an index.
-= ribasim.Node(
- node =gpd.GeoDataFrame(
- df={
- data"node_type": node_type,
- "subnetwork_id": 5 * [2],
-
- },=pd.Index(np.arange(len(xy)) + 1, name="fid"),
- index=node_xy,
- geometry="EPSG:28992",
- crs
- ) )
-
-Setup the edges:
-
-= np.array([1, 2, 4, 3, 4])
- from_id = np.array([2, 3, 2, 5, 5])
- to_id = ["flow", "flow", "control", "flow", "control"]
- edge_type = [2, None, None, None, None]
- subnetwork_id
-= node.geometry_from_connectivity(from_id.tolist(), to_id.tolist())
- lines = ribasim.Edge(
- edge =gpd.GeoDataFrame(
- df={
- data"from_node_id": from_id,
- "to_node_id": to_id,
- "edge_type": edge_type,
- "subnetwork_id": subnetwork_id,
-
- },=lines,
- geometry="EPSG:28992",
- crs
- ) )
+
+5 Model with allocation (basin supply/demand)
+Setup a model:
+
+= ribasim.Model(
+ model ="2020-01-01 00:00:00",
+ starttime="2020-02-01 00:00:00",
+ endtime )
Setup the basins:
-
-= pd.DataFrame(
- profile ={"node_id": [2, 2, 5, 5], "area": 1e3, "level": [0.0, 1.0, 0.0, 1.0]}
- data
- )= pd.DataFrame(
- static ={
- data"node_id": [5],
- "drainage": 0.0,
- "potential_evaporation": 0.0,
- "infiltration": 0.0,
- "precipitation": 0.0,
- "urban_runoff": 0.0,
-
- }
- )= pd.DataFrame(
- time ={
- data"node_id": 2,
- "time": ["2020-01-01 00:00:00", "2020-01-16 00:00:00"],
- "drainage": 0.0,
- "potential_evaporation": 0.0,
- "infiltration": 0.0,
- "precipitation": [1e-6, 0.0],
- "urban_runoff": 0.0,
-
- },
- )
-= pd.DataFrame(data={"node_id": [2, 5], "level": 0.5})
- state = ribasim.Basin(profile=profile, static=static, time=time, state=state) basin
+
+= [
+ basin_data =[1e3, 1e3], level=[0.0, 1.0]),
+ basin.Profile(area=[0.5]),
+ basin.State(level
+ ]
+ model.basin.add(2, Point(1.0, 0.0)),
+ Node(
+ [*basin_data,
+
+ basin.Time(=["2020-01-01 00:00:00", "2020-01-16 00:00:00"],
+ time=[0.0, 0.0],
+ drainage=[0.0, 0.0],
+ potential_evaporation=[0.0, 0.0],
+ infiltration=[1e-6, 0.0],
+ precipitation=[0.0, 0.0],
+ urban_runoff
+ ),
+ ],
+ )
+ model.basin.add(5, Point(2.0, -1.0)),
+ Node(
+ [*basin_data,
+
+ basin.Static(=[0.0],
+ drainage=[0.0],
+ potential_evaporation=[0.0],
+ infiltration=[0.0],
+ precipitation=[0.0],
+ urban_runoff
+ ),
+ ],
+ )= pd.DataFrame(
+ profile ={"node_id": [2, 2, 5, 5], "area": 1e3, "level": [0.0, 1.0, 0.0, 1.0]}
+ data )
Setup the flow boundary:
-
-= ribasim.FlowBoundary(
- flow_boundary =pd.DataFrame(data={"node_id": [1], "flow_rate": 1e-3})
- static )
+
+
+ model.flow_boundary.add(1, Point(0.0, 0.0)), [flow_boundary.Static(flow_rate=[1e-3])]
+ Node( )
Setup allocation level control:
-
-= ribasim.LevelDemand(
- level_demand =pd.DataFrame(
- static={"node_id": [4], "priority": 1, "min_level": 1.0, "max_level": 1.5}
- data
- ) )
+
+
+ model.level_demand.add(4, Point(1.0, -1.0)),
+ Node(=[1], min_level=[1.0], max_level=[1.5])],
+ [level_demand.Static(priority )
Setup the users:
-
-= ribasim.UserDemand(
- user_demand =pd.DataFrame(
- static={
- data"node_id": [3],
- "priority": [2],
- "demand": [1.5e-3],
- "return_factor": [0.2],
- "min_level": [0.2],
-
- }
- ) )
+
+
+ model.user_demand.add(3, Point(2.0, 0.0)),
+ Node(
+ [
+ user_demand.Static(=[2], demand=[1.5e-3], return_factor=[0.2], min_level=[0.2]
+ priority
+ )
+ ], )
Setup the allocation:
-
-= ribasim.Allocation(use_allocation=True, timestep=1e5) allocation
+
+= ribasim.Allocation(use_allocation=True, timestep=1e5) model.allocation
-Setup a model:
-
-= ribasim.Model(
- model =ribasim.Network(node=node, edge=edge),
- network=basin,
- basin=flow_boundary,
- flow_boundary=level_demand,
- level_demand=user_demand,
- user_demand=allocation,
- allocation="2020-01-01 00:00:00",
- starttime="2020-02-01 00:00:00",
- endtime )
+Setup the edges:
+
+1], model.basin[2], "flow", subnetwork_id=2)
+ model.edge.add(model.flow_boundary[2], model.user_demand[3], "flow")
+ model.edge.add(model.basin[4], model.basin[2], "control")
+ model.edge.add(model.level_demand[3], model.basin[5], "flow")
+ model.edge.add(model.user_demand[4], model.basin[5], "control") model.edge.add(model.level_demand[
Let’s take a look at the model:
-
- model.plot()
+
+ model.plot()
Write the model to a TOML and GeoPackage:
-
-/ "level_demand/ribasim.toml") model.write(datadir
-
+
+/ "level_demand/ribasim.toml") model.write(datadir
+
PosixPath('data/level_demand/ribasim.toml')
Now run the model with ribasim level_demand/ribasim.toml
. After running the model, read back the results:
-
-= pd.read_feather(datadir / "level_demand/results/basin.arrow")
- df_basin = df_basin[df_basin.node_id == 2]
- df_basin = df_basin.pivot_table(
- df_basin_wide ="time", columns="node_id", values=["storage", "level"]
- index
- )= df_basin_wide["level"].plot()
- ax = (
- where_allocation - df_basin_wide.index[0]
- df_basin_wide.index % model.allocation.timestep == 0
- ).total_seconds() 0] = False
- where_allocation["level"].plot(
- df_basin_wide[where_allocation][="o",
- style=ax,
- ax
- )"level [m]") ax.set_ylabel(
-
+
+= pd.read_feather(datadir / "level_demand/results/basin.arrow")
+ df_basin = df_basin[df_basin.node_id == 2]
+ df_basin = df_basin.pivot_table(
+ df_basin_wide ="time", columns="node_id", values=["storage", "level"]
+ index
+ )= df_basin_wide["level"].plot()
+ ax = (
+ where_allocation - df_basin_wide.index[0]
+ df_basin_wide.index % model.allocation.timestep == 0
+ ).total_seconds() 0] = False
+ where_allocation["level"].plot(
+ df_basin_wide[where_allocation][="o",
+ style=ax,
+ ax
+ )"level [m]") ax.set_ylabel(
+
Text(0, 0.5, 'level [m]')
In the plot above, the line denotes the level of Basin #2 over time and the dots denote the times at which allocation optimization was run, with intervals of \(\Delta t_{\text{alloc}}\). The Basin level is a piecewise linear function of time, with several stages explained below.
-Constants: - \(d\): UserDemand #3 demand, - \(\phi\): Basin #2 precipitation rate, - \(q\): LevelBoundary flow.
-Stages: - In the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \(q + \phi - d\); - In the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \(q + \phi\); - In the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \(\phi\); - In the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \(q + \phi - d\); - At the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \(q - d\). - In the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.
+Constants:
+
+- \(d\): UserDemand #3 demand,
+- \(\phi\): Basin #2 precipitation rate,
+- \(q\): LevelBoundary flow.
+
+Stages:
+
+- In the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \(q + \phi - d\);
+- In the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \(q + \phi\);
+- In the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \(\phi\);
+- In the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \(q + \phi - d\);
+- At the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \(q - d\).
+- In the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.
+
diff --git a/python/examples_files/figure-html/cell-16-output-1.png b/python/examples_files/figure-html/cell-16-output-1.png
index 5494dfd59..c10d22b42 100644
Binary files a/python/examples_files/figure-html/cell-16-output-1.png and b/python/examples_files/figure-html/cell-16-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-26-output-1.png b/python/examples_files/figure-html/cell-19-output-1.png
similarity index 100%
rename from python/examples_files/figure-html/cell-26-output-1.png
rename to python/examples_files/figure-html/cell-19-output-1.png
diff --git a/python/examples_files/figure-html/cell-20-output-1.png b/python/examples_files/figure-html/cell-20-output-1.png
new file mode 100644
index 000000000..0c0a411f7
Binary files /dev/null and b/python/examples_files/figure-html/cell-20-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-27-output-1.png b/python/examples_files/figure-html/cell-27-output-1.png
deleted file mode 100644
index 4a7e40f6c..000000000
Binary files a/python/examples_files/figure-html/cell-27-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-29-output-1.png b/python/examples_files/figure-html/cell-29-output-1.png
new file mode 100644
index 000000000..72ad8364f
Binary files /dev/null and b/python/examples_files/figure-html/cell-29-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-32-output-1.png b/python/examples_files/figure-html/cell-32-output-1.png
new file mode 100644
index 000000000..8e5ebf24c
Binary files /dev/null and b/python/examples_files/figure-html/cell-32-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-38-output-1.png b/python/examples_files/figure-html/cell-38-output-1.png
deleted file mode 100644
index fae390f9d..000000000
Binary files a/python/examples_files/figure-html/cell-38-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-41-output-1.png b/python/examples_files/figure-html/cell-41-output-1.png
index 8e5ebf24c..bf3b22cb6 100644
Binary files a/python/examples_files/figure-html/cell-41-output-1.png and b/python/examples_files/figure-html/cell-41-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-44-output-1.png b/python/examples_files/figure-html/cell-44-output-1.png
new file mode 100644
index 000000000..0da7ebb3e
Binary files /dev/null and b/python/examples_files/figure-html/cell-44-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-52-output-1.png b/python/examples_files/figure-html/cell-52-output-1.png
deleted file mode 100644
index 9a713e59a..000000000
Binary files a/python/examples_files/figure-html/cell-52-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-55-output-1.png b/python/examples_files/figure-html/cell-55-output-1.png
deleted file mode 100644
index 220cc31bf..000000000
Binary files a/python/examples_files/figure-html/cell-55-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-56-output-1.png b/python/examples_files/figure-html/cell-56-output-1.png
new file mode 100644
index 000000000..284be855d
Binary files /dev/null and b/python/examples_files/figure-html/cell-56-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-59-output-1.png b/python/examples_files/figure-html/cell-59-output-1.png
new file mode 100644
index 000000000..efcc16cad
Binary files /dev/null and b/python/examples_files/figure-html/cell-59-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-72-output-2.png b/python/examples_files/figure-html/cell-60-output-2.png
similarity index 100%
rename from python/examples_files/figure-html/cell-72-output-2.png
rename to python/examples_files/figure-html/cell-60-output-2.png
diff --git a/python/examples_files/figure-html/cell-68-output-1.png b/python/examples_files/figure-html/cell-68-output-1.png
index 5d1d3c453..2226321f0 100644
Binary files a/python/examples_files/figure-html/cell-68-output-1.png and b/python/examples_files/figure-html/cell-68-output-1.png differ
diff --git a/python/examples_files/figure-html/cell-71-output-1.png b/python/examples_files/figure-html/cell-71-output-1.png
deleted file mode 100644
index 54374cc99..000000000
Binary files a/python/examples_files/figure-html/cell-71-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-71-output-2.png b/python/examples_files/figure-html/cell-71-output-2.png
new file mode 100644
index 000000000..7bd0a8a26
Binary files /dev/null and b/python/examples_files/figure-html/cell-71-output-2.png differ
diff --git a/python/examples_files/figure-html/cell-81-output-1.png b/python/examples_files/figure-html/cell-81-output-1.png
deleted file mode 100644
index 7f7bd21fb..000000000
Binary files a/python/examples_files/figure-html/cell-81-output-1.png and /dev/null differ
diff --git a/python/examples_files/figure-html/cell-84-output-2.png b/python/examples_files/figure-html/cell-84-output-2.png
deleted file mode 100644
index 015320f77..000000000
Binary files a/python/examples_files/figure-html/cell-84-output-2.png and /dev/null differ
diff --git a/python/reference/Edge.html b/python/reference/Edge.html
deleted file mode 100644
index b3819ec07..000000000
--- a/python/reference/Edge.html
+++ /dev/null
@@ -1,574 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/DiscreteControl.html b/python/reference/EdgeTable.html
similarity index 98%
rename from python/reference/DiscreteControl.html
rename to python/reference/EdgeTable.html
index 3b060a1f9..3bcb476a3 100644
--- a/python/reference/DiscreteControl.html
+++ b/python/reference/EdgeTable.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,10 @@ On this page
-
-1 DiscreteControl
-DiscreteControl()
+
+1 EdgeTable
+EdgeTable(self, **kwargs)
+Defines the connections between nodes.
diff --git a/python/reference/LevelBoundary.html b/python/reference/LevelBoundary.html
deleted file mode 100644
index f1da07f3a..000000000
--- a/python/reference/LevelBoundary.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/LinearResistance.html b/python/reference/LinearResistance.html
deleted file mode 100644
index cb825ff89..000000000
--- a/python/reference/LinearResistance.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Model.html b/python/reference/Model.html
index 2e7f99c73..4c87bfaef 100644
--- a/python/reference/Model.html
+++ b/python/reference/Model.html
@@ -130,10 +130,7 @@
On this page
- - 1 Model
-
- - 1.1 Parameters
-
+ - 1 Model
@@ -146,163 +143,8 @@ On this page
1 Model
Model()
-A full Ribasim model schematisation with all input.
-Ribasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.
-
-1.1 Parameters
-
-
-
-
-
-
-
-
-
-Name
-Type
-Description
-Default
-
-
-
-
-starttime
-datetime.datetime.datetime
-Starting time of the simulation.
-required
-
-
-endtime
-datetime.datetime.datetime
-End time of the simulation.
-required
-
-
-input_dir
-
-The directory of the input files.
-required
-
-
-results_dir
-
-The directory of the results files.
-required
-
-
-network
-
-Class containing the topology (nodes and edges) of the model.
-required
-
-
-results
-
-Results configuration options.
-required
-
-
-solver
-
-Solver configuration options.
-required
-
-
-logging
-
-Logging configuration options.
-required
-
-
-allocation
-
-The allocation configuration.
-required
-
-
-basin
-ribasim.config.Basin
-The waterbodies.
-required
-
-
-fractional_flow
-ribasim.config.FractionalFlow
-Split flows into fractions.
-required
-
-
-level_boundary
-ribasim.config.LevelBoundary
-Boundary condition specifying the water level.
-required
-
-
-flow_boundary
-ribasim.config.FlowBoundary
-Boundary conditions specifying the flow.
-required
-
-
-linear_resistance
-
-Linear flow resistance.
-required
-
-
-manning_resistance
-ribasim.config.ManningResistance
-Flow resistance based on the Manning formula.
-required
-
-
-tabulated_rating_curve
-ribasim.config.TabulatedRatingCurve
-Tabulated rating curve describing flow based on the upstream water level.
-required
-
-
-pump
-ribasim.config.Pump
-Prescribed flow rate from one basin to the other.
-required
-
-
-outlet
-ribasim.config.Outlet
-Prescribed flow rate from one basin to the other.
-required
-
-
-terminal
-ribasim.config.Terminal
-Water sink without state or properties.
-required
-
-
-discrete_control
-ribasim.config.DiscreteControl
-Discrete control logic.
-required
-
-
-pid_control
-ribasim.config.PidControl
-PID controller attempting to set the level of a basin to a desired value using a pump/outlet.
-required
-
-
-user_demand
-ribasim.config.UserDemand
-UserDemand node type with demand and priority.
-required
-
-
-
-
diff --git a/python/reference/Node.html b/python/reference/Node.html
deleted file mode 100644
index dfd46163a..000000000
--- a/python/reference/Node.html
+++ /dev/null
@@ -1,544 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Outlet.html b/python/reference/Outlet.html
deleted file mode 100644
index 9a9631598..000000000
--- a/python/reference/Outlet.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Pump.html b/python/reference/Pump.html
deleted file mode 100644
index b37aaf175..000000000
--- a/python/reference/Pump.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/Terminal.html b/python/reference/Terminal.html
deleted file mode 100644
index 7245d0817..000000000
--- a/python/reference/Terminal.html
+++ /dev/null
@@ -1,543 +0,0 @@
-
-
-
-
-
-
-
-
-
-Ribasim
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/python/reference/index.html b/python/reference/index.html
index d2b6b558c..27851c9c8 100644
--- a/python/reference/index.html
+++ b/python/reference/index.html
@@ -176,7 +176,7 @@ On this page
- 1 API Reference
@@ -197,22 +197,18 @@
Model
-A full Ribasim model schematisation with all input.
+
-
-1.2 Network
-The Node and Edge database layers define the network layout.
+
+1.2 Edge
+The Edge database layer.
-Node
-The Ribasim nodes as Point geometries.
-
-
-Edge
+EdgeTable
Defines the connections between nodes.
@@ -224,55 +220,55 @@
-Basin
+nodes.basin
-FractionalFlow
+nodes.fractional_flow
-TabulatedRatingCurve
+nodes.tabulated_rating_curve
-Pump
+nodes.pump
-Outlet
+nodes.outlet
-UserDemand
+nodes.user_demand
-LevelBoundary
+nodes.level_boundary
-FlowBoundary
+nodes.flow_boundary
-LinearResistance
+nodes.linear_resistance
-ManningResistance
+nodes.manning_resistance
-Terminal
+nodes.terminal
-DiscreteControl
+nodes.discrete_control
-PidControl
+nodes.pid_control
diff --git a/python/reference/UserDemand.html b/python/reference/nodes.basin.html
similarity index 98%
rename from python/reference/UserDemand.html
rename to python/reference/nodes.basin.html
index db254cf8b..fcd468753 100644
--- a/python/reference/UserDemand.html
+++ b/python/reference/nodes.basin.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 UserDemand
-UserDemand()
+
+1 nodes.basin
+nodes.basin
diff --git a/python/reference/nodes.discrete_control.html b/python/reference/nodes.discrete_control.html
new file mode 100644
index 000000000..42dec9b66
--- /dev/null
+++ b/python/reference/nodes.discrete_control.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/nodes.flow_boundary.html b/python/reference/nodes.flow_boundary.html
new file mode 100644
index 000000000..43aa532c2
--- /dev/null
+++ b/python/reference/nodes.flow_boundary.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/nodes.fractional_flow.html b/python/reference/nodes.fractional_flow.html
new file mode 100644
index 000000000..d6b21e860
--- /dev/null
+++ b/python/reference/nodes.fractional_flow.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/TabulatedRatingCurve.html b/python/reference/nodes.level_boundary.html
similarity index 98%
rename from python/reference/TabulatedRatingCurve.html
rename to python/reference/nodes.level_boundary.html
index 93f7399c8..e8f621f64 100644
--- a/python/reference/TabulatedRatingCurve.html
+++ b/python/reference/nodes.level_boundary.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 TabulatedRatingCurve
-TabulatedRatingCurve()
+
+1 nodes.level_boundary
+nodes.level_boundary
diff --git a/python/reference/nodes.linear_resistance.html b/python/reference/nodes.linear_resistance.html
new file mode 100644
index 000000000..85be76230
--- /dev/null
+++ b/python/reference/nodes.linear_resistance.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/nodes.manning_resistance.html b/python/reference/nodes.manning_resistance.html
new file mode 100644
index 000000000..e15e3f439
--- /dev/null
+++ b/python/reference/nodes.manning_resistance.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/FlowBoundary.html b/python/reference/nodes.outlet.html
similarity index 98%
rename from python/reference/FlowBoundary.html
rename to python/reference/nodes.outlet.html
index 777ee1d6c..52b971920 100644
--- a/python/reference/FlowBoundary.html
+++ b/python/reference/nodes.outlet.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 FlowBoundary
-FlowBoundary()
+
+1 nodes.outlet
+nodes.outlet
diff --git a/python/reference/ManningResistance.html b/python/reference/nodes.pid_control.html
similarity index 98%
rename from python/reference/ManningResistance.html
rename to python/reference/nodes.pid_control.html
index 16f837416..5d89240ea 100644
--- a/python/reference/ManningResistance.html
+++ b/python/reference/nodes.pid_control.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 ManningResistance
-ManningResistance()
+
+1 nodes.pid_control
+nodes.pid_control
diff --git a/python/reference/PidControl.html b/python/reference/nodes.pump.html
similarity index 98%
rename from python/reference/PidControl.html
rename to python/reference/nodes.pump.html
index afc4f2964..7edf07b93 100644
--- a/python/reference/PidControl.html
+++ b/python/reference/nodes.pump.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 PidControl
-PidControl()
+
+1 nodes.pump
+nodes.pump
diff --git a/python/reference/nodes.tabulated_rating_curve.html b/python/reference/nodes.tabulated_rating_curve.html
new file mode 100644
index 000000000..095cdad9b
--- /dev/null
+++ b/python/reference/nodes.tabulated_rating_curve.html
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+
+
+Ribasim
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/reference/FractionalFlow.html b/python/reference/nodes.terminal.html
similarity index 98%
rename from python/reference/FractionalFlow.html
rename to python/reference/nodes.terminal.html
index 3e0a24fea..37d83a560 100644
--- a/python/reference/FractionalFlow.html
+++ b/python/reference/nodes.terminal.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 FractionalFlow
-FractionalFlow()
+
+1 nodes.terminal
+nodes.terminal
diff --git a/python/reference/Basin.html b/python/reference/nodes.user_demand.html
similarity index 98%
rename from python/reference/Basin.html
rename to python/reference/nodes.user_demand.html
index 9e5937bdd..e91608107 100644
--- a/python/reference/Basin.html
+++ b/python/reference/nodes.user_demand.html
@@ -130,7 +130,7 @@
On this page
@@ -140,9 +140,9 @@ On this page
-
-1 Basin
-Basin()
+
+1 nodes.user_demand
+nodes.user_demand
diff --git a/python/test-models.html b/python/test-models.html
index ba75a1d23..f1dcc0089 100644
--- a/python/test-models.html
+++ b/python/test-models.html
@@ -227,7 +227,7 @@ Test models
Ribasim developers use the following models in their testbench and in order to test new features.
-
+
Code
import ribasim_testmodels
@@ -249,35 +249,35 @@ Test models
@@ -291,35 +291,35 @@ Test models
@@ -333,140 +333,140 @@ Test models
diff --git a/python/test-models_files/figure-html/cell-2-output-1.png b/python/test-models_files/figure-html/cell-2-output-1.png
index 92d2d521d..2f2c20593 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-1.png and b/python/test-models_files/figure-html/cell-2-output-1.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-10.png b/python/test-models_files/figure-html/cell-2-output-10.png
index 61c772eac..da65e3dd1 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-10.png and b/python/test-models_files/figure-html/cell-2-output-10.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-11.png b/python/test-models_files/figure-html/cell-2-output-11.png
index 7acf51ba9..735392009 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-11.png and b/python/test-models_files/figure-html/cell-2-output-11.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-13.png b/python/test-models_files/figure-html/cell-2-output-13.png
index a14b6dfe3..c9ec49b37 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-13.png and b/python/test-models_files/figure-html/cell-2-output-13.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-14.png b/python/test-models_files/figure-html/cell-2-output-14.png
index 3700441cd..a3793e5e8 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-14.png and b/python/test-models_files/figure-html/cell-2-output-14.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-15.png b/python/test-models_files/figure-html/cell-2-output-15.png
index e5b45280f..4d1c93fd5 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-15.png and b/python/test-models_files/figure-html/cell-2-output-15.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-16.png b/python/test-models_files/figure-html/cell-2-output-16.png
index 51472f2e6..266b60de0 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-16.png and b/python/test-models_files/figure-html/cell-2-output-16.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-17.png b/python/test-models_files/figure-html/cell-2-output-17.png
index 28f13a6b3..a19439a4d 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-17.png and b/python/test-models_files/figure-html/cell-2-output-17.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-18.png b/python/test-models_files/figure-html/cell-2-output-18.png
index 98bba3b7d..c9429ef9d 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-18.png and b/python/test-models_files/figure-html/cell-2-output-18.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-19.png b/python/test-models_files/figure-html/cell-2-output-19.png
index 6feed0dcf..9484a0b7b 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-19.png and b/python/test-models_files/figure-html/cell-2-output-19.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-2.png b/python/test-models_files/figure-html/cell-2-output-2.png
index abbbbe1b4..78ac80a73 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-2.png and b/python/test-models_files/figure-html/cell-2-output-2.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-20.png b/python/test-models_files/figure-html/cell-2-output-20.png
index 48143149e..c5638eb99 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-20.png and b/python/test-models_files/figure-html/cell-2-output-20.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-21.png b/python/test-models_files/figure-html/cell-2-output-21.png
index e23644dbf..d16b5a91e 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-21.png and b/python/test-models_files/figure-html/cell-2-output-21.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-22.png b/python/test-models_files/figure-html/cell-2-output-22.png
index 385f70f3c..34126b746 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-22.png and b/python/test-models_files/figure-html/cell-2-output-22.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-23.png b/python/test-models_files/figure-html/cell-2-output-23.png
index 30bfdf250..2d39d123b 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-23.png and b/python/test-models_files/figure-html/cell-2-output-23.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-24.png b/python/test-models_files/figure-html/cell-2-output-24.png
index c6cd3c340..432e4c960 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-24.png and b/python/test-models_files/figure-html/cell-2-output-24.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-25.png b/python/test-models_files/figure-html/cell-2-output-25.png
index f489ccb5d..fe981e085 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-25.png and b/python/test-models_files/figure-html/cell-2-output-25.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-26.png b/python/test-models_files/figure-html/cell-2-output-26.png
index 59848dc9c..400e8079d 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-26.png and b/python/test-models_files/figure-html/cell-2-output-26.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-27.png b/python/test-models_files/figure-html/cell-2-output-27.png
index ca63191bd..6d22ccd41 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-27.png and b/python/test-models_files/figure-html/cell-2-output-27.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-28.png b/python/test-models_files/figure-html/cell-2-output-28.png
index fba859b58..45ce43b7c 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-28.png and b/python/test-models_files/figure-html/cell-2-output-28.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-29.png b/python/test-models_files/figure-html/cell-2-output-29.png
index 6cd33e1c7..a6b194c71 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-29.png and b/python/test-models_files/figure-html/cell-2-output-29.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-3.png b/python/test-models_files/figure-html/cell-2-output-3.png
index 3164625c9..78dbb8a4e 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-3.png and b/python/test-models_files/figure-html/cell-2-output-3.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-30.png b/python/test-models_files/figure-html/cell-2-output-30.png
index a7ac0d0fa..0216a688a 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-30.png and b/python/test-models_files/figure-html/cell-2-output-30.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-31.png b/python/test-models_files/figure-html/cell-2-output-31.png
index 59aa41129..044dd3909 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-31.png and b/python/test-models_files/figure-html/cell-2-output-31.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-32.png b/python/test-models_files/figure-html/cell-2-output-32.png
index bd21c153c..20e39a650 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-32.png and b/python/test-models_files/figure-html/cell-2-output-32.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-4.png b/python/test-models_files/figure-html/cell-2-output-4.png
index 7f6c4c0cf..d9c7d4249 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-4.png and b/python/test-models_files/figure-html/cell-2-output-4.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-5.png b/python/test-models_files/figure-html/cell-2-output-5.png
index d12bac473..beb111767 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-5.png and b/python/test-models_files/figure-html/cell-2-output-5.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-7.png b/python/test-models_files/figure-html/cell-2-output-7.png
index 24b41b39a..3ca104cf4 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-7.png and b/python/test-models_files/figure-html/cell-2-output-7.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-8.png b/python/test-models_files/figure-html/cell-2-output-8.png
index 9d5652095..d1485e332 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-8.png and b/python/test-models_files/figure-html/cell-2-output-8.png differ
diff --git a/python/test-models_files/figure-html/cell-2-output-9.png b/python/test-models_files/figure-html/cell-2-output-9.png
index a2637d6fc..6d8b03039 100644
Binary files a/python/test-models_files/figure-html/cell-2-output-9.png and b/python/test-models_files/figure-html/cell-2-output-9.png differ
diff --git a/search.json b/search.json
index 830341918..3795f71f3 100644
--- a/search.json
+++ b/search.json
@@ -472,7 +472,7 @@
"href": "core/usage.html#discretecontrol-condition",
"title": "Usage",
"section": "17.1 DiscreteControl / condition",
- "text": "17.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_feature_id\nInt\n-\nsorted per node_id\n\n\nlisten_feature_type\nString\n-\nknown node type\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”, sorted per listen_feature_id\n\n\ngreater_than\nFloat64\nvarious\nsorted per variable\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)",
+ "text": "17.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_node_type\nString\n-\nknown node type\n\n\nlisten_node_id\nInt\n-\nsorted per node_id\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”, sorted per listen_node_id\n\n\ngreater_than\nFloat64\nvarious\nsorted per variable\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)",
"crumbs": [
"Julia core",
"Usage"
@@ -494,7 +494,7 @@
"href": "core/usage.html#pidcontrol-time",
"title": "Usage",
"section": "18.1 PidControl / time",
- "text": "18.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntime\nDateTime\n-\nsorted per node_id\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-",
+ "text": "18.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntime\nDateTime\n-\nsorted per node_id\n\n\nlisten_node_type\nInt\n-\nknown node type\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-",
"crumbs": [
"Julia core",
"Usage"
@@ -655,118 +655,113 @@
]
},
{
- "objectID": "python/reference/Outlet.html",
- "href": "python/reference/Outlet.html",
- "title": "1 Outlet",
+ "objectID": "python/reference/Model.html",
+ "href": "python/reference/Model.html",
+ "title": "1 Model",
"section": "",
- "text": "1 Outlet\nOutlet()"
+ "text": "1 Model\nModel()"
},
{
- "objectID": "python/reference/Basin.html",
- "href": "python/reference/Basin.html",
- "title": "1 Basin",
+ "objectID": "python/reference/nodes.pump.html",
+ "href": "python/reference/nodes.pump.html",
+ "title": "1 nodes.pump",
"section": "",
- "text": "1 Basin\nBasin()"
+ "text": "1 nodes.pump\nnodes.pump"
},
{
- "objectID": "python/reference/index.html",
- "href": "python/reference/index.html",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.user_demand.html",
+ "href": "python/reference/nodes.user_demand.html",
+ "title": "1 nodes.user_demand",
"section": "",
- "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.\n\n\n\n\n\n\nThe Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUserDemand\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.user_demand\nnodes.user_demand"
},
{
- "objectID": "python/reference/index.html#model",
- "href": "python/reference/index.html#model",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.fractional_flow.html",
+ "href": "python/reference/nodes.fractional_flow.html",
+ "title": "1 nodes.fractional_flow",
"section": "",
- "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.fractional_flow\nnodes.fractional_flow"
},
{
- "objectID": "python/reference/index.html#network",
- "href": "python/reference/index.html#network",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.terminal.html",
+ "href": "python/reference/nodes.terminal.html",
+ "title": "1 nodes.terminal",
"section": "",
- "text": "The Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.terminal\nnodes.terminal"
},
{
- "objectID": "python/reference/index.html#node-types",
- "href": "python/reference/index.html#node-types",
- "title": "1 API Reference",
+ "objectID": "python/reference/nodes.level_boundary.html",
+ "href": "python/reference/nodes.level_boundary.html",
+ "title": "1 nodes.level_boundary",
"section": "",
- "text": "Available node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUserDemand\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl",
- "crumbs": [
- "Python tooling",
- "API Reference"
- ]
+ "text": "1 nodes.level_boundary\nnodes.level_boundary"
+ },
+ {
+ "objectID": "python/reference/nodes.pid_control.html",
+ "href": "python/reference/nodes.pid_control.html",
+ "title": "1 nodes.pid_control",
+ "section": "",
+ "text": "1 nodes.pid_control\nnodes.pid_control"
},
{
- "objectID": "python/reference/PidControl.html",
- "href": "python/reference/PidControl.html",
- "title": "1 PidControl",
+ "objectID": "python/reference/nodes.linear_resistance.html",
+ "href": "python/reference/nodes.linear_resistance.html",
+ "title": "1 nodes.linear_resistance",
"section": "",
- "text": "1 PidControl\nPidControl()"
+ "text": "1 nodes.linear_resistance\nnodes.linear_resistance"
+ },
+ {
+ "objectID": "python/test-models.html",
+ "href": "python/test-models.html",
+ "title": "Test models",
+ "section": "",
+ "text": "Ribasim developers use the following models in their testbench and in order to test new features.\n\n\nCode\nimport ribasim_testmodels\nimport matplotlib.pyplot as plt\n\nfor model_name, model_constructor in ribasim_testmodels.constructors.items():\n if model_name.startswith(\"invalid\"):\n continue\n\n model = model_constructor()\n fig, ax = plt.subplots()\n model.plot(ax)\n ax.set_title(label=model_name, loc=\"left\")\n fig.text(0, 1, model_constructor.__doc__)\n fig.tight_layout()\n plt.show()\n plt.close(fig)",
+ "crumbs": [
+ "Python tooling",
+ "Test models"
+ ]
},
{
- "objectID": "python/reference/ManningResistance.html",
- "href": "python/reference/ManningResistance.html",
- "title": "1 ManningResistance",
+ "objectID": "src/index.html",
+ "href": "src/index.html",
+ "title": "1 API Reference",
"section": "",
- "text": "1 ManningResistance\nManningResistance()"
+ "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
},
{
- "objectID": "python/reference/LevelBoundary.html",
- "href": "python/reference/LevelBoundary.html",
- "title": "1 LevelBoundary",
+ "objectID": "src/index.html#modules",
+ "href": "src/index.html#modules",
+ "title": "1 API Reference",
"section": "",
- "text": "1 LevelBoundary\nLevelBoundary()"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]"
},
{
- "objectID": "python/reference/Node.html",
- "href": "python/reference/Node.html",
- "title": "1 Node",
+ "objectID": "src/index.html#types",
+ "href": "src/index.html#types",
+ "title": "1 API Reference",
"section": "",
- "text": "1 Node\nNode()\nThe Ribasim nodes as Point geometries."
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]"
},
{
- "objectID": "python/reference/Pump.html",
- "href": "python/reference/Pump.html",
- "title": "1 Pump",
+ "objectID": "src/index.html#functions",
+ "href": "src/index.html#functions",
+ "title": "1 API Reference",
"section": "",
- "text": "1 Pump\nPump()"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]"
},
{
- "objectID": "python/index.html",
- "href": "python/index.html",
- "title": "Python tooling",
+ "objectID": "src/index.html#constants",
+ "href": "src/index.html#constants",
+ "title": "1 API Reference",
"section": "",
- "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip:\npip install ribasim\nFor wheel (.whl) downloads, including nightly builds, see the download section. After downloading wheels can be installed by referring to the correct path:\npip install path/to/ribasim-*.whl\nFor documentation please see the examples and API reference.",
- "crumbs": [
- "Python tooling"
- ]
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]"
},
{
- "objectID": "python/examples.html",
- "href": "python/examples.html",
- "title": "Examples",
+ "objectID": "src/index.html#macros",
+ "href": "src/index.html#macros",
+ "title": "1 API Reference",
"section": "",
- "text": "1 Basic model with static forcing\n\nfrom pathlib import Path\n\nimport geopandas as gpd\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport ribasim\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1, 3, 3, 6, 6, 9, 9],\n \"area\": [0.01, 1000.0] * 4,\n \"level\": [0.0, 1.0] * 4,\n }\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\nseconds_in_day = 24 * 3600\nprecipitation = 0.002 / seconds_in_day\nevaporation = 0.001 / seconds_in_day\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [0],\n \"potential_evaporation\": [evaporation],\n \"precipitation\": [precipitation],\n }\n)\nstatic = static.iloc[[0, 0, 0, 0]]\nstatic[\"node_id\"] = [1, 3, 6, 9]\n\nbasin = ribasim.Basin(profile=profile, static=static)\n\nSetup linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\"node_id\": [10, 12], \"resistance\": [5e3, (3600.0 * 24) / 100.0]}\n )\n)\n\nSetup Manning resistance:\n\nmanning_resistance = ribasim.ManningResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [2],\n \"length\": [900.0],\n \"manning_n\": [0.04],\n \"profile_width\": [6.0],\n \"profile_slope\": [3.0],\n }\n )\n)\n\nSet up a rating curve node:\n\n# Discharge: lose 1% of storage volume per day at storage = 1000.0.\nq1000 = 1000.0 * 0.01 / seconds_in_day\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": [4, 4],\n \"level\": [0.0, 1.0],\n \"flow_rate\": [0.0, q1000],\n }\n )\n)\n\nSetup fractional flows:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [5, 8, 13],\n \"fraction\": [0.3, 0.6, 0.1],\n }\n )\n)\n\nSetup pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [7],\n \"flow_rate\": [0.5 / 3600],\n }\n )\n)\n\nSetup level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [11, 17],\n \"level\": [0.5, 1.5],\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [15, 16],\n \"flow_rate\": [1e-4, 1e-4],\n }\n )\n)\n\nSetup terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [14],\n }\n )\n)\n\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin,\n (1.0, 0.0), # 2: ManningResistance\n (2.0, 0.0), # 3: Basin\n (3.0, 0.0), # 4: TabulatedRatingCurve\n (3.0, 1.0), # 5: FractionalFlow\n (3.0, 2.0), # 6: Basin\n (4.0, 1.0), # 7: Pump\n (4.0, 0.0), # 8: FractionalFlow\n (5.0, 0.0), # 9: Basin\n (6.0, 0.0), # 10: LinearResistance\n (2.0, 2.0), # 11: LevelBoundary\n (2.0, 1.0), # 12: LinearResistance\n (3.0, -1.0), # 13: FractionalFlow\n (3.0, -2.0), # 14: Terminal\n (3.0, 3.0), # 15: FlowBoundary\n (0.0, 1.0), # 16: FlowBoundary\n (6.0, 1.0), # 17: LevelBoundary\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_id, node_type = ribasim.Node.node_ids_and_types(\n basin,\n manning_resistance,\n rating_curve,\n pump,\n fractional_flow,\n linear_resistance,\n level_boundary,\n flow_boundary,\n terminal,\n)\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type},\n index=pd.Index(node_id, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64\n)\nto_id = np.array(\n [2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64\n)\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": len(from_id) * [\"flow\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n level_boundary=level_boundary,\n flow_boundary=flow_boundary,\n pump=pump,\n linear_resistance=linear_resistance,\n manning_resistance=manning_resistance,\n tabulated_rating_curve=rating_curve,\n fractional_flow=fractional_flow,\n terminal=terminal,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"basic/ribasim.toml\")\n\nPosixPath('data/basic/ribasim.toml')\n\n\n\n\n2 Update the basic model with transient forcing\nThis assumes you have already created the basic model with static forcing.\n\nimport numpy as np\nimport pandas as pd\nimport ribasim\nimport xarray as xr\n\n\nmodel = ribasim.Model(filepath=datadir / \"basic/ribasim.toml\")\n\nCan't read from data/basic/database.gpkg:Basin / area\n\n\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\nWe’ll use xarray to easily broadcast the values.\n\ntimeseries = (\n pd.DataFrame(\n data={\n \"node_id\": 1,\n \"time\": pd.date_range(model.starttime, model.endtime),\n \"drainage\": 0.0,\n \"potential_evaporation\": evaporation,\n \"infiltration\": 0.0,\n \"precipitation\": precipitation,\n \"urban_runoff\": 0.0,\n }\n )\n .set_index(\"time\")\n .to_xarray()\n)\n\nbasin_ids = model.basin.static.df[\"node_id\"].to_numpy()\nbasin_nodes = xr.DataArray(\n np.ones(len(basin_ids)), coords={\"node_id\": basin_ids}, dims=[\"node_id\"]\n)\nforcing = (timeseries * basin_nodes).to_dataframe().reset_index()\n\n\nstate = pd.DataFrame(\n data={\n \"node_id\": basin_ids,\n \"level\": 1.4,\n }\n)\n\n\nmodel.basin.time.df = forcing\nmodel.basin.state.df = state\n\n\nmodel.write(datadir / \"basic_transient/ribasim.toml\")\n\nPosixPath('data/basic_transient/ribasim.toml')\n\n\nNow run the model with ribasim basic_transient/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"basic_transient/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n\n\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic_transient/results/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow_rate * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n\n\n\n\n\n\n\n\ntype(df_flow)\n\npandas.core.frame.DataFrame\n\n\n\n\n3 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin\n (1.0, 1.0), # 2: Pump\n (1.0, -1.0), # 3: Pump\n (2.0, 0.0), # 4: LevelBoundary\n (-1.0, 0.0), # 5: TabulatedRatingCurve\n (-2.0, 0.0), # 6: Terminal\n (1.0, 0.0), # 7: DiscreteControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"Basin\",\n \"Pump\",\n \"Pump\",\n \"LevelBoundary\",\n \"TabulatedRatingCurve\",\n \"Terminal\",\n \"DiscreteControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)\nto_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)\n\nedge_type = 6 * [\"flow\"] + 2 * [\"control\"]\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\"from_node_id\": from_id, \"to_node_id\": to_id, \"edge_type\": edge_type},\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1],\n \"area\": [1000.0, 1000.0],\n \"level\": [0.0, 1.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [1], \"level\": [20.0]})\n\nbasin = ribasim.Basin(profile=profile, state=state)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": 3 * [7],\n \"listen_feature_id\": 3 * [1],\n \"variable\": 3 * [\"level\"],\n \"greater_than\": [5.0, 10.0, 15.0], # min, setpoint, max\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 5 * [7],\n \"truth_state\": [\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n \"control_state\": [\"in\", \"in\", \"none\", \"out\", \"out\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nThe above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": 3 * [2] + 3 * [3],\n \"control_state\": 2 * [\"none\", \"in\", \"out\"],\n \"flow_rate\": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],\n }\n )\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(data={\"node_id\": [4], \"level\": [10.0]})\n)\n\nSetup the rating curve:\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\"node_id\": 2 * [5], \"level\": [2.0, 15.0], \"flow_rate\": [0.0, 1e-3]}\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(static=pd.DataFrame(data={\"node_id\": [6]}))\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n pump=pump,\n level_boundary=level_boundary,\n tabulated_rating_curve=rating_curve,\n terminal=terminal,\n discrete_control=discrete_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax/ribasim.toml\")\n\nPosixPath('data/level_setpoint_with_minmax/ribasim.toml')\n\n\nNow run the model with level_setpoint_with_minmax/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.df.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\n\n\n\n\nThe highlighted regions show where a pump is active.\nLet’s print an overview of what happened with control:\n\nmodel.print_discrete_control_record(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\n0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level > 15.0\n\n This yielded control state \"out\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan\n\n1. At 2020-02-08 19:07:33.550000 the control node with ID 7 reached truth state TFF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n\n2. At 2020-07-05 09:06:07.383000 the control node with ID 7 reached truth state FFF:\n For node ID 1 (Basin): level < 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"in\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.002, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n\n3. At 2020-08-11 06:17:03.985000 the control node with ID 7 reached truth state TTF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n For node ID 3 (Pump): active = <NA>, flow_rate = 0.0, min_flow_rate = nan, max_flow_rate = nan\n\n\n\nNote that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.\n\n\n4 Model with PID control\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.5), # 3: Pump\n (3.0, 0.0), # 4: LevelBoundary\n (1.5, 1.0), # 5: PidControl\n (2.0, -0.5), # 6: outlet\n (1.5, -1.0), # 7: PidControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"Pump\",\n \"LevelBoundary\",\n \"PidControl\",\n \"Outlet\",\n \"PidControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)\nto_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": 5 * [\"flow\"] + 2 * [\"control\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2], \"level\": [0.0, 1.0], \"area\": [1000.0, 1000.0]}\n)\n\nstate = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"level\": [6.0],\n }\n)\n\nbasin = ribasim.Basin(profile=profile, state=state)\n\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup the outlet:\n\noutlet = ribasim.Outlet(\n static=pd.DataFrame(\n data={\n \"node_id\": [6],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": [1e-3]})\n)\n\nSetup flow boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"level\": [1.0], # Not relevant\n }\n )\n)\n\nSetup PID control:\n\npid_control = ribasim.PidControl(\n time=pd.DataFrame(\n data={\n \"node_id\": 4 * [5, 7],\n \"time\": [\n \"2020-01-01 00:00:00\",\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n \"listen_node_id\": 4 * [2, 2],\n \"target\": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],\n \"proportional\": 4 * [-1e-3, 1e-3],\n \"integral\": 4 * [-1e-7, 1e-7],\n \"derivative\": 4 * [0.0, 0.0],\n }\n )\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n flow_boundary=flow_boundary,\n level_boundary=level_boundary,\n pump=pump,\n outlet=outlet,\n pid_control=pid_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control/ribasim.toml\")\n\nPosixPath('data/pid_control/ribasim.toml')\n\n\nNow run the model with ribasim pid_control/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\nlevel_demands = model.pid_control.time.df.target.to_numpy()[:4]\ntimes = date2num(model.pid_control.time.df.time)[:4]\nax.plot(times, level_demands, color=\"k\", ls=\":\", label=\"target level\")\npass\n\n\n\n\n\n\n\n\n\n\n5 Model with allocation (user demand)\nSetup the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (1.0, 1.0), # 3: UserDemand\n (2.0, 0.0), # 4: LinearResistance\n (3.0, 0.0), # 5: Basin\n (3.0, 1.0), # 6: UserDemand\n (4.0, 0.0), # 7: TabulatedRatingCurve\n (4.5, 0.0), # 8: FractionalFlow\n (4.5, 0.5), # 9: FractionalFlow\n (5.0, 0.0), # 10: Terminal\n (4.5, 0.25), # 11: DiscreteControl\n (4.5, 1.0), # 12: Basin\n (5.0, 1.0), # 13: UserDemand\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"UserDemand\",\n \"LinearResistance\",\n \"Basin\",\n \"UserDemand\",\n \"TabulatedRatingCurve\",\n \"FractionalFlow\",\n \"FractionalFlow\",\n \"Terminal\",\n \"DiscreteControl\",\n \"Basin\",\n \"UserDemand\",\n]\n\n# All nodes belong to allocation network id 1\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"node_type\": node_type, \"subnetwork_id\": 1},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 2, 4, 5, 5, 7, 3, 6, 7, 8, 9, 12, 13, 11, 11],\n dtype=np.int64,\n)\nto_id = np.array(\n [2, 3, 4, 5, 6, 7, 8, 2, 5, 9, 10, 12, 13, 10, 8, 9],\n dtype=np.int64,\n)\n# Denote the first edge, 1 => 2, as a source edge for\n# allocation network 1\nsubnetwork_id = len(from_id) * [None]\nsubnetwork_id[0] = 1\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": (len(from_id) - 2) * [\"flow\"] + 2 * [\"control\"],\n \"subnetwork_id\": subnetwork_id,\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [2, 2, 5, 5, 12, 12],\n \"area\": 300_000.0,\n \"level\": 3 * [0.0, 1.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [2, 5, 12], \"level\": 1.0})\n\nbasin = ribasim.Basin(profile=profile, state=state)\n\nSetup the flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [1],\n \"flow_rate\": 2.0,\n }\n )\n)\n\nSetup the linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"resistance\": 0.06,\n }\n )\n)\n\nSetup the tabulated rating curve:\n\ntabulated_rating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": 7,\n \"level\": [0.0, 0.5, 1.0],\n \"flow_rate\": [0.0, 0.0, 2.0],\n }\n )\n)\n\nSetup the fractional flow:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [8, 8, 9, 9],\n \"fraction\": [0.6, 0.9, 0.4, 0.1],\n \"control_state\": [\"divert\", \"close\", \"divert\", \"close\"],\n }\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [10],\n }\n )\n)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": [11],\n \"listen_feature_id\": 5,\n \"variable\": \"level\",\n \"greater_than\": 0.52,\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 11,\n \"truth_state\": [\"T\", \"F\"],\n \"control_state\": [\"divert\", \"close\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nSetup the users:\n\nuser_demand = ribasim.UserDemand(\n static=pd.DataFrame(\n data={\n \"node_id\": [6, 13],\n \"demand\": [1.5, 1.0],\n \"return_factor\": 0.0,\n \"min_level\": -1.0,\n \"priority\": [1, 3],\n }\n ),\n time=pd.DataFrame(\n data={\n \"node_id\": [3, 3, 3, 3],\n \"demand\": [0.0, 1.0, 1.2, 1.2],\n \"priority\": [1, 1, 2, 2],\n \"return_factor\": 0.0,\n \"min_level\": -1.0,\n \"time\": 2 * [\"2020-01-01 00:00:00\", \"2020-01-20 00:00:00\"],\n }\n ),\n)\n\nSetup the allocation:\n\nallocation = ribasim.Allocation(use_allocation=True, timestep=86400)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n flow_boundary=flow_boundary,\n linear_resistance=linear_resistance,\n tabulated_rating_curve=tabulated_rating_curve,\n terminal=terminal,\n user_demand=user_demand,\n discrete_control=discrete_control,\n fractional_flow=fractional_flow,\n allocation=allocation,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-01-20 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"allocation_example/ribasim.toml\")\n\nPosixPath('data/allocation_example/ribasim.toml')\n\n\nNow run the model with ribasim allocation_example/ribasim.toml. After running the model, read back the results:\n\nimport matplotlib.ticker as plticker\n\ndf_allocation = pd.read_feather(datadir / \"allocation_example/results/allocation.arrow\")\ndf_allocation_wide = df_allocation.pivot_table(\n index=\"time\",\n columns=[\"node_type\", \"node_id\", \"priority\"],\n values=[\"demand\", \"allocated\", \"realized\"],\n)\ndf_allocation_wide = df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]\n\nfig, axs = plt.subplots(1, 3, figsize=(8, 5))\n\ndf_allocation_wide[\"demand\"].plot(ax=axs[0], ls=\":\")\ndf_allocation_wide[\"allocated\"].plot(ax=axs[1], ls=\"--\")\ndf_allocation_wide[\"realized\"].plot(ax=axs[2])\n\nfig.tight_layout()\nloc = plticker.MultipleLocator(2)\n\naxs[0].set_ylabel(\"level [m]\")\n\nfor ax, title in zip(axs, [\"Demand\", \"Allocated\", \"Abstracted\"]):\n ax.set_title(title)\n ax.set_ylim(0.0, 1.6)\n ax.xaxis.set_major_locator(loc)\n\n\n\n\n\n\n\n\nSome things to note about this plot:\n\nAbstraction behaves somewhat erratically at the start of the simulation. This is because allocation is based on flows computed in the physical layer, and at the start of the simulation these are not known yet.\nAlthough there is a plotted line for abstraction per priority, abstraction is actually accumulated over all priorities per user.\n\n\ndf_basin = pd.read_feather(datadir / \"allocation_example/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\nax.set_title(\"Basin levels\")\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\n\n\n6 Model with allocation (basin supply/demand)\nSetup the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.0), # 3: UserDemand\n (1.0, -1.0), # 4: LevelDemand\n (2.0, -1.0), # 5: Basin\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\"FlowBoundary\", \"Basin\", \"UserDemand\", \"LevelDemand\", \"Basin\"]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\n \"node_type\": node_type,\n \"subnetwork_id\": 5 * [2],\n },\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 4, 3, 4])\nto_id = np.array([2, 3, 2, 5, 5])\nedge_type = [\"flow\", \"flow\", \"control\", \"flow\", \"control\"]\nsubnetwork_id = [2, None, None, None, None]\n\nlines = node.geometry_from_connectivity(from_id.tolist(), to_id.tolist())\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": edge_type,\n \"subnetwork_id\": subnetwork_id,\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2, 5, 5], \"area\": 1e3, \"level\": [0.0, 1.0, 0.0, 1.0]}\n)\nstatic = pd.DataFrame(\n data={\n \"node_id\": [5],\n \"drainage\": 0.0,\n \"potential_evaporation\": 0.0,\n \"infiltration\": 0.0,\n \"precipitation\": 0.0,\n \"urban_runoff\": 0.0,\n }\n)\ntime = pd.DataFrame(\n data={\n \"node_id\": 2,\n \"time\": [\"2020-01-01 00:00:00\", \"2020-01-16 00:00:00\"],\n \"drainage\": 0.0,\n \"potential_evaporation\": 0.0,\n \"infiltration\": 0.0,\n \"precipitation\": [1e-6, 0.0],\n \"urban_runoff\": 0.0,\n },\n)\n\nstate = pd.DataFrame(data={\"node_id\": [2, 5], \"level\": 0.5})\nbasin = ribasim.Basin(profile=profile, static=static, time=time, state=state)\n\nSetup the flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": 1e-3})\n)\n\nSetup allocation level control:\n\nlevel_demand = ribasim.LevelDemand(\n static=pd.DataFrame(\n data={\"node_id\": [4], \"priority\": 1, \"min_level\": 1.0, \"max_level\": 1.5}\n )\n)\n\nSetup the users:\n\nuser_demand = ribasim.UserDemand(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"priority\": [2],\n \"demand\": [1.5e-3],\n \"return_factor\": [0.2],\n \"min_level\": [0.2],\n }\n )\n)\n\nSetup the allocation:\n\nallocation = ribasim.Allocation(use_allocation=True, timestep=1e5)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(node=node, edge=edge),\n basin=basin,\n flow_boundary=flow_boundary,\n level_demand=level_demand,\n user_demand=user_demand,\n allocation=allocation,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-02-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\nmodel.write(datadir / \"level_demand/ribasim.toml\")\n\nPosixPath('data/level_demand/ribasim.toml')\n\n\nNow run the model with ribasim level_demand/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"level_demand/results/basin.arrow\")\ndf_basin = df_basin[df_basin.node_id == 2]\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nwhere_allocation = (\n df_basin_wide.index - df_basin_wide.index[0]\n).total_seconds() % model.allocation.timestep == 0\nwhere_allocation[0] = False\ndf_basin_wide[where_allocation][\"level\"].plot(\n style=\"o\",\n ax=ax,\n)\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\nIn the plot above, the line denotes the level of Basin #2 over time and the dots denote the times at which allocation optimization was run, with intervals of \\(\\Delta t_{\\text{alloc}}\\). The Basin level is a piecewise linear function of time, with several stages explained below.\nConstants: - \\(d\\): UserDemand #3 demand, - \\(\\phi\\): Basin #2 precipitation rate, - \\(q\\): LevelBoundary flow.\nStages: - In the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \\(q + \\phi - d\\); - In the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \\(q + \\phi\\); - In the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \\(\\phi\\); - In the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \\(q + \\phi - d\\); - At the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \\(q - d\\). - In the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.",
- "crumbs": [
- "Python tooling",
- "Examples"
- ]
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
},
{
"objectID": "build/index.html#modules",
@@ -835,134 +830,118 @@
]
},
{
- "objectID": "src/index.html",
- "href": "src/index.html",
- "title": "1 API Reference",
- "section": "",
- "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
- },
- {
- "objectID": "src/index.html#modules",
- "href": "src/index.html#modules",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]"
- },
- {
- "objectID": "src/index.html#types",
- "href": "src/index.html#types",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]"
- },
- {
- "objectID": "src/index.html#functions",
- "href": "src/index.html#functions",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]"
- },
- {
- "objectID": "src/index.html#constants",
- "href": "src/index.html#constants",
- "title": "1 API Reference",
- "section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]"
- },
- {
- "objectID": "src/index.html#macros",
- "href": "src/index.html#macros",
- "title": "1 API Reference",
+ "objectID": "python/examples.html",
+ "href": "python/examples.html",
+ "title": "Examples",
"section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
+ "text": "1 Basic model with static forcing\n\nimport shutil\nfrom pathlib import Path\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport ribasim\nfrom ribasim.config import Node\nfrom ribasim.model import Model\nfrom ribasim.nodes import (\n basin,\n discrete_control,\n flow_boundary,\n fractional_flow,\n level_boundary,\n level_demand,\n linear_resistance,\n manning_resistance,\n outlet,\n pid_control,\n pump,\n tabulated_rating_curve,\n user_demand,\n)\nfrom shapely.geometry import Point\n\n\ndatadir = Path(\"data\")\nshutil.rmtree(datadir, ignore_errors=True)\n\n\nmodel = Model(starttime=\"2020-01-01 00:00:00\", endtime=\"2021-01-01 00:00:00\")\n\nSetup the basins:\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\n\nbasin_data = [\n basin.Profile(area=[0.01, 1000.0], level=[0.0, 1.0]),\n basin.Time(\n time=pd.date_range(model.starttime, model.endtime),\n drainage=0.0,\n potential_evaporation=evaporation,\n infiltration=0.0,\n precipitation=precipitation,\n urban_runoff=0.0,\n ),\n basin.State(level=[1.4]),\n]\n\nmodel.basin.add(Node(1, Point(0.0, 0.0)), basin_data)\nmodel.basin.add(Node(3, Point(2.0, 0.0)), basin_data)\nmodel.basin.add(Node(6, Point(3.0, 2.0)), basin_data)\nmodel.basin.add(Node(9, Point(5.0, 0.0)), basin_data)\n\nSetup linear resistance:\n\nmodel.linear_resistance.add(\n Node(10, Point(6.0, 0.0)),\n [linear_resistance.Static(resistance=[5e3])],\n)\nmodel.linear_resistance.add(\n Node(12, Point(2.0, 1.0)),\n [linear_resistance.Static(resistance=[3600.0 * 24.0 / 100.0])],\n)\n\nSetup Manning resistance:\n\nmodel.manning_resistance.add(\n Node(2, Point(1.0, 0.0)),\n [\n manning_resistance.Static(\n length=[900], manning_n=[0.04], profile_width=[6.0], profile_slope=[3.0]\n )\n ],\n)\n\nSet up a rating curve node:\n\nmodel.tabulated_rating_curve.add(\n Node(4, Point(3.0, 0.0)),\n [tabulated_rating_curve.Static(level=[0.0, 1.0], flow_rate=[0.0, 10 / 86400])],\n)\n\nSetup fractional flows:\n\nmodel.fractional_flow.add(\n Node(5, Point(3.0, 1.0)), [fractional_flow.Static(fraction=[0.3])]\n)\nmodel.fractional_flow.add(\n Node(8, Point(4.0, 0.0)), [fractional_flow.Static(fraction=[0.6])]\n)\nmodel.fractional_flow.add(\n Node(13, Point(3.0, -1.0)),\n [fractional_flow.Static(fraction=[0.1])],\n)\n\nSetup pump:\n\nmodel.pump.add(Node(7, Point(4.0, 1.0)), [pump.Static(flow_rate=[0.5 / 3600])])\n\nSetup level boundary:\n\nmodel.level_boundary.add(\n Node(11, Point(2.0, 2.0)), [level_boundary.Static(level=[0.5])]\n)\nmodel.level_boundary.add(\n Node(17, Point(6.0, 1.0)), [level_boundary.Static(level=[1.5])]\n)\n\nSetup flow boundary:\n\nmodel.flow_boundary.add(\n Node(15, Point(3.0, 3.0)), [flow_boundary.Static(flow_rate=[1e-4])]\n)\nmodel.flow_boundary.add(\n Node(16, Point(0.0, 1.0)), [flow_boundary.Static(flow_rate=[1e-4])]\n)\n\nSetup terminal:\n\nmodel.terminal.add(Node(14, Point(3.0, -2.0)))\n\nSetup the edges:\n\nmodel.edge.add(model.basin[1], model.manning_resistance[2], \"flow\")\nmodel.edge.add(model.manning_resistance[2], model.basin[3], \"flow\")\nmodel.edge.add(model.basin[3], model.tabulated_rating_curve[4], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[5], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[8], \"flow\")\nmodel.edge.add(model.fractional_flow[5], model.basin[6], \"flow\")\nmodel.edge.add(model.basin[6], model.pump[7], \"flow\")\nmodel.edge.add(model.fractional_flow[8], model.basin[9], \"flow\")\nmodel.edge.add(model.pump[7], model.basin[9], \"flow\")\nmodel.edge.add(model.basin[9], model.linear_resistance[10], \"flow\")\nmodel.edge.add(model.level_boundary[11], model.linear_resistance[12], \"flow\")\nmodel.edge.add(model.linear_resistance[12], model.basin[3], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[4], model.fractional_flow[13], \"flow\")\nmodel.edge.add(model.fractional_flow[13], model.terminal[14], \"flow\")\nmodel.edge.add(model.flow_boundary[15], model.basin[6], \"flow\")\nmodel.edge.add(model.flow_boundary[16], model.basin[1], \"flow\")\nmodel.edge.add(model.linear_resistance[10], model.level_boundary[17], \"flow\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\nmodel.write(datadir / \"basic/ribasim.toml\")\n\nPosixPath('data/basic/ribasim.toml')\n\n\nNow run the model with ribasim basic/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"basic/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n\n\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic/results/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow_rate * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n\n\n\n\n\n\n\n\n\n2 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSetup the basins:\n\nmodel = Model(starttime=\"2020-01-01 00:00:00\", endtime=\"2021-01-01 00:00:00\")\n\n\nmodel.basin.add(\n Node(1, Point(0.0, 0.0)),\n [\n basin.Profile(area=[1000.0, 1000.0], level=[0.0, 1.0]),\n basin.State(level=[20.0]),\n ],\n)\n\nSetup the discrete control:\n\nmodel.discrete_control.add(\n Node(7, Point(1.0, 0.0)),\n [\n discrete_control.Condition(\n listen_node_id=[1, 1, 1],\n listen_node_type=[\"Basin\", \"Basin\", \"Basin\"],\n variable=[\"level\", \"level\", \"level\"],\n greater_than=[5.0, 10.0, 15.0],\n ),\n discrete_control.Logic(\n truth_state=[\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n control_state=[\"in\", \"in\", \"none\", \"out\", \"out\"],\n ),\n ],\n)\n\nThe above control logic can be summarized as follows:\n\nIf the level gets above the maximum, activate the control state “out” until the setpoint is reached;\nIf the level gets below the minimum, active the control state “in” until the setpoint is reached;\nOtherwise activate the control state “none”.\n\nSetup the pump:\n\nmodel.pump.add(\n Node(2, Point(1.0, 1.0)),\n [pump.Static(control_state=[\"none\", \"in\", \"out\"], flow_rate=[0.0, 2e-3, 0.0])],\n)\nmodel.pump.add(\n Node(3, Point(1.0, -1.0)),\n [pump.Static(control_state=[\"none\", \"in\", \"out\"], flow_rate=[0.0, 0.0, 2e-3])],\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nmodel.level_boundary.add(\n Node(4, Point(2.0, 0.0)), [level_boundary.Static(level=[10.0])]\n)\n\nSetup the rating curve:\n\nmodel.tabulated_rating_curve.add(\n Node(5, Point(-1.0, 0.0)),\n [tabulated_rating_curve.Static(level=[2.0, 15.0], flow_rate=[0.0, 1e-3])],\n)\n\nSetup the terminal:\n\nmodel.terminal.add(Node(6, Point(-2.0, 0.0)))\n\nSetup edges:\n\nmodel.edge.add(model.basin[1], model.pump[3], \"flow\")\nmodel.edge.add(model.pump[3], model.level_boundary[4], \"flow\")\nmodel.edge.add(model.level_boundary[4], model.pump[2], \"flow\")\nmodel.edge.add(model.pump[2], model.basin[1], \"flow\")\nmodel.edge.add(model.basin[1], model.tabulated_rating_curve[5], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[5], model.terminal[6], \"flow\")\nmodel.edge.add(model.discrete_control[7], model.pump[2], \"control\")\nmodel.edge.add(model.discrete_control[7], model.pump[3], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax/ribasim.toml\")\n\nPosixPath('data/level_setpoint_with_minmax/ribasim.toml')\n\n\nNow run the model with level_setpoint_with_minmax/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.df.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\n\n\n\n\nThe highlighted regions show where a pump is active.\n\n\n3 Model with PID control\nSet up the model:\n\nmodel = Model(\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nSetup the basins:\n\nmodel.basin.add(\n Node(2, Point(1.0, 0.0)),\n [basin.Profile(area=[1000.0, 1000.0], level=[0.0, 1.0]), basin.State(level=[6.0])],\n)\n\nSetup the pump:\n\nmodel.pump.add(\n Node(3, Point(2.0, 0.5)),\n [pump.Static(flow_rate=[0.0])], # Will be overwritten by PID controller\n)\n\nSetup the outlet:\n\nmodel.outlet.add(\n Node(6, Point(2.0, -0.5)),\n [outlet.Static(flow_rate=[0.0])], # Will be overwritten by PID controller\n)\n\nSetup flow boundary:\n\nmodel.flow_boundary.add(\n Node(1, Point(0.0, 0.0)),\n [flow_boundary.Static(flow_rate=[1e-3])],\n)\n\nSetup flow boundary:\n\nmodel.level_boundary.add(\n Node(4, Point(3.0, 0.0)),\n [level_boundary.Static(level=[1])],\n)\n\nSetup PID control:\n\nmodel.pid_control.add(\n Node(5, Point(1.5, 1.0)),\n [\n pid_control.Time(\n time=[\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n listen_node_id=[2, 2, 2, 2],\n listen_node_type=[\"Basin\", \"Basin\", \"Basin\", \"Basin\"],\n target=[5.0, 5.0, 7.5, 7.5],\n proportional=[-1e-3, 1e-3, 1e-3, 1e-3],\n integral=[-1e-7, 1e-7, -1e-7, 1e-7],\n derivative=[0.0, 0.0, 0.0, 0.0],\n )\n ],\n)\nmodel.pid_control.add(\n Node(7, Point(1.5, -1.0)),\n [\n pid_control.Time(\n time=[\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n listen_node_id=[2, 2, 2, 2],\n listen_node_type=[\"Basin\", \"Basin\", \"Basin\", \"Basin\"],\n target=[5.0, 5.0, 7.5, 7.5],\n proportional=[-1e-3, 1e-3, 1e-3, 1e-3],\n integral=[-1e-7, 1e-7, -1e-7, 1e-7],\n derivative=[0.0, 0.0, 0.0, 0.0],\n )\n ],\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup the edges:\n\nmodel.edge.add(model.flow_boundary[1], model.basin[2], \"flow\")\nmodel.edge.add(model.basin[2], model.pump[3], \"flow\")\nmodel.edge.add(model.pump[3], model.level_boundary[4], \"flow\")\nmodel.edge.add(model.level_boundary[4], model.outlet[6], \"flow\")\nmodel.edge.add(model.outlet[6], model.basin[2], \"flow\")\nmodel.edge.add(model.pid_control[5], model.pump[3], \"control\")\nmodel.edge.add(model.pid_control[7], model.outlet[6], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control/ribasim.toml\")\n\nPosixPath('data/pid_control/ribasim.toml')\n\n\nNow run the model with ribasim pid_control/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\nlevel_demands = model.pid_control.time.df.target.to_numpy()[:4]\ntimes = date2num(model.pid_control.time.df.time)[:4]\nax.plot(times, level_demands, color=\"k\", ls=\":\", label=\"target level\")\npass\n\n\n\n\n\n\n\n\n\n\n4 Model with allocation (user demand)\nSetup a model:\n\nmodel = Model(\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-01-20 00:00:00\",\n)\n\nSetup the basins:\n\nbasin_data = [\n basin.Profile(area=[300_000.0, 300_000.0], level=[0.0, 1.0]),\n basin.State(level=[1.0]),\n]\n\nmodel.basin.add(\n Node(2, Point(1.0, 0.0), subnetwork_id=1),\n basin_data,\n)\nmodel.basin.add(\n Node(5, Point(3.0, 0.0), subnetwork_id=1),\n basin_data,\n)\nmodel.basin.add(\n Node(12, Point(4.5, 1.0), subnetwork_id=1),\n basin_data,\n)\n\nSetup the flow boundary:\n\nmodel.flow_boundary.add(\n Node(1, Point(0.0, 0.0), subnetwork_id=1), [flow_boundary.Static(flow_rate=[2.0])]\n)\n\nSetup the linear resistance:\n\nmodel.linear_resistance.add(\n Node(4, Point(2.0, 0.0), subnetwork_id=1),\n [linear_resistance.Static(resistance=[0.06])],\n)\n\nSetup the tabulated rating curve:\n\nmodel.tabulated_rating_curve.add(\n Node(7, Point(4.0, 0.0), subnetwork_id=1),\n [tabulated_rating_curve.Static(level=[0.0, 0.5, 1.0], flow_rate=[0.0, 0.0, 2.0])],\n)\n\nSetup the fractional flow:\n\nmodel.fractional_flow.add(\n Node(8, Point(4.5, 0.0), subnetwork_id=1),\n [fractional_flow.Static(fraction=[0.6, 0.9], control_state=[\"divert\", \"close\"])],\n)\nmodel.fractional_flow.add(\n Node(9, Point(4.5, 0.5), subnetwork_id=1),\n [fractional_flow.Static(fraction=[0.4, 0.1], control_state=[\"divert\", \"close\"])],\n)\n\nSetup the terminal:\n\nmodel.terminal.add(Node(10, Point(5.0, 0.0), subnetwork_id=1))\n\nSetup the discrete control:\n\nmodel.discrete_control.add(\n Node(11, Point(4.5, 0.25), subnetwork_id=1),\n [\n discrete_control.Condition(\n listen_node_id=[5],\n listen_node_type=[\"Basin\"],\n variable=[\"level\"],\n greater_than=[0.52],\n ),\n discrete_control.Logic(\n truth_state=[\"T\", \"F\"], control_state=[\"divert\", \"close\"]\n ),\n ],\n)\n\nSetup the users:\n\nmodel.user_demand.add(\n Node(6, Point(3.0, 1.0), subnetwork_id=1),\n [\n user_demand.Static(\n demand=[1.5], return_factor=[0.0], min_level=[-1.0], priority=[1]\n )\n ],\n)\nmodel.user_demand.add(\n Node(13, Point(5.0, 1.0), subnetwork_id=1),\n [\n user_demand.Static(\n demand=[1.0], return_factor=[0.0], min_level=[-1.0], priority=[3]\n )\n ],\n)\nmodel.user_demand.add(\n Node(3, Point(1.0, 1.0), subnetwork_id=1),\n [\n user_demand.Time(\n demand=[0.0, 1.0, 1.2, 1.2],\n return_factor=[0.0, 0.0, 0.0, 0.0],\n min_level=[-1.0, -1.0, -1.0, -1.0],\n priority=[1, 1, 2, 2],\n time=2 * [\"2020-01-01 00:00:00\", \"2020-01-20 00:00:00\"],\n )\n ],\n)\n\nSetup the allocation:\n\nmodel.allocation = ribasim.Allocation(use_allocation=True, timestep=86400)\n\nSetup the edges:\n\nmodel.edge.add(model.flow_boundary[1], model.basin[2], \"flow\", subnetwork_id=1)\nmodel.edge.add(model.basin[2], model.user_demand[3], \"flow\")\nmodel.edge.add(model.basin[2], model.linear_resistance[4], \"flow\")\nmodel.edge.add(model.linear_resistance[4], model.basin[5], \"flow\")\nmodel.edge.add(model.basin[5], model.user_demand[6], \"flow\")\nmodel.edge.add(model.basin[5], model.tabulated_rating_curve[7], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[7], model.fractional_flow[8], \"flow\")\nmodel.edge.add(model.user_demand[3], model.basin[2], \"flow\")\nmodel.edge.add(model.user_demand[6], model.basin[5], \"flow\")\nmodel.edge.add(model.tabulated_rating_curve[7], model.fractional_flow[9], \"flow\")\nmodel.edge.add(model.fractional_flow[8], model.terminal[10], \"flow\")\nmodel.edge.add(model.fractional_flow[9], model.basin[12], \"flow\")\nmodel.edge.add(model.basin[12], model.user_demand[13], \"flow\")\nmodel.edge.add(model.user_demand[13], model.terminal[10], \"flow\")\nmodel.edge.add(model.discrete_control[11], model.fractional_flow[8], \"control\")\nmodel.edge.add(model.discrete_control[11], model.fractional_flow[9], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"allocation_example/ribasim.toml\")\n\nPosixPath('data/allocation_example/ribasim.toml')\n\n\nNow run the model with ribasim allocation_example/ribasim.toml. After running the model, read back the results:\n\nimport matplotlib.ticker as plticker\n\ndf_allocation = pd.read_feather(datadir / \"allocation_example/results/allocation.arrow\")\ndf_allocation_wide = df_allocation.pivot_table(\n index=\"time\",\n columns=[\"node_type\", \"node_id\", \"priority\"],\n values=[\"demand\", \"allocated\", \"realized\"],\n)\ndf_allocation_wide = df_allocation_wide.loc[:, (df_allocation_wide != 0).any(axis=0)]\n\nfig, axs = plt.subplots(1, 3, figsize=(8, 5))\n\ndf_allocation_wide[\"demand\"].plot(ax=axs[0], ls=\":\")\ndf_allocation_wide[\"allocated\"].plot(ax=axs[1], ls=\"--\")\ndf_allocation_wide[\"realized\"].plot(ax=axs[2])\n\nfig.tight_layout()\nloc = plticker.MultipleLocator(2)\n\naxs[0].set_ylabel(\"level [m]\")\n\nfor ax, title in zip(axs, [\"Demand\", \"Allocated\", \"Abstracted\"]):\n ax.set_title(title)\n ax.set_ylim(0.0, 1.6)\n ax.xaxis.set_major_locator(loc)\n\n\n\n\n\n\n\n\nSome things to note about this plot:\n\nAbstraction behaves somewhat erratically at the start of the simulation. This is because allocation is based on flows computed in the physical layer, and at the start of the simulation these are not known yet.\nAlthough there is a plotted line for abstraction per priority, abstraction is actually accumulated over all priorities per user.\n\n\ndf_basin = pd.read_feather(datadir / \"allocation_example/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\nax.set_title(\"Basin levels\")\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\n\n\n5 Model with allocation (basin supply/demand)\nSetup a model:\n\nmodel = ribasim.Model(\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-02-01 00:00:00\",\n)\n\nSetup the basins:\n\nbasin_data = [\n basin.Profile(area=[1e3, 1e3], level=[0.0, 1.0]),\n basin.State(level=[0.5]),\n]\nmodel.basin.add(\n Node(2, Point(1.0, 0.0)),\n [\n *basin_data,\n basin.Time(\n time=[\"2020-01-01 00:00:00\", \"2020-01-16 00:00:00\"],\n drainage=[0.0, 0.0],\n potential_evaporation=[0.0, 0.0],\n infiltration=[0.0, 0.0],\n precipitation=[1e-6, 0.0],\n urban_runoff=[0.0, 0.0],\n ),\n ],\n)\nmodel.basin.add(\n Node(5, Point(2.0, -1.0)),\n [\n *basin_data,\n basin.Static(\n drainage=[0.0],\n potential_evaporation=[0.0],\n infiltration=[0.0],\n precipitation=[0.0],\n urban_runoff=[0.0],\n ),\n ],\n)\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2, 5, 5], \"area\": 1e3, \"level\": [0.0, 1.0, 0.0, 1.0]}\n)\n\nSetup the flow boundary:\n\nmodel.flow_boundary.add(\n Node(1, Point(0.0, 0.0)), [flow_boundary.Static(flow_rate=[1e-3])]\n)\n\nSetup allocation level control:\n\nmodel.level_demand.add(\n Node(4, Point(1.0, -1.0)),\n [level_demand.Static(priority=[1], min_level=[1.0], max_level=[1.5])],\n)\n\nSetup the users:\n\nmodel.user_demand.add(\n Node(3, Point(2.0, 0.0)),\n [\n user_demand.Static(\n priority=[2], demand=[1.5e-3], return_factor=[0.2], min_level=[0.2]\n )\n ],\n)\n\nSetup the allocation:\n\nmodel.allocation = ribasim.Allocation(use_allocation=True, timestep=1e5)\n\nSetup the edges:\n\nmodel.edge.add(model.flow_boundary[1], model.basin[2], \"flow\", subnetwork_id=2)\nmodel.edge.add(model.basin[2], model.user_demand[3], \"flow\")\nmodel.edge.add(model.level_demand[4], model.basin[2], \"control\")\nmodel.edge.add(model.user_demand[3], model.basin[5], \"flow\")\nmodel.edge.add(model.level_demand[4], model.basin[5], \"control\")\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n\n\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\nmodel.write(datadir / \"level_demand/ribasim.toml\")\n\nPosixPath('data/level_demand/ribasim.toml')\n\n\nNow run the model with ribasim level_demand/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"level_demand/results/basin.arrow\")\ndf_basin = df_basin[df_basin.node_id == 2]\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nwhere_allocation = (\n df_basin_wide.index - df_basin_wide.index[0]\n).total_seconds() % model.allocation.timestep == 0\nwhere_allocation[0] = False\ndf_basin_wide[where_allocation][\"level\"].plot(\n style=\"o\",\n ax=ax,\n)\nax.set_ylabel(\"level [m]\")\n\nText(0, 0.5, 'level [m]')\n\n\n\n\n\n\n\n\n\nIn the plot above, the line denotes the level of Basin #2 over time and the dots denote the times at which allocation optimization was run, with intervals of \\(\\Delta t_{\\text{alloc}}\\). The Basin level is a piecewise linear function of time, with several stages explained below.\nConstants:\n\n\\(d\\): UserDemand #3 demand,\n\\(\\phi\\): Basin #2 precipitation rate,\n\\(q\\): LevelBoundary flow.\n\nStages:\n\nIn the first stage the UserDemand abstracts fully, so the net change of Basin #2 is \\(q + \\phi - d\\);\nIn the second stage the Basin takes precedence so the UserDemand doesn’t abstract, hence the net change of Basin #2 is \\(q + \\phi\\);\nIn the third stage (and following stages) the Basin no longer has a positive demand, since precipitation provides enough water to get the Basin to its target level. The FlowBoundary flow gets fully allocated to the UserDemand, hence the net change of Basin #2 is \\(\\phi\\);\nIn the fourth stage the Basin enters its surplus stage, even though initially the level is below the maximum level. This is because the simulation anticipates that the current precipitation is going to bring the Basin level over its maximum level. The net change of Basin #2 is now \\(q + \\phi - d\\);\nAt the start of the fifth stage the precipitation stops, and so the UserDemand partly uses surplus water from the Basin to fulfill its demand. The net change of Basin #2 becomes \\(q - d\\).\nIn the final stage the Basin is in a dynamical equilibrium, since the Basin has no supply so the user abstracts precisely the flow from the LevelBoundary.",
+ "crumbs": [
+ "Python tooling",
+ "Examples"
+ ]
},
{
- "objectID": "python/test-models.html",
- "href": "python/test-models.html",
- "title": "Test models",
+ "objectID": "python/index.html",
+ "href": "python/index.html",
+ "title": "Python tooling",
"section": "",
- "text": "Ribasim developers use the following models in their testbench and in order to test new features.\n\n\nCode\nimport ribasim_testmodels\nimport matplotlib.pyplot as plt\n\nfor model_name, model_constructor in ribasim_testmodels.constructors.items():\n if model_name.startswith(\"invalid\"):\n continue\n\n model = model_constructor()\n fig, ax = plt.subplots()\n model.plot(ax)\n ax.set_title(label=model_name, loc=\"left\")\n fig.text(0, 1, model_constructor.__doc__)\n fig.tight_layout()\n plt.show()\n plt.close(fig)",
+ "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip:\npip install ribasim\nFor wheel (.whl) downloads, including nightly builds, see the download section. After downloading wheels can be installed by referring to the correct path:\npip install path/to/ribasim-*.whl\nFor documentation please see the examples and API reference.",
"crumbs": [
- "Python tooling",
- "Test models"
+ "Python tooling"
]
},
{
- "objectID": "python/reference/Terminal.html",
- "href": "python/reference/Terminal.html",
- "title": "1 Terminal",
+ "objectID": "python/reference/nodes.manning_resistance.html",
+ "href": "python/reference/nodes.manning_resistance.html",
+ "title": "1 nodes.manning_resistance",
"section": "",
- "text": "1 Terminal\nTerminal()"
+ "text": "1 nodes.manning_resistance\nnodes.manning_resistance"
},
{
- "objectID": "python/reference/FlowBoundary.html",
- "href": "python/reference/FlowBoundary.html",
- "title": "1 FlowBoundary",
+ "objectID": "python/reference/nodes.flow_boundary.html",
+ "href": "python/reference/nodes.flow_boundary.html",
+ "title": "1 nodes.flow_boundary",
"section": "",
- "text": "1 FlowBoundary\nFlowBoundary()"
+ "text": "1 nodes.flow_boundary\nnodes.flow_boundary"
},
{
- "objectID": "python/reference/UserDemand.html",
- "href": "python/reference/UserDemand.html",
- "title": "1 UserDemand",
+ "objectID": "python/reference/nodes.basin.html",
+ "href": "python/reference/nodes.basin.html",
+ "title": "1 nodes.basin",
"section": "",
- "text": "1 UserDemand\nUserDemand()"
+ "text": "1 nodes.basin\nnodes.basin"
},
{
- "objectID": "python/reference/FractionalFlow.html",
- "href": "python/reference/FractionalFlow.html",
- "title": "1 FractionalFlow",
+ "objectID": "python/reference/nodes.outlet.html",
+ "href": "python/reference/nodes.outlet.html",
+ "title": "1 nodes.outlet",
"section": "",
- "text": "1 FractionalFlow\nFractionalFlow()"
+ "text": "1 nodes.outlet\nnodes.outlet"
},
{
- "objectID": "python/reference/LinearResistance.html",
- "href": "python/reference/LinearResistance.html",
- "title": "1 LinearResistance",
+ "objectID": "python/reference/nodes.discrete_control.html",
+ "href": "python/reference/nodes.discrete_control.html",
+ "title": "1 nodes.discrete_control",
"section": "",
- "text": "1 LinearResistance\nLinearResistance()"
+ "text": "1 nodes.discrete_control\nnodes.discrete_control"
},
{
- "objectID": "python/reference/TabulatedRatingCurve.html",
- "href": "python/reference/TabulatedRatingCurve.html",
- "title": "1 TabulatedRatingCurve",
+ "objectID": "python/reference/index.html",
+ "href": "python/reference/index.html",
+ "title": "1 API Reference",
"section": "",
- "text": "1 TabulatedRatingCurve\nTabulatedRatingCurve()"
+ "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\n\n\n\n\n\n\n\nThe Edge database layer.\n\n\n\nEdgeTable\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nnodes.basin\n\n\n\nnodes.fractional_flow\n\n\n\nnodes.tabulated_rating_curve\n\n\n\nnodes.pump\n\n\n\nnodes.outlet\n\n\n\nnodes.user_demand\n\n\n\nnodes.level_boundary\n\n\n\nnodes.flow_boundary\n\n\n\nnodes.linear_resistance\n\n\n\nnodes.manning_resistance\n\n\n\nnodes.terminal\n\n\n\nnodes.discrete_control\n\n\n\nnodes.pid_control",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/DiscreteControl.html",
- "href": "python/reference/DiscreteControl.html",
- "title": "1 DiscreteControl",
+ "objectID": "python/reference/index.html#model",
+ "href": "python/reference/index.html#model",
+ "title": "1 API Reference",
"section": "",
- "text": "1 DiscreteControl\nDiscreteControl()"
+ "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/Edge.html",
- "href": "python/reference/Edge.html",
- "title": "1 Edge",
+ "objectID": "python/reference/index.html#edge",
+ "href": "python/reference/index.html#edge",
+ "title": "1 API Reference",
"section": "",
- "text": "Edge()\nDefines the connections between nodes.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.pandas.DataFrame\nTable describing the flow connections.\nrequired"
+ "text": "The Edge database layer.\n\n\n\nEdgeTable\nDefines the connections between nodes.",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/Edge.html#parameters",
- "href": "python/reference/Edge.html#parameters",
- "title": "1 Edge",
+ "objectID": "python/reference/index.html#node-types",
+ "href": "python/reference/index.html#node-types",
+ "title": "1 API Reference",
"section": "",
- "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.pandas.DataFrame\nTable describing the flow connections.\nrequired"
+ "text": "Available node types to model different situations.\n\n\n\nnodes.basin\n\n\n\nnodes.fractional_flow\n\n\n\nnodes.tabulated_rating_curve\n\n\n\nnodes.pump\n\n\n\nnodes.outlet\n\n\n\nnodes.user_demand\n\n\n\nnodes.level_boundary\n\n\n\nnodes.flow_boundary\n\n\n\nnodes.linear_resistance\n\n\n\nnodes.manning_resistance\n\n\n\nnodes.terminal\n\n\n\nnodes.discrete_control\n\n\n\nnodes.pid_control",
+ "crumbs": [
+ "Python tooling",
+ "API Reference"
+ ]
},
{
- "objectID": "python/reference/Model.html",
- "href": "python/reference/Model.html",
- "title": "1 Model",
+ "objectID": "python/reference/EdgeTable.html",
+ "href": "python/reference/EdgeTable.html",
+ "title": "1 EdgeTable",
"section": "",
- "text": "Model()\nA full Ribasim model schematisation with all input.\nRibasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime.datetime\nEnd time of the simulation.\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nribasim.config.Basin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nribasim.config.FractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nribasim.config.LevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nribasim.config.FlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nribasim.config.ManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nribasim.config.TabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nribasim.config.Pump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nribasim.config.Outlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nribasim.config.Terminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nribasim.config.DiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nribasim.config.PidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser_demand\nribasim.config.UserDemand\nUserDemand node type with demand and priority.\nrequired"
+ "text": "1 EdgeTable\nEdgeTable(self, **kwargs)\nDefines the connections between nodes."
},
{
- "objectID": "python/reference/Model.html#parameters",
- "href": "python/reference/Model.html#parameters",
- "title": "1 Model",
+ "objectID": "python/reference/nodes.tabulated_rating_curve.html",
+ "href": "python/reference/nodes.tabulated_rating_curve.html",
+ "title": "1 nodes.tabulated_rating_curve",
"section": "",
- "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime.datetime\nEnd time of the simulation.\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nribasim.config.Basin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nribasim.config.FractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nribasim.config.LevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nribasim.config.FlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nribasim.config.ManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nribasim.config.TabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nribasim.config.Pump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nribasim.config.Outlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nribasim.config.Terminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nribasim.config.DiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nribasim.config.PidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser_demand\nribasim.config.UserDemand\nUserDemand node type with demand and priority.\nrequired"
+ "text": "1 nodes.tabulated_rating_curve\nnodes.tabulated_rating_curve"
},
{
"objectID": "core/allocation.html",
@@ -1057,7 +1036,7 @@
"href": "core/allocation.html#example",
"title": "Allocation",
"section": "5.1 Example",
- "text": "5.1 Example\nThe following is an example of an optimization problem for the example shown here:\n\n\nCode\nusing Ribasim\nusing Ribasim: NodeID\nusing SQLite\nusing ComponentArrays: ComponentVector\n\ntoml_path = normpath(@__DIR__, \"../../generated_testmodels/allocation_example/ribasim.toml\")\np = Ribasim.Model(toml_path).integrator.p\nu = ComponentVector(; storage = zeros(length(p.basin.node_id)))\n\nallocation_model = p.allocation.allocation_models[1]\nt = 0.0\npriority_idx = 1\n\nRibasim.set_flow!(p.graph, NodeID(:FlowBoundary, 1), NodeID(:Basin, 2), 1.0)\n\nRibasim.adjust_source_capacities!(allocation_model, p, priority_idx)\nRibasim.adjust_edge_capacities!(allocation_model, p, priority_idx)\nRibasim.set_objective_priority!(allocation_model, p, u, t, priority_idx)\n\nprintln(p.allocation.allocation_models[1].problem)\n\n\nMin F_abs_user_demand[UserDemand #3] + F_abs_user_demand[UserDemand #13] + F_abs_user_demand[UserDemand #6] + F_abs_basin[Basin #2] + F_abs_basin[Basin #5] + F_abs_basin[Basin #12]\nSubject to\n flow_conservation[Basin #2] : -F[(FlowBoundary #1, Basin #2)] - F[(Basin #5, Basin #2)] + F[(Basin #2, Basin #5)] + F[(Basin #2, UserDemand #3)] + F_basin_in[Basin #2] - F_basin_out[Basin #2] = 0\n flow_conservation[Basin #5] : F[(Basin #5, TabulatedRatingCurve #7)] + F[(Basin #5, UserDemand #6)] + F[(Basin #5, Basin #2)] - F[(Basin #2, Basin #5)] + F_basin_in[Basin #5] - F_basin_out[Basin #5] = 0\n flow_conservation[Basin #12] : -F[(TabulatedRatingCurve #7, Basin #12)] + F[(Basin #12, UserDemand #13)] + F_basin_in[Basin #12] - F_basin_out[Basin #12] = 0\n source[(FlowBoundary #1, Basin #2)] : F[(FlowBoundary #1, Basin #2)] ≤ 1\n return_flow[UserDemand #13] : F[(UserDemand #13, Terminal #10)] ≤ 0\n fractional_flow[(TabulatedRatingCurve #7, Basin #12)] : -0.4 F[(Basin #5, TabulatedRatingCurve #7)] + F[(TabulatedRatingCurve #7, Basin #12)] ≤ 0\n basin_outflow[Basin #2] : F_basin_out[Basin #2] ≤ 0\n basin_outflow[Basin #5] : F_basin_out[Basin #5] ≤ 0\n basin_outflow[Basin #12] : F_basin_out[Basin #12] ≤ 0\n F[(Basin #5, TabulatedRatingCurve #7)] ≥ 0\n F[(Basin #5, UserDemand #6)] ≥ 0\n F[(FlowBoundary #1, Basin #2)] ≥ 0\n F[(Basin #5, Basin #2)] ≥ 0\n F[(Basin #2, Basin #5)] ≥ 0\n F[(Basin #2, UserDemand #3)] ≥ 0\n F[(TabulatedRatingCurve #7, Basin #12)] ≥ 0\n F[(TabulatedRatingCurve #7, Terminal #10)] ≥ 0\n F[(Basin #12, UserDemand #13)] ≥ 0\n F[(UserDemand #13, Terminal #10)] ≥ 0\n F_basin_in[Basin #2] ≥ 0\n F_basin_in[Basin #5] ≥ 0\n F_basin_in[Basin #12] ≥ 0\n F_basin_out[Basin #2] ≥ 0\n F_basin_out[Basin #5] ≥ 0\n F_basin_out[Basin #12] ≥ 0\n abs_positive_user_demand[UserDemand #3] : -F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 0\n abs_positive_user_demand[UserDemand #13] : -F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_positive_user_demand[UserDemand #6] : -F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ -1.5\n abs_negative_user_demand[UserDemand #3] : F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 0\n abs_negative_user_demand[UserDemand #13] : F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_negative_user_demand[UserDemand #6] : F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 1.5\n abs_positive_basin[Basin #2] : -F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_positive_basin[Basin #5] : -F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n abs_positive_basin[Basin #12] : -F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0\n abs_negative_basin[Basin #2] : F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_negative_basin[Basin #5] : F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n abs_negative_basin[Basin #12] : F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0",
+ "text": "5.1 Example\nThe following is an example of an optimization problem for the example shown here:\n\n\nCode\nusing Ribasim\nusing Ribasim: NodeID\nusing SQLite\nusing ComponentArrays: ComponentVector\n\ntoml_path = normpath(@__DIR__, \"../../generated_testmodels/allocation_example/ribasim.toml\")\np = Ribasim.Model(toml_path).integrator.p\nu = ComponentVector(; storage = zeros(length(p.basin.node_id)))\n\nallocation_model = p.allocation.allocation_models[1]\nt = 0.0\npriority_idx = 1\n\nRibasim.set_flow!(p.graph, NodeID(:FlowBoundary, 1), NodeID(:Basin, 2), 1.0)\n\nRibasim.adjust_source_capacities!(allocation_model, p, priority_idx)\nRibasim.adjust_edge_capacities!(allocation_model, p, priority_idx)\nRibasim.set_objective_priority!(allocation_model, p, u, t, priority_idx)\n\nprintln(p.allocation.allocation_models[1].problem)\n\n\nMin F_abs_user_demand[UserDemand #3] + F_abs_user_demand[UserDemand #6] + F_abs_user_demand[UserDemand #13] + F_abs_basin[Basin #12] + F_abs_basin[Basin #2] + F_abs_basin[Basin #5]\nSubject to\n flow_conservation[Basin #12] : -F[(TabulatedRatingCurve #7, Basin #12)] + F[(Basin #12, UserDemand #13)] + F_basin_in[Basin #12] - F_basin_out[Basin #12] = 0\n flow_conservation[Basin #2] : -F[(Basin #5, Basin #2)] + F[(Basin #2, UserDemand #3)] - F[(FlowBoundary #1, Basin #2)] + F[(Basin #2, Basin #5)] + F_basin_in[Basin #2] - F_basin_out[Basin #2] = 0\n flow_conservation[Basin #5] : F[(Basin #5, Basin #2)] + F[(Basin #5, TabulatedRatingCurve #7)] + F[(Basin #5, UserDemand #6)] - F[(Basin #2, Basin #5)] + F_basin_in[Basin #5] - F_basin_out[Basin #5] = 0\n abs_positive_user_demand[UserDemand #3] : -F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ -1.5\n abs_positive_user_demand[UserDemand #6] : -F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 0\n abs_positive_user_demand[UserDemand #13] : -F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_negative_user_demand[UserDemand #3] : F[(Basin #2, UserDemand #3)] + F_abs_user_demand[UserDemand #3] ≥ 1.5\n abs_negative_user_demand[UserDemand #6] : F[(Basin #5, UserDemand #6)] + F_abs_user_demand[UserDemand #6] ≥ 0\n abs_negative_user_demand[UserDemand #13] : F[(Basin #12, UserDemand #13)] + F_abs_user_demand[UserDemand #13] ≥ 0\n abs_positive_basin[Basin #12] : -F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0\n abs_positive_basin[Basin #2] : -F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_positive_basin[Basin #5] : -F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n abs_negative_basin[Basin #12] : F_basin_in[Basin #12] + F_abs_basin[Basin #12] ≥ 0\n abs_negative_basin[Basin #2] : F_basin_in[Basin #2] + F_abs_basin[Basin #2] ≥ 0\n abs_negative_basin[Basin #5] : F_basin_in[Basin #5] + F_abs_basin[Basin #5] ≥ 0\n source[(FlowBoundary #1, Basin #2)] : F[(FlowBoundary #1, Basin #2)] ≤ 1\n return_flow[UserDemand #13] : F[(UserDemand #13, Terminal #10)] ≤ 0\n fractional_flow[(TabulatedRatingCurve #7, Basin #12)] : F[(TabulatedRatingCurve #7, Basin #12)] - 0.4 F[(Basin #5, TabulatedRatingCurve #7)] ≤ 0\n basin_outflow[Basin #12] : F_basin_out[Basin #12] ≤ 0\n basin_outflow[Basin #2] : F_basin_out[Basin #2] ≤ 0\n basin_outflow[Basin #5] : F_basin_out[Basin #5] ≤ 0\n F[(UserDemand #13, Terminal #10)] ≥ 0\n F[(TabulatedRatingCurve #7, Basin #12)] ≥ 0\n F[(Basin #5, Basin #2)] ≥ 0\n F[(Basin #5, TabulatedRatingCurve #7)] ≥ 0\n F[(Basin #5, UserDemand #6)] ≥ 0\n F[(Basin #2, UserDemand #3)] ≥ 0\n F[(TabulatedRatingCurve #7, Terminal #10)] ≥ 0\n F[(FlowBoundary #1, Basin #2)] ≥ 0\n F[(Basin #12, UserDemand #13)] ≥ 0\n F[(Basin #2, Basin #5)] ≥ 0\n F_basin_in[Basin #12] ≥ 0\n F_basin_in[Basin #2] ≥ 0\n F_basin_in[Basin #5] ≥ 0\n F_basin_out[Basin #12] ≥ 0\n F_basin_out[Basin #2] ≥ 0\n F_basin_out[Basin #5] ≥ 0",
"crumbs": [
"Julia core",
"Allocation"