from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
import plotly.express as px
from ribasim import Model, Node
from ribasim.nodes import (
basin,
flow_boundary,
tabulated_rating_curve,
user_demand,
)from shapely.geometry import Point
Reservoir
= Path("crystal-basin")
base_dir
= "2022-01-01"
starttime = "2023-01-01"
endtime = Model(
model =starttime,
starttime=endtime,
endtime="EPSG:4326",
crs )
These nodes are identical to the previous tutorial:
# FlowBoundary
= pd.DataFrame({
data "time": pd.date_range(start="2022-01-01", end="2023-01-01", freq="MS"),
"main": [74.7, 57.9, 63.2, 183.9, 91.8, 47.5, 32.6, 27.6, 26.5, 25.1, 39.3, 37.8, 57.9],
"minor": [16.3, 3.8, 3.0, 37.6, 18.2, 11.1, 12.9, 12.2, 11.2, 10.8, 15.1, 14.3, 11.8]
# fmt: skip
}) "total"] = data["minor"] + data["main"]
data[= model.flow_boundary.add(
main 1, Point(0.0, 0.0), name="main"),
Node(
[
flow_boundary.Time(=data.time,
time=data.main,
flow_rate
)
],
)= model.flow_boundary.add(
minor 2, Point(-3.0, 0.0), name="minor"),
Node(
[
flow_boundary.Time(=data.time,
time=data.minor,
flow_rate
)
],
)
# Basin
= model.basin.add(
confluence 3, Point(-1.5, -1), name="confluence"),
Node(
[=[672000, 5600000], level=[0, 6]),
basin.Profile(area=[4]),
basin.State(level=[starttime, endtime]),
basin.Time(time
],
)
# TabulatedRatingCurve
= model.tabulated_rating_curve.add(
weir 4, Point(-1.5, -1.5), name="weir"),
Node(
[
tabulated_rating_curve.Static(=[0.0, 2, 5],
level=[0.0, 50, 200],
flow_rate
)
],
)= model.tabulated_rating_curve.add(
diversion_weir 8, Point(-1.125, -0.75), name="diversion_weir"),
Node(
[
tabulated_rating_curve.Static(=[0.0, 1.5, 5],
level=[0.0, 45, 200],
flow_rate
)
],
)
# UserDemand
= model.user_demand.add(
irrigation 7, Point(-1.5, 0.5), name="irrigation"),
Node(
[
user_demand.Time(=[0.0, 0.0, 10, 12, 12, 0.0],
demand=0,
return_factor=0,
min_level=1,
priority=[
time
starttime,"2022-03-31",
"2022-04-01",
"2022-07-01",
"2022-09-30",
"2022-10-01",
],
)
],
)
# Terminal
= model.terminal.add(Node(5, Point(-1.5, -3.0), name="sea")) sea
Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See Figure 1). The reservoir is to help ensure a reliable supply during dry periods. In this module, the user will update the model to incorporate the reservoir’s impact on the whole Crystal basin.
1 Reservoir
1.1 Add a Basin
The diversion_basin
from the previous tutorial is not used, but replaced by a larger reservoir
Basin. Its water will play an important role for the users (the city and the irrigation district). The reservoir has a maximum area of \(32.3 \text{ km}^2\) and a maximum depth of \(7 \text{ m}\).
= model.basin.add(
reservoir 6, Point(-0.75, -0.5), name="reservoir"),
Node(
[=[20000000, 32300000], level=[0, 7]),
basin.Profile(area=[3.5]),
basin.State(level=[starttime, endtime]),
basin.Time(time
], )
1.2 Add a demand node
\(50.000\) people live in Crystal City. To represents the total flow rate or abstraction rate required to meet the water demand of \(50.000\) people, another demand node needs to be added assuming a return flow of \(60%\).
= model.user_demand.add(
city 9, Point(0, -1), name="city"),
Node(
[
user_demand.Time(# Total demand in m³/s
=[0.07, 0.08, 0.09, 0.10, 0.12, 0.14, 0.15, 0.14, 0.12, 0.10, 0.09, 0.08],
demand=0.6,
return_factor=0,
min_level=1,
priority=pd.date_range(start="2022-01-01", periods=12, freq="MS"),
time
)
],# fmt: skip )
="main")
model.edge.add(main, reservoir, name="minor")
model.edge.add(minor, confluence, name="irrigation")
model.edge.add(reservoir, irrigation, name
model.edge.add(irrigation, confluence)="city")
model.edge.add(reservoir, city, name="city returnflow")
model.edge.add(city, confluence, name="not diverted")
model.edge.add(reservoir, diversion_weir, name
model.edge.add(diversion_weir, confluence)
model.edge.add(confluence, weir)="sea") model.edge.add(weir, sea, name
; model.plot()
= base_dir / "Crystal-3/ribasim.toml"
toml_path
model.write(toml_path)= "ribasim" cli_path
1.3 Adjust the code
Adjust the naming of the Basin in the dictionary mapping and the saving file should be Crystal-3
.
2 Plot reservoir storage and level
= pd.read_feather(
df_basin / "Crystal-3/results/basin.arrow", dtype_backend="pyarrow"
base_dir
)
# Create pivot tables and plot for Basin data
= df_basin.pivot_table(
df_basin_wide ="time", columns="node_id", values=["storage", "level"]
index
)= df_basin_wide.loc[:, pd.IndexSlice[:, reservoir.node_id]]
df_basin_wide
# Plot level and storage on the same graph with dual y-axes
= plt.subplots(figsize=(12, 6))
fig, ax1
# Plot level on the primary y-axis
= "b"
color "Time")
ax1.set_xlabel("Level [m]", color=color)
ax1.set_ylabel("level"], color=color)
ax1.plot(df_basin_wide.index, df_basin_wide[="y", labelcolor=color)
ax1.tick_params(axis
# Create a secondary y-axis for storage
= ax1.twinx()
ax2 = "r"
color "Storage [m³]", color="r")
ax2.set_ylabel("storage"], linestyle="--", color=color)
ax2.plot(df_basin_wide.index, df_basin_wide[="y", labelcolor=color)
ax2.tick_params(axis
# Adjust layout to fit labels
fig.tight_layout() "Basin level and storage")
plt.title( plt.show()
The figure above illustrates the storage and water level at the reservoir. As expected, after increasing the profile of the Basin, its storage capacity increased as well.
3 Plot flows
= pd.read_feather(
df_flow / "Crystal-3/results/flow.arrow", dtype_backend="pyarrow"
base_dir
)# Add the edge names and then remove unnamed edges
"name"] = model.edge.df["name"].loc[df_flow["edge_id"]].to_numpy()
df_flow[= df_flow[df_flow["name"].astype(bool)]
df_flow
# Plot the flow data, interactive plot with Plotly
= df_flow.pivot_table(
pivot_flow ="time", columns="name", values="flow_rate"
index
).reset_index()= px.line(pivot_flow, x="time", y=pivot_flow.columns[1:], title="Flow [m3/s]")
fig
="Edge")
fig.update_layout(legend_title_text fig.show()