from ribasim import run_ribasimExamples
1 Basic model with static forcing
import shutil
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from ribasim import Allocation, Model, Node, Solver
from ribasim.config import Experimental
from ribasim.nodes import (
basin,
continuous_control,
discrete_control,
flow_boundary,
level_boundary,
level_demand,
linear_resistance,
manning_resistance,
outlet,
pid_control,
pump,
tabulated_rating_curve,
user_demand,
)
from shapely.geometry import Pointdatadir = Path("data")
shutil.rmtree(datadir, ignore_errors=True)model = Model(starttime="2020-01-01", endtime="2021-01-01", crs="EPSG:4326")Setup the basins:
time = pd.date_range(model.starttime, model.endtime)
day_of_year = time.day_of_year.to_numpy()
seconds_per_day = 24 * 60 * 60
evaporation = (
(-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day
)
rng = np.random.default_rng(seed=0)
precipitation = (
rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day
)
# Convert steady forcing to m/s
# 2 mm/d precipitation, 1 mm/d evaporation
basin_data = [
basin.Profile(area=[0.01, 1000.0], level=[0.0, 1.0]),
basin.Time(
time=pd.date_range(model.starttime, model.endtime),
drainage=0.0,
potential_evaporation=evaporation,
infiltration=0.0,
precipitation=precipitation,
),
basin.State(level=[1.4]),
]
basin1 = model.basin.add(Node(1, Point(0.0, 0.0)), basin_data)
basin3 = model.basin.add(Node(3, Point(2.0, 0.0)), basin_data)
basin6 = model.basin.add(Node(6, Point(3.0, 2.0)), basin_data)
basin9 = model.basin.add(Node(9, Point(5.0, 0.0)), basin_data)Setup linear resistance:
linear_resistance10 = model.linear_resistance.add(
Node(10, Point(6.0, 0.0)),
[linear_resistance.Static(resistance=[5e3])],
)
linear_resistance12 = model.linear_resistance.add(
Node(12, Point(2.0, 1.0)),
[linear_resistance.Static(resistance=[3600.0 * 24.0 / 100.0])],
)Setup Manning resistance:
manning_resistance2 = model.manning_resistance.add(
Node(2, Point(1.0, 0.0)),
[
manning_resistance.Static(
length=[900], manning_n=[0.04], profile_width=[6.0], profile_slope=[3.0]
)
],
)Set up rating curve nodes:
q = 10 / 86400 # 10 m³/day
tabulated_rating_curve4 = model.tabulated_rating_curve.add(
Node(8, Point(3.0, -1.0)),
[
tabulated_rating_curve.Static(
level=[0.0, 1.0],
flow_rate=[0.0, 0.6 * q],
)
],
)
tabulated_rating_curve5 = model.tabulated_rating_curve.add(
Node(5, Point(3.0, 1.0)),
[
tabulated_rating_curve.Static(
level=[0.0, 1.0],
flow_rate=[0.0, 0.3 * q],
)
],
)
tabulated_rating_curve8 = model.tabulated_rating_curve.add(
Node(4, Point(4.0, 0.0)),
[
tabulated_rating_curve.Static(
level=[0.0, 1.0],
flow_rate=[0.0, 0.1 * q],
)
],
)Setup pump:
pump7 = model.pump.add(Node(7, Point(4.0, 1.0)), [pump.Static(flow_rate=[0.5 / 3600])])Setup level boundary:
level_boundary11 = model.level_boundary.add(
Node(11, Point(2.0, 2.0)), [level_boundary.Static(level=[0.5])]
)
level_boundary17 = model.level_boundary.add(
Node(17, Point(6.0, 1.0)), [level_boundary.Static(level=[1.5])]
)Setup flow boundary:
flow_boundary15 = model.flow_boundary.add(
Node(15, Point(3.0, 3.0)), [flow_boundary.Static(flow_rate=[1e-4])]
)
flow_boundary16 = model.flow_boundary.add(
Node(16, Point(0.0, 1.0)), [flow_boundary.Static(flow_rate=[1e-4])]
)Setup terminal:
terminal14 = model.terminal.add(Node(14, Point(3.0, -2.0)))Setup the links:
model.link.add(basin1, manning_resistance2)
model.link.add(manning_resistance2, basin3)
model.link.add(
basin3,
tabulated_rating_curve8,
)
model.link.add(
basin3,
tabulated_rating_curve5,
)
model.link.add(
basin3,
tabulated_rating_curve4,
)
model.link.add(tabulated_rating_curve5, basin6)
model.link.add(tabulated_rating_curve8, basin9)
model.link.add(
tabulated_rating_curve4,
terminal14,
)
model.link.add(basin6, pump7)
model.link.add(pump7, basin9)
model.link.add(basin9, linear_resistance10)
model.link.add(level_boundary11, linear_resistance12)
model.link.add(linear_resistance12, basin3)
model.link.add(flow_boundary15, basin6)
model.link.add(flow_boundary16, basin1)
model.link.add(linear_resistance10, level_boundary17)Let’s take a look at the model:
model.plot()
Write the model to a TOML and GeoPackage:
toml_path = datadir / "basic/ribasim.toml"
model.write(toml_path)PosixPath('data/basic/ribasim.toml')
1.1 Running a model
Now run the model:
run_ribasim(toml_path)┌ Info: Starting a Ribasim simulation.
│ toml_path = "data/basic/ribasim.toml"
│ cli.ribasim_version = "2025.6.0"
│ starttime = 2020-01-01T00:00:00
│ endtime = 2021-01-01T00:00:00
└ threads = 1
Simulating 0%| | ETA: N/A
Simulating 2%|▊ | ETA: 0:28:07
Simulating 4%|█▌ | ETA: 0:14:03
Simulating 6%|██▍ | ETA: 0:09:08
Simulating 8%|███▍ | ETA: 0:06:10
Simulating 11%|████▌ | ETA: 0:04:27
Simulating 14%|█████▋ | ETA: 0:03:24
Simulating 18%|███████ | ETA: 0:02:39
Simulating 21%|████████▌ | ETA: 0:02:04
Simulating 25%|██████████▏ | ETA: 0:01:39
Simulating 30%|████████████ | ETA: 0:01:18
Simulating 36%|██████████████▌ | ETA: 0:00:59
Simulating 47%|██████████████████▉ | ETA: 0:00:38
Simulating 53%|█████████████████████▏ | ETA: 0:00:30
Simulating 63%|█████████████████████████▍ | ETA: 0:00:19
Simulating 77%|██████████████████████████████▊ | ETA: 0:00:10
Simulating 92%|█████████████████████████████████████ | ETA: 0:00:03
Simulating 100%|████████████████████████████████████████| Time: 0:00:33
[ Info: Computation time: 16 seconds, 399 milliseconds
[ Info: The model finished successfully.
You can also open a terminal and run it from there. For example, to run the basic model, input:
ribasim basic/ribasim.tomlAfter running the model, read back the results:
df_basin = pd.read_feather(datadir / "basic/results/basin.arrow")
df_basin_wide = df_basin.pivot_table(
index="time", columns="node_id", values=["storage", "level"]
)
ax = df_basin_wide["level"].plot()
ax.set_ylabel("level [m]");
df_flow = pd.read_feather(datadir / "basic/results/flow.arrow")
df_flow["link"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
df_flow["flow_m3d"] = df_flow.flow_rate * 86400
ax = df_flow.pivot_table(index="time", columns="link", values="flow_m3d").plot()
ax.legend(bbox_to_anchor=(1.3, 1), title="Link")
ax.set_ylabel("flow [m³day⁻¹]");
2 Model with discrete control
The model constructed below consists of a single basin which slowly drains through a TabulatedRatingCurve, but is held within a range by two connected pumps. These two pumps together 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.
Setup the basins:
model = Model(
starttime="2020-01-01",
endtime="2021-01-01",
crs="EPSG:4326",
solver=Solver(abstol=1e-6, reltol=1e-5),
)model.basin.add(
Node(1, Point(0.0, 0.0)),
[
basin.Profile(area=[1000.0, 1000.0], level=[0.0, 1.0]),
basin.State(level=[20.0]),
basin.Time(time=["2020-01-01", "2020-07-01"], precipitation=[0.0, 3e-6]),
],
)Basin #1
Setup the discrete control:
model.discrete_control.add(
Node(7, Point(1.0, 0.0)),
[
discrete_control.Variable(
compound_variable_id=1,
listen_node_id=1,
variable=["level"],
),
discrete_control.Condition(
compound_variable_id=1,
condition_id=[1, 2],
# min, max
threshold_high=[5.0, 15.0],
),
discrete_control.Logic(
truth_state=["FF", "TF", "TT"],
control_state=["in", "none", "out"],
),
],
)DiscreteControl #7
The above control logic can be summarized as follows:
- If the level is above the maximum, activate the control state “out”;
- If the level is below the minimum, active the control state “in”;
- Otherwise activate the control state “none”.
Setup the pump:
model.pump.add(
Node(2, Point(1.0, 1.0)),
[pump.Static(control_state=["none", "in", "out"], flow_rate=[0.0, 2e-3, 0.0])],
)
model.pump.add(
Node(3, Point(1.0, -1.0)),
[pump.Static(control_state=["none", "in", "out"], flow_rate=[0.0, 0.0, 2e-3])],
)Pump #3
The pump data defines the following:
| Control state | Pump #2 flow rate (m/s) | Pump #3 flow rate (m/s) |
|---|---|---|
| “none” | 0.0 | 0.0 |
| “in” | 2e-3 | 0.0 |
| “out” | 0.0 | 2e-3 |
Setup the level boundary:
model.level_boundary.add(
Node(4, Point(2.0, 0.0)), [level_boundary.Static(level=[10.0])]
)LevelBoundary #4
Setup the rating curve:
model.tabulated_rating_curve.add(
Node(5, Point(-1.0, 0.0)),
[tabulated_rating_curve.Static(level=[2.0, 15.0], flow_rate=[0.0, 2e-3])],
)TabulatedRatingCurve #5
Setup the terminal:
model.terminal.add(Node(6, Point(-2.0, 0.0)))Terminal #6
Setup links:
model.link.add(model.basin[1], model.pump[3])
model.link.add(model.pump[3], model.level_boundary[4])
model.link.add(model.level_boundary[4], model.pump[2])
model.link.add(model.pump[2], model.basin[1])
model.link.add(model.basin[1], model.tabulated_rating_curve[5])
model.link.add(model.tabulated_rating_curve[5], model.terminal[6])
model.link.add(model.discrete_control[7], model.pump[2])
model.link.add(model.discrete_control[7], model.pump[3])Let’s take a look at the model:
model.plot()
Listen links are plotted with a dashed line since they are not present in the “Link / static” schema but only in the “Control / condition” schema.
datadir = Path("data")
model.write(datadir / "level_range/ribasim.toml")PosixPath('data/level_range/ribasim.toml')
Now run the model.
run_ribasim(datadir / "level_range/ribasim.toml")┌ Info: Starting a Ribasim simulation.
│ toml_path = "data/level_range/ribasim.toml"
│ cli.ribasim_version = "2025.6.0"
│ starttime = 2020-01-01T00:00:00
│ endtime = 2021-01-01T00:00:00
└ threads = 1
Simulating 0%| | ETA: N/A
Simulating 13%|█████▎ | ETA: 0:00:00
Simulating 40%|████████████████▏ | ETA: 0:00:00
Simulating 65%|██████████████████████████ | ETA: 0:00:00
Simulating 66%|██████████████████████████▍ | ETA: 0:00:00
Simulating 66%|██████████████████████████▌ | ETA: 0:00:00
Simulating 66%|██████████████████████████▌ | ETA: 0:00:00
Simulating 66%|██████████████████████████▌ | ETA: 0:00:00
Simulating 66%|██████████████████████████▌ | ETA: 0:00:00
Simulating 66%|██████████████████████████▌ | ETA: 0:00:00
Simulating 66%|██████████████████████████▌ | ETA: 0:00:00
Simulating 66%|██████████████████████████▋ | ETA: 0:00:00
Simulating 66%|██████████████████████████▋ | ETA: 0:00:00
Simulating 66%|██████████████████████████▋ | ETA: 0:00:00
Simulating 66%|██████████████████████████▋ | ETA: 0:00:00
Simulating 66%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▋ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▊ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|██████████████████████████▉ | ETA: 0:00:00
Simulating 67%|███████████████████████████ | ETA: 0:00:00
Simulating 67%|███████████████████████████ | ETA: 0:00:00
Simulating 67%|███████████████████████████ | ETA: 0:00:00
Simulating 67%|███████████████████████████ | ETA: 0:00:00
Simulating 67%|███████████████████████████ | ETA: 0:00:00
Simulating 68%|███████████████████████████ | ETA: 0:00:00
Simulating 68%|███████████████████████████ | ETA: 0:00:00
Simulating 68%|███████████████████████████ | ETA: 0:00:00
Simulating 68%|███████████████████████████ | ETA: 0:00:00
Simulating 68%|███████████████████████████ | ETA: 0:00:00
Simulating 68%|███████████████████████████ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▏ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▎ | ETA: 0:00:00
Simulating 68%|███████████████████████████▍ | ETA: 0:00:00
Simulating 68%|███████████████████████████▍ | ETA: 0:00:00
Simulating 68%|███████████████████████████▍ | ETA: 0:00:00
Simulating 68%|███████████████████████████▍ | ETA: 0:00:00
Simulating 68%|███████████████████████████▍ | ETA: 0:00:00
Simulating 69%|███████████████████████████▍ | ETA: 0:00:00
Simulating 69%|███████████████████████████▍ | ETA: 0:00:00
Simulating 69%|███████████████████████████▌ | ETA: 0:00:00
Simulating 69%|███████████████████████████▌ | ETA: 0:00:00
Simulating 69%|███████████████████████████▌ | ETA: 0:00:00
Simulating 69%|███████████████████████████▌ | ETA: 0:00:00
Simulating 69%|███████████████████████████▌ | ETA: 0:00:00
Simulating 69%|███████████████████████████▌ | ETA: 0:00:00
Simulating 69%|███████████████████████████▌ | ETA: 0:00:00
Simulating 69%|███████████████████████████▋ | ETA: 0:00:00
Simulating 69%|███████████████████████████▋ | ETA: 0:00:00
Simulating 69%|███████████████████████████▋ | ETA: 0:00:00
Simulating 69%|███████████████████████████▋ | ETA: 0:00:00
Simulating 69%|███████████████████████████▋ | ETA: 0:00:00
Simulating 69%|███████████████████████████▋ | ETA: 0:00:00
Simulating 69%|███████████████████████████▋ | ETA: 0:00:00
Simulating 69%|███████████████████████████▊ | ETA: 0:00:00
Simulating 69%|███████████████████████████▊ | ETA: 0:00:00
Simulating 69%|███████████████████████████▊ | ETA: 0:00:00
Simulating 69%|███████████████████████████▊ | ETA: 0:00:00
Simulating 69%|███████████████████████████▊ | ETA: 0:00:00
Simulating 69%|███████████████████████████▊ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 70%|████████████████████████████ | ETA: 0:00:00
Simulating 70%|████████████████████████████ | ETA: 0:00:00
Simulating 70%|████████████████████████████ | ETA: 0:00:00
Simulating 70%|████████████████████████████ | ETA: 0:00:00
Simulating 70%|████████████████████████████ | ETA: 0:00:00
Simulating 70%|████████████████████████████ | ETA: 0:00:00
Simulating 70%|████████████████████████████▏ | ETA: 0:00:00
Simulating 70%|████████████████████████████▏ | ETA: 0:00:00
Simulating 70%|████████████████████████████▏ | ETA: 0:00:00
Simulating 70%|████████████████████████████▏ | ETA: 0:00:00
Simulating 70%|████████████████████████████▏ | ETA: 0:00:00
Simulating 70%|████████████████████████████▏ | ETA: 0:00:00
Simulating 70%|████████████████████████████▏ | ETA: 0:00:00
Simulating 71%|████████████████████████████▎ | ETA: 0:00:00
Simulating 71%|████████████████████████████▎ | ETA: 0:00:00
Simulating 71%|████████████████████████████▎ | ETA: 0:00:00
Simulating 71%|████████████████████████████▎ | ETA: 0:00:00
Simulating 71%|████████████████████████████▎ | ETA: 0:00:00
Simulating 71%|████████████████████████████▎ | ETA: 0:00:00
Simulating 71%|████████████████████████████▍ | ETA: 0:00:00
Simulating 71%|████████████████████████████▍ | ETA: 0:00:00
Simulating 71%|████████████████████████████▍ | ETA: 0:00:00
Simulating 71%|████████████████████████████▍ | ETA: 0:00:00
Simulating 71%|████████████████████████████▍ | ETA: 0:00:00
Simulating 71%|████████████████████████████▍ | ETA: 0:00:00
Simulating 71%|████████████████████████████▌ | ETA: 0:00:00
Simulating 71%|████████████████████████████▌ | ETA: 0:00:00
Simulating 71%|████████████████████████████▌ | ETA: 0:00:00
Simulating 71%|████████████████████████████▌ | ETA: 0:00:00
Simulating 71%|████████████████████████████▌ | ETA: 0:00:00
Simulating 71%|████████████████████████████▌ | ETA: 0:00:00
Simulating 71%|████████████████████████████▋ | ETA: 0:00:00
Simulating 71%|████████████████████████████▋ | ETA: 0:00:00
Simulating 72%|████████████████████████████▋ | ETA: 0:00:00
Simulating 72%|████████████████████████████▋ | ETA: 0:00:00
Simulating 72%|████████████████████████████▋ | ETA: 0:00:00
Simulating 72%|████████████████████████████▋ | ETA: 0:00:00
Simulating 72%|████████████████████████████▊ | ETA: 0:00:00
Simulating 72%|████████████████████████████▊ | ETA: 0:00:00
Simulating 72%|████████████████████████████▊ | ETA: 0:00:00
Simulating 72%|████████████████████████████▊ | ETA: 0:00:00
Simulating 72%|████████████████████████████▊ | ETA: 0:00:00
Simulating 72%|████████████████████████████▉ | ETA: 0:00:00
Simulating 72%|████████████████████████████▉ | ETA: 0:00:00
Simulating 72%|████████████████████████████▉ | ETA: 0:00:00
Simulating 72%|████████████████████████████▉ | ETA: 0:00:00
Simulating 72%|████████████████████████████▉ | ETA: 0:00:00
Simulating 72%|████████████████████████████▉ | ETA: 0:00:00
Simulating 72%|█████████████████████████████ | ETA: 0:00:00
Simulating 72%|█████████████████████████████ | ETA: 0:00:00
Simulating 72%|█████████████████████████████ | ETA: 0:00:00
Simulating 73%|█████████████████████████████ | ETA: 0:00:00
Simulating 73%|█████████████████████████████ | ETA: 0:00:00
Simulating 73%|█████████████████████████████ | ETA: 0:00:00
Simulating 73%|█████████████████████████████ | ETA: 0:00:00
Simulating 73%|█████████████████████████████ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▏ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▏ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▏ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▏ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▏ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▎ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▎ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▎ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▎ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▎ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▍ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▍ | ETA: 0:00:00
Simulating 73%|█████████████████████████████▍ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▍ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▍ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▍ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▌ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▌ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▌ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▌ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▌ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▌ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▌ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▋ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▋ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▋ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▋ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▋ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▊ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▊ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▊ | ETA: 0:00:00
Simulating 74%|█████████████████████████████▊ | ETA: 0:00:00
Simulating 75%|█████████████████████████████▉ | ETA: 0:00:00
Simulating 75%|█████████████████████████████▉ | ETA: 0:00:00
Simulating 75%|█████████████████████████████▉ | ETA: 0:00:00
Simulating 75%|█████████████████████████████▉ | ETA: 0:00:00
Simulating 75%|█████████████████████████████▉ | ETA: 0:00:00
Simulating 75%|██████████████████████████████ | ETA: 0:00:00
Simulating 75%|██████████████████████████████ | ETA: 0:00:00
Simulating 75%|██████████████████████████████ | ETA: 0:00:00
Simulating 75%|██████████████████████████████ | ETA: 0:00:00
Simulating 75%|██████████████████████████████ | ETA: 0:00:00
Simulating 75%|██████████████████████████████ | ETA: 0:00:00
Simulating 75%|██████████████████████████████ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 75%|██████████████████████████████▏ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▎ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▎ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▎ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▎ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▎ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▎ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▍ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▍ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▍ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▍ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▌ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▌ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▌ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▌ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▌ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▌ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▊ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▊ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▊ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▊ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▊ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▉ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▉ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▉ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▉ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▉ | ETA: 0:00:00
Simulating 77%|███████████████████████████████ | ETA: 0:00:00
Simulating 77%|███████████████████████████████ | ETA: 0:00:00
Simulating 78%|███████████████████████████████ | ETA: 0:00:00
Simulating 78%|███████████████████████████████ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▏ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▏ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▏ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▏ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▏ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▏ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▏ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▎ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▎ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▎ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▎ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▎ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▍ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▍ | ETA: 0:00:00
Simulating 78%|███████████████████████████████▍ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▍ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▌ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▌ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▌ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▌ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▋ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▋ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▋ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▋ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▊ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▊ | ETA: 0:00:00
Simulating 79%|███████████████████████████████▊ | ETA: 0:00:00
Simulating 80%|███████████████████████████████▊ | ETA: 0:00:00
Simulating 80%|███████████████████████████████▉ | ETA: 0:00:00
Simulating 80%|███████████████████████████████▉ | ETA: 0:00:00
Simulating 80%|███████████████████████████████▉ | ETA: 0:00:00
Simulating 80%|███████████████████████████████▉ | ETA: 0:00:00
Simulating 80%|███████████████████████████████▉ | ETA: 0:00:00
Simulating 80%|████████████████████████████████ | ETA: 0:00:00
Simulating 80%|████████████████████████████████ | ETA: 0:00:00
Simulating 80%|████████████████████████████████ | ETA: 0:00:00
Simulating 80%|████████████████████████████████ | ETA: 0:00:00
Simulating 80%|████████████████████████████████ | ETA: 0:00:00
Simulating 80%|████████████████████████████████ | ETA: 0:00:00
Simulating 80%|████████████████████████████████▏ | ETA: 0:00:00
Simulating 80%|████████████████████████████████▏ | ETA: 0:00:00
Simulating 80%|████████████████████████████████▏ | ETA: 0:00:00
Simulating 80%|████████████████████████████████▏ | ETA: 0:00:00
Simulating 80%|████████████████████████████████▏ | ETA: 0:00:00
Simulating 80%|████████████████████████████████▏ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▎ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▎ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▎ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▎ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▍ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▍ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▍ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▍ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▌ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▌ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▌ | ETA: 0:00:00
Simulating 81%|████████████████████████████████▋ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▋ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▋ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▋ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▋ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▊ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▊ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▊ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▊ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▉ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▉ | ETA: 0:00:00
Simulating 82%|████████████████████████████████▉ | ETA: 0:00:00
Simulating 82%|█████████████████████████████████ | ETA: 0:00:00
Simulating 82%|█████████████████████████████████ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▏ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▏ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▏ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▎ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▎ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▎ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▎ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▍ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▍ | ETA: 0:00:00
Simulating 83%|█████████████████████████████████▍ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▍ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▌ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▌ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▋ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▋ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▋ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▋ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▊ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▊ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▊ | ETA: 0:00:00
Simulating 84%|█████████████████████████████████▊ | ETA: 0:00:00
Simulating 85%|█████████████████████████████████▊ | ETA: 0:00:00
Simulating 85%|█████████████████████████████████▉ | ETA: 0:00:00
Simulating 85%|█████████████████████████████████▉ | ETA: 0:00:00
Simulating 85%|█████████████████████████████████▉ | ETA: 0:00:00
Simulating 85%|█████████████████████████████████▉ | ETA: 0:00:00
Simulating 85%|██████████████████████████████████ | ETA: 0:00:00
Simulating 85%|██████████████████████████████████ | ETA: 0:00:00
Simulating 85%|██████████████████████████████████ | ETA: 0:00:00
Simulating 85%|██████████████████████████████████ | ETA: 0:00:00
Simulating 85%|██████████████████████████████████▏ | ETA: 0:00:00
Simulating 85%|██████████████████████████████████▏ | ETA: 0:00:00
Simulating 85%|██████████████████████████████████▏ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▎ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▎ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▎ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▍ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▍ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▍ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▌ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▌ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▌ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▌ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▋ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▋ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▋ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▊ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▊ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▊ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▊ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▉ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▉ | ETA: 0:00:00
Simulating 87%|███████████████████████████████████ | ETA: 0:00:00
Simulating 87%|███████████████████████████████████ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████▏ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████▏ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████▏ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████▎ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████▎ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████▍ | ETA: 0:00:00
Simulating 88%|███████████████████████████████████▍ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▍ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▌ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▌ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▌ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▌ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▋ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▋ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▋ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▋ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▊ | ETA: 0:00:00
Simulating 89%|███████████████████████████████████▊ | ETA: 0:00:00
Simulating 90%|███████████████████████████████████▉ | ETA: 0:00:00
Simulating 90%|███████████████████████████████████▉ | ETA: 0:00:00
Simulating 90%|███████████████████████████████████▉ | ETA: 0:00:00
Simulating 90%|███████████████████████████████████▉ | ETA: 0:00:00
Simulating 90%|████████████████████████████████████ | ETA: 0:00:00
Simulating 90%|████████████████████████████████████ | ETA: 0:00:00
Simulating 90%|████████████████████████████████████ | ETA: 0:00:00
Simulating 90%|████████████████████████████████████▏ | ETA: 0:00:00
Simulating 90%|████████████████████████████████████▏ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▎ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▎ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▍ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▍ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▍ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▌ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▌ | ETA: 0:00:00
Simulating 91%|████████████████████████████████████▋ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▋ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▋ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▊ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▊ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▊ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▉ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▉ | ETA: 0:00:00
Simulating 92%|████████████████████████████████████▉ | ETA: 0:00:00
Simulating 92%|█████████████████████████████████████ | ETA: 0:00:00
Simulating 92%|█████████████████████████████████████ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▏ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▏ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▏ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▎ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▎ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▎ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▎ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▍ | ETA: 0:00:00
Simulating 93%|█████████████████████████████████████▍ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▍ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▌ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▌ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▌ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▋ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▋ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▋ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▊ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▊ | ETA: 0:00:00
Simulating 94%|█████████████████████████████████████▊ | ETA: 0:00:00
Simulating 95%|█████████████████████████████████████▉ | ETA: 0:00:00
Simulating 95%|█████████████████████████████████████▉ | ETA: 0:00:00
Simulating 95%|█████████████████████████████████████▉ | ETA: 0:00:00
Simulating 95%|█████████████████████████████████████▉ | ETA: 0:00:00
Simulating 95%|██████████████████████████████████████ | ETA: 0:00:00
Simulating 95%|██████████████████████████████████████ | ETA: 0:00:00
Simulating 95%|██████████████████████████████████████▏ | ETA: 0:00:00
Simulating 95%|██████████████████████████████████████▏ | ETA: 0:00:00
Simulating 95%|██████████████████████████████████████▏ | ETA: 0:00:00
Simulating 95%|██████████████████████████████████████▏ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▎ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▎ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▍ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▍ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▌ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▌ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▋ | ETA: 0:00:00
Simulating 97%|██████████████████████████████████████▋ | ETA: 0:00:00
Simulating 97%|██████████████████████████████████████▋ | ETA: 0:00:00
Simulating 97%|██████████████████████████████████████▊ | ETA: 0:00:00
Simulating 97%|██████████████████████████████████████▊ | ETA: 0:00:00
Simulating 97%|██████████████████████████████████████▉ | ETA: 0:00:00
Simulating 97%|██████████████████████████████████████▉ | ETA: 0:00:00
Simulating 97%|███████████████████████████████████████ | ETA: 0:00:00
Simulating 98%|███████████████████████████████████████ | ETA: 0:00:00
Simulating 98%|███████████████████████████████████████ | ETA: 0:00:00
Simulating 98%|███████████████████████████████████████▏| ETA: 0:00:00
Simulating 98%|███████████████████████████████████████▏| ETA: 0:00:00
Simulating 98%|███████████████████████████████████████▎| ETA: 0:00:00
Simulating 98%|███████████████████████████████████████▎| ETA: 0:00:00
Simulating 98%|███████████████████████████████████████▍| ETA: 0:00:00
Simulating 99%|███████████████████████████████████████▍| ETA: 0:00:00
Simulating 99%|███████████████████████████████████████▌| ETA: 0:00:00
Simulating 99%|███████████████████████████████████████▌| ETA: 0:00:00
Simulating 99%|███████████████████████████████████████▋| ETA: 0:00:00
Simulating 99%|███████████████████████████████████████▋| ETA: 0:00:00
Simulating 99%|███████████████████████████████████████▊| ETA: 0:00:00
Simulating 99%|███████████████████████████████████████▊| ETA: 0:00:00
Simulating 100%|███████████████████████████████████████▉| ETA: 0:00:00
Simulating 100%|███████████████████████████████████████▉| ETA: 0:00:00
Simulating 100%|████████████████████████████████████████| ETA: 0:00:00
Simulating 100%|████████████████████████████████████████| Time: 0:00:01
[ Info: Computation time: 1 second, 683 milliseconds
[ Info: The model finished successfully.
After running the model, read back the results:
df_basin = pd.read_feather(datadir / "level_range/results/basin.arrow")
df_basin_wide = df_basin.pivot_table(
index="time", columns="node_id", values=["storage", "level"]
)
ax = df_basin_wide["level"].plot()
threshold_high = model.discrete_control.condition.df.threshold_high
ax.hlines(
threshold_high,
df_basin.time[0],
df_basin.time.max(),
lw=1,
ls="--",
color="k",
)
ax.set_yticks(threshold_high, ["min", "max"])
ax.set_ylabel("level")
plt.show()
We see that in January the level of the basin is too high and thus water is pumped out until the maximum level of the desired range is reached. Then until May water flows out of the basin freely through the tabulated rating curve until the minimum level is reached. From this point until the start of July water is pumped into the basin in short bursts to stay within the desired range. At the start of July rain starts falling on the basin, which causes the basin level to rise until the maximum level. From this point onward water is pumped out of the basin in short bursts to stay within the desired range.
3 Model with PID control
Set up the model:
model = Model(starttime="2020-01-01", endtime="2020-12-01", crs="EPSG:4326")Setup the basins:
model.basin.add(
Node(2, Point(1.0, 0.0)),
[basin.Profile(area=[1000.0, 1000.0], level=[0.0, 1.0]), basin.State(level=[6.0])],
)Basin #2
Setup the pump:
model.pump.add(
Node(3, Point(2.0, 0.5)),
[pump.Static(flow_rate=[0.0])], # Will be overwritten by PID controller
)Pump #3
Setup the outlet:
model.outlet.add(
Node(6, Point(2.0, -0.5)),
[outlet.Static(flow_rate=[0.0])], # Will be overwritten by PID controller
)Outlet #6
Setup flow boundary:
model.flow_boundary.add(
Node(1, Point(0.0, 0.0)),
[flow_boundary.Static(flow_rate=[1e-3])],
)FlowBoundary #1
Setup level boundary:
model.level_boundary.add(
Node(4, Point(3.0, 0.0)),
[level_boundary.Static(level=[5.0])],
)LevelBoundary #4
Setup PID control:
for node, proportional, integral in [
(Node(5, Point(1.5, 1.0)), -1e-3, -1e-7),
(Node(7, Point(1.5, -1.0)), 1e-3, 1e-7),
]:
pid_control_data = [
pid_control.Time(
time=[
"2020-01-01",
"2020-05-01",
"2020-07-01",
"2020-12-01",
],
listen_node_id=2,
target=[5.0, 5.0, 7.5, 7.5],
proportional=proportional,
integral=integral,
derivative=0.0,
)
]
model.pid_control.add(node, pid_control_data)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 the links:
model.link.add(model.flow_boundary[1], model.basin[2])
model.link.add(model.basin[2], model.pump[3])
model.link.add(model.pump[3], model.level_boundary[4])
model.link.add(model.level_boundary[4], model.outlet[6])
model.link.add(model.outlet[6], model.basin[2])
model.link.add(model.pid_control[5], model.pump[3])
model.link.add(model.pid_control[7], model.outlet[6])Let’s take a look at the model:
model.plot()
Write the model to a TOML and GeoPackage:
datadir = Path("data")
model.write(datadir / "pid_control/ribasim.toml")PosixPath('data/pid_control/ribasim.toml')
Now run the model.
run_ribasim(datadir / "pid_control/ribasim.toml")┌ Info: Starting a Ribasim simulation.
│ toml_path = "data/pid_control/ribasim.toml"
│ cli.ribasim_version = "2025.6.0"
│ starttime = 2020-01-01T00:00:00
│ endtime = 2020-12-01T00:00:00
└ threads = 1
Simulating 0%| | ETA: N/A
Simulating 1%|▌ | ETA: 0:00:00
Simulating 3%|█▎ | ETA: 0:00:00
Simulating 5%|██▏ | ETA: 0:00:00
Simulating 8%|███▏ | ETA: 0:00:00
Simulating 10%|████ | ETA: 0:00:00
Simulating 14%|█████▊ | ETA: 0:00:00
Simulating 18%|███████▎ | ETA: 0:00:00
Simulating 23%|█████████▏ | ETA: 0:00:00
Simulating 29%|███████████▌ | ETA: 0:00:00
Simulating 35%|██████████████▏ | ETA: 0:00:00
Simulating 39%|███████████████▋ | ETA: 0:00:00
Simulating 44%|█████████████████▌ | ETA: 0:00:00
Simulating 50%|███████████████████▉ | ETA: 0:00:00
Simulating 55%|██████████████████████▏ | ETA: 0:00:00
Simulating 61%|████████████████████████▎ | ETA: 0:00:00
Simulating 65%|██████████████████████████▏ | ETA: 0:00:00
Simulating 70%|███████████████████████████▉ | ETA: 0:00:00
Simulating 77%|██████████████████████████████▋ | ETA: 0:00:00
Simulating 86%|██████████████████████████████████▍ | ETA: 0:00:00
Simulating 97%|██████████████████████████████████████▋ | ETA: 0:00:00
Simulating 100%|████████████████████████████████████████| Time: 0:00:00
[ Info: Computation time: 81 milliseconds
[ Info: The model finished successfully.
After running the model, read back the results:
from matplotlib.dates import date2num
df_basin = pd.read_feather(datadir / "pid_control/results/basin.arrow")
df_basin_wide = df_basin.pivot_table(
index="time", columns="node_id", values=["storage", "level"]
)
ax = df_basin_wide["level"].plot()
ax.set_ylabel("level [m]")
# Plot target level
level_demands = model.pid_control.time.df.target.to_numpy()[:4]
times = date2num(model.pid_control.time.df.time)[:4]
ax.plot(times, level_demands, color="k", ls=":", label="target level")
pass
4 Guidance of modeling a cascade of polder basins
Situation description: This example shows how to make a model for a given practical water system, which consists of a cascade of level control polder basins with inlet and outlet to the main systems. Note that alternative model layouts are feasible for the same water system, each having its positive items and drawbacks.
The polder system is composed of a sequence of level controlled polder basins with weirs inbetween each basin and an inlet and outlet to main system
model = Model(starttime="2020-01-01", endtime="2021-01-01", crs="EPSG:28992")All the polder basins are exposed to time varying forcings (precipitation, evaporation, drainage, infiltration) to mimic situations of water excess and water shortage.
In case of water excess, a pump in the most downstream polder will need to pump the surplus water to the main water system. In case of water shortage, an inlet at the most upstream polder will need to bring water into the cascase of polders. The main water system acts as a water source.
Model approach: All polder basins as well as the main water system are modelled with basin nodes. To let the system experience all 4 excess/shortage situation, forcing time series are made in a way that is adapting to them. Overall, assume that in one year, the system will experience precipitation (situation 1) in winter and early spring, precipitation shortage (situation 2) from late spring until early autumn. During situation 2, polder basin 4 will experience additional seepage (compoensating its shortage), and later polder basin 3 will also receive more seepage.
Setting up the basins:
time = pd.date_range(model.starttime, model.endtime)
day_of_year = time.day_of_year.to_numpy()
precipitation = np.zeros(day_of_year.size)
precipitation[0:90] = 1.72e-8
precipitation[330:366] = 1.72e-8
evaporation = np.zeros(day_of_year.size)
evaporation[130:270] = 2.87e-8
drainage = np.zeros(day_of_year.size)
drainage[120:270] = 0.4 * 2.87e-8
drainage_3 = drainage.copy()
drainage_3[210:240] = 17 * 2.87e-8
drainage_4 = drainage.copy()
drainage_4[160:240] = 13 * 2.87e-8
infiltration = np.zeros(day_of_year.size)
infiltration[0:90] = 5e-8
polder_profile = basin.Profile(area=[100, 100], level=[0.0, 3.0])
basin_time = [
basin.Time(
time=pd.date_range(model.starttime, model.endtime),
drainage=drainage,
potential_evaporation=evaporation,
infiltration=0.0,
precipitation=precipitation,
),
]
basin_time4 = [
basin.Time(
time=pd.date_range(model.starttime, model.endtime),
drainage=drainage_4,
potential_evaporation=evaporation,
infiltration=0.0,
precipitation=precipitation,
),
]
basin_time3 = [
basin.Time(
time=pd.date_range(model.starttime, model.endtime),
drainage=drainage_3,
potential_evaporation=evaporation,
infiltration=0.0,
precipitation=precipitation,
),
]
model.basin.add(
Node(1, Point(2.0, 0.0)),
[
basin.State(level=[2.5]),
basin.Profile(area=[1000, 1000], level=[0.0, 3.0]),
basin.Time(
time=pd.date_range(model.starttime, model.endtime),
drainage=0.0,
potential_evaporation=0.0,
infiltration=0.0,
precipitation=0.0,
),
],
)
model.basin.add(
Node(4, Point(0.0, -2.0)),
[basin.State(level=[1.5]), polder_profile, *basin_time],
)
model.basin.add(
Node(6, Point(0.0, -4.0)),
[basin.State(level=[1.0]), polder_profile, *basin_time],
)
model.basin.add(
Node(8, Point(2.0, -4.0)),
[basin.State(level=[1.5]), polder_profile, *basin_time3],
)
model.basin.add(
Node(10, Point(4.0, -4.0)),
[basin.State(level=[1.3]), polder_profile, *basin_time4],
)
model.basin.add(
Node(12, Point(4.0, -2.0)),
[basin.State(level=[0.1]), polder_profile, *basin_time],
)Basin #12
After all the basins are defined the connecting component inbetween the basins needs to be determined. For polder basin 5 (node 12), the water level needs to be maintain at 0.0 meter. This means that either there should be no water in this basin, or the basin bottom is lower than the reference level, and the water level should be maintained at the reference level.
Since the water level of the main system is at 2.5 meter above the reference level a pump is needed to remove the water from polder basin 5.
Setup the pumps:
model.pump.add(
Node(13, Point(4.0, -1.0)),
[pump.Static(flow_rate=[0.5 / 3600])],
)Pump #13
According to the description of situation 1 and 2, the water in one polder basin needs to be able to flow to the downstream basin if the current basin has too much water (i.e. the water level is above the setpoint) or if the downstream basin is below setpoint and needs more water. This could be modelled with an uncontrolled TabulatedRatingCurve node with Q=0 at the setpoint level (and Q rising when the level rises above setpoint) , or with an Outlet node where the min_upstream_level is specified at or just below the setpoint. In this example, we’ve chosen for the Outlet where we specify the minimum upstream level 5 cm below the setpoint. For example: the Outlet of polder basin 1 (node 4) is specified with a minimum upstream level of 1.95 meter.
Setup the outlets:
# Set up outlet
model.outlet.add(
Node(2, Point(0.0, -1.0)),
[outlet.Static(flow_rate=[2 * 0.5 / 3600], min_upstream_level=[0.0])],
)
model.outlet.add(
Node(5, Point(0.0, -3.0)),
[outlet.Static(flow_rate=[0.5 / 3600], min_upstream_level=[1.95])],
)
model.outlet.add(
Node(7, Point(1.0, -4.0)),
[outlet.Static(flow_rate=[0.5 / 3600], min_upstream_level=[1.45])],
)
model.outlet.add(
Node(9, Point(3.0, -4.0)),
[outlet.Static(flow_rate=[0.5 / 3600], min_upstream_level=[0.95])],
)
model.outlet.add(
Node(11, Point(4.0, -3.0)),
[outlet.Static(flow_rate=[0.5 / 3600], min_upstream_level=[0.45])],
)Outlet #11
When using Outlets as connecting nodes, the flow over the Outlet needs to be controlled to maintain the water level at the setpoint. For this purpose we introduce local PidControllers, where the targets of the PidControllers are set to the setpoints. Disadvantage of this local control approach is the delay that is introduced to transport the ‘basin X has a shortage’ message upstream through the cascade to the inlet. Current functionality does not offer the capability for PidControl to take multiple observations into account when controlling the inlet. Combining multiple observations in one control is feasible with DiscreteControl. This could be an alternative approach to controlling the inlet for the cascading water system.
Setup the PID control:
pid_control_data = {
"proportional": [0.05],
"integral": [0.00],
"derivative": [0.0],
}
model.pid_control.add(
Node(3, Point(-1.0, -1.0)),
[pid_control.Static(listen_node_id=[4], target=[2.0], **pid_control_data)],
)
model.pid_control.add(
Node(14, Point(-1.0, -3.0)),
[pid_control.Static(listen_node_id=[6], target=[1.5], **pid_control_data)],
)
model.pid_control.add(
Node(15, Point(1.0, -3.0)),
[pid_control.Static(listen_node_id=[8], target=[1.0], **pid_control_data)],
)
model.pid_control.add(
Node(16, Point(3.0, -3.0)),
[pid_control.Static(listen_node_id=[10], target=[0.5], **pid_control_data)],
)PidControl #16
Setup the links:
model.link.add(model.basin[1], model.outlet[2])
model.link.add(model.pid_control[3], model.outlet[2])
model.link.add(model.outlet[2], model.basin[4])
model.link.add(model.basin[4], model.outlet[5])
model.link.add(model.outlet[5], model.basin[6])
model.link.add(model.basin[6], model.outlet[7])
model.link.add(model.outlet[7], model.basin[8])
model.link.add(model.basin[8], model.outlet[9])
model.link.add(model.outlet[9], model.basin[10])
model.link.add(model.basin[10], model.outlet[11])
model.link.add(model.outlet[11], model.basin[12])
model.link.add(model.basin[12], model.pump[13])
model.link.add(model.pump[13], model.basin[1])
model.link.add(model.pid_control[14], model.outlet[5])
model.link.add(model.pid_control[15], model.outlet[7])
model.link.add(model.pid_control[16], model.outlet[9])To plot the model
model.plot()
Write the model to a TOML file and run it in the Julia.
datadir = Path("data")
model.write(datadir / "local_pidcontrolled_cascade/ribasim.toml")PosixPath('data/local_pidcontrolled_cascade/ribasim.toml')
Now run the model.
run_ribasim(datadir / "local_pidcontrolled_cascade/ribasim.toml")┌ Info: Starting a Ribasim simulation.
│ toml_path = "data/local_pidcontrolled_cascade/ribasim.toml"
│ cli.ribasim_version = "2025.6.0"
│ starttime = 2020-01-01T00:00:00
│ endtime = 2021-01-01T00:00:00
└ threads = 1
Simulating 0%| | ETA: N/A
Simulating 0%| | ETA: 0:00:14
Simulating 2%|▊ | ETA: 0:00:01
Simulating 3%|█▎ | ETA: 0:00:01
Simulating 28%|███████████ | ETA: 0:00:00
Simulating 51%|████████████████████▌ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▌ | ETA: 0:00:00
Simulating 100%|████████████████████████████████████████| Time: 0:00:00
[ Info: Computation time: 44 milliseconds
[ Info: The model finished successfully.
After running the model, read back the result to plot the flow of each polder basin.
datadir_flow = datadir / "local_pidcontrolled_cascade/results/flow.arrow"
df_flow = pd.read_feather(datadir_flow)
df_flow["link"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
df_flow["flow_m3d"] = df_flow.flow_rate * 86400
df_pivot = df_flow.pivot_table(index="time", columns="link", values="flow_m3d")Below graphs show the flow exchanged with the mainsystem (i.e. the inlet and the pump), and the flow of weirs inbetween the polder basins.
df_input = df_pivot.loc[:, [(1, 2), (13, 1)]]
ax = df_input.plot(ylim=[-1.0, 20.0])
ax.set_ylabel("flow [m³day⁻¹]")
df_weirs = df_pivot.loc[:, [(4, 5), (6, 7), (8, 9), (10, 11)]]
ax = df_weirs.plot(ylim=[-1.0, 15.0])
ax.set_ylabel("flow [m³day⁻¹]");

Below graph shows the vertical flux on each basin.
datadir_basin = datadir / "local_pidcontrolled_cascade/results/basin.arrow"
df_basin = pd.read_feather(datadir_basin)
df_basin["vertical_flux"] = (
df_basin["precipitation"]
- df_basin["evaporation"]
+ df_basin["drainage"]
+ df_basin["infiltration"]
)
df_basin_wide = df_basin.pivot_table(
index="time", columns="node_id", values=["storage", "level", "vertical_flux"]
)
df_basin_wide["vertical_flux"] *= 86400
ax = df_basin_wide["vertical_flux"].plot()
ax.set_ylabel("vertical flux [m³day⁻¹]");
In the following graph, the water level of basins are shown. The five polder basins are given starting levels that are different from their setpoints. It can be observed that in the beginning, the water level are changing and approaching to the set points. Later when the water levels are stable, they will not be affected by the forcing.
ax = df_basin_wide["level"].plot()
ax.set_ylabel("level [m]");
5 Model with continuous control
model = Model(starttime="2020-01-01", endtime="2021-01-01", crs="EPSG:28992")Set up the transient level boundary:
model.level_boundary.add(
Node(1, Point(0, 0)),
[
level_boundary.Time(
time=pd.date_range(
start="2020-01-01", end="2021-01-01", periods=100, unit="ms"
),
level=6.0 + np.sin(np.linspace(0, 6 * np.pi, 100)),
)
],
)LevelBoundary #1
Set up the linear resistance:
model.linear_resistance.add(
Node(2, Point(1, 0)), [linear_resistance.Static(resistance=[10.0])]
)LinearResistance #2
Set up the basin:
model.basin.add(
Node(3, Point(2, 0)),
[
basin.Profile(area=10000.0, level=[0.0, 1.0]),
basin.State(level=[10.0]),
],
)Basin #3
Set up the outlets:
model.outlet.add(Node(4, Point(3, 1)), [outlet.Static(flow_rate=[1.0])])
model.outlet.add(Node(5, Point(3, -1)), [outlet.Static(flow_rate=[1.0])])Outlet #5
Set up the terminals:
model.terminal.add(Node(6, Point(4, 1)))
model.terminal.add(Node(7, Point(4, -1)))Terminal #7
Set up the continuous control:
model.continuous_control.add(
Node(8, Point(2, 1)),
[
continuous_control.Variable(
listen_node_id=[2],
variable="flow_rate",
),
continuous_control.Function(
input=[0.0, 1.0],
output=[0.0, 0.6],
controlled_variable="flow_rate",
),
],
)
model.continuous_control.add(
Node(9, Point(2, -1)),
[
continuous_control.Variable(
listen_node_id=[2],
variable="flow_rate",
),
continuous_control.Function(
input=[0.0, 1.0],
output=[0.0, 0.4],
controlled_variable="flow_rate",
),
],
)ContinuousControl #9
This defines:
- A
ContinuousControlnode with ID 1, which listens to the flow rate of theLinearResistancenode with ID 1, puts that through the function \(f(x) = \max(0, 0.6x)\), and assigns the result to the flow rate of the node thisContinuousControlnode is controlling, which is defined by a (control) link; - A
ContinuousControlnode with ID 2, which listens to the flow rate of theLinearResistancenode with ID 1, puts that through the function \(f(x) = \max(0, 0.4x)\), and assigns the result to the flow rate of the node thisContinuousControlnode is controlling, which is defined by a (control) link.
model.link.add(model.level_boundary[1], model.linear_resistance[2])
model.link.add(model.linear_resistance[2], model.basin[3])
model.link.add(model.basin[3], model.outlet[4])
model.link.add(model.basin[3], model.outlet[5])
model.link.add(model.outlet[4], model.terminal[6])
model.link.add(model.outlet[5], model.terminal[7])
# Define which node is controlled by each continuous control node
model.link.add(model.continuous_control[8], model.outlet[4])
model.link.add(model.continuous_control[9], model.outlet[5])Let’s take a look at the model:
model.plot()
With this setup we want to split the flow coming into the basin into a 60% - 40% ratio.
Write the model to a TOML and GeoPackage:
toml_path = datadir / "outlet_continuous_control/ribasim.toml"
model.write(toml_path)PosixPath('data/outlet_continuous_control/ribasim.toml')
Now run the model.
run_ribasim(toml_path)┌ Info: Starting a Ribasim simulation.
│ toml_path = "data/outlet_continuous_control/ribasim.toml"
│ cli.ribasim_version = "2025.6.0"
│ starttime = 2020-01-01T00:00:00
│ endtime = 2021-01-01T00:00:00
└ threads = 1
Simulating 0%| | ETA: N/A
Simulating 2%|▊ | ETA: 0:00:00
Simulating 11%|████▌ | ETA: 0:00:00
Simulating 19%|███████▋ | ETA: 0:00:00
Simulating 25%|██████████▏ | ETA: 0:00:00
Simulating 35%|██████████████ | ETA: 0:00:00
Simulating 45%|██████████████████▏ | ETA: 0:00:00
Simulating 56%|██████████████████████▎ | ETA: 0:00:00
Simulating 65%|██████████████████████████ | ETA: 0:00:00
Simulating 76%|██████████████████████████████▍ | ETA: 0:00:00
Simulating 87%|██████████████████████████████████▊ | ETA: 0:00:00
Simulating 96%|██████████████████████████████████████▋ | ETA: 0:00:00
Simulating 100%|████████████████████████████████████████| Time: 0:00:00
[ Info: Computation time: 61 milliseconds
[ Info: The model finished successfully.
After running the model, read back the results:
df_flow = pd.read_feather(datadir / "outlet_continuous_control/results/flow.arrow")
fig, ax = plt.subplots()
def plot_link_flow(from_node_type, from_node_id, to_node_type, to_node_id):
df_flow_filtered = df_flow[
(df_flow["from_node_id"] == from_node_id)
& (df_flow["to_node_id"] == to_node_id)
]
df_flow_filtered.plot(
x="time",
y="flow_rate",
ax=ax,
label=f"{from_node_type} #{from_node_id} → {to_node_type} #{to_node_id}",
)
plot_link_flow("LinearResistance", 1, "Basin", 1)
plot_link_flow("Basin", 1, "Outlet", 1)
plot_link_flow("Basin", 1, "Outlet", 2)
ax.set_ylabel("flow [m³s⁻¹]");