Quick start guide
1 Introduction
Welcome to Ribasim! This guide will help you get started with the basics of installing and using Ribasim for river basin simulation. In this guide, the schematization of models will be implemented in Python using the Ribasim Python package. The Ribasim package (named ribasim
) simplifies the process of building, updating, and analyzing Ribasim model programmatically. It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible. This package is available on PyPI.
1.1 Learning objectives
In this guide, we will focus on a fictional river basin called Crystal, which will serve as our case study. The guide is divided into different modules, each covering various scenarios. These include simulating natural flow, implementing reservoirs, and observing the impact of other structures. While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations. By the end of the guide, users will be able to:
- Set Up a Basic Ribasim Model: Understand how to create a new model for a river basin using the Ribasim Python package.
- Evaluate the Impact of Demands: Introduce water demand (such as irrigation) and assess their effects on the river basin.
- Modify and Update Models: Learn how to update existing models with new data and changes.
- Analyze Simulation Results: Use built-in tools to analyze and interpret the results of your simulations.
2 Starting RIBASIM
2.1 System requirements
Before installing Ribasim, ensure your system meets the following requirements:
- Operating System: Windows 10 or later, or Linux (latest distributions)
- Processor: x86-64 (64-bit)
- RAM: 4 GB minimum, 8 GB recommended
- Hard Drive: 1GB of free space
2.2 Installation
- Download Ribasim: Obtain the Ribasim 9 installation package from the official website: Ribasim - Installation under chapter ‘2 Download’:
- For Windows download:
ribasim_windows.zip
- For Linux:
ribasim_linux.zip
- Unpack the
.zip
archive: It is important to keep the contents of the zip file organized within a single directory. The Ribasim executable can be found in the directory; - Check installation: To check whether the installation was performed successfully, in
cmd
go to the executable path and typeribasim
with no arguments in the command line. This will give the following message:
error: the following required arguments were not provided:
<TOML_PATH>
Usage: ribasim <TOML_PATH>
For more information, try '--help'.
- We use a command line interface (CLI) to install our Ribasim packages. To install Ribasim open PowerShell or Windows command prompt and write:
conda install ribasim
or
mamba install ribasim
2.3 Data preparation
Download the Crystal_Basin.zip
file from the website. Extract Crystal_Basin.zip
and place it in the same directory as your Ribasim installation. This folder includes:
QuickStartGuide.pdf
data
: Contains data inputs such as time series needed for running the case. Additionally, your Python model (.py
) and eventually the output files will also be saved in this folder.
3 Modual 1 - Crystal River Basin
We will examine a straightforward example of the Crystal River Basin, which includes a main river and a single tributary flowing into the sea (see Figure 1). An average discharge of \(44.45 \text{ m}^3/\text{s}\) is measured at the confluence. In this module, the basin is free of any activities, allowing the model to simulate the natural flow. The next step is to include a demand (irrigation) that taps from a canal out of the main river.
After this module the user will be able to:
- Build a river basin model from scratch
- Understand the functionality of the ‘demand’ and ‘basin’ nodes
- Generate overview of results
- Evaluate the simulation results
3.1 Modual 1.1 - Natural Flow
3.1.1 Step 1: Import packages
Before building the model we need to import some modules. Open your python platform (Spyder, VS code etc.), created a new file and name it Crystal_1.1
and save it into your model folder Crystal_Basin
. Import the following modules in python:
import shutil
from pathlib import Path
import pathlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from ribasim import Allocation, Model, Node # The main library used for river basin modeling.
from ribasim.nodes import (
flow_boundary,
basin,
tabulated_rating_curve,
terminal
)from shapely.geometry import Point
import subprocess # For running the model
3.1.2 Step 2: Setup paths and model configuration
Reference the paths of the Ribasim installation and model directory and define the time period (2022-01-01 until 2023-01-01) for the model simulation:
= Path("c:/Ribasim")
base_dir = base_dir / "Crystal_Basin"
model_dir = model_dir / "data/input/ACTINFLW.csv"
data_path
= "2022-01-01"
starttime = "2023-01-01"
endtime = Model(
model =starttime,
starttime=endtime,
endtime="EPSG:4326",
crs )
3.1.3 Step 3: Flow boundary nodes
Crystal Basin consists of two inflow points, the tributary and the main Crystal river, we will call them Minor
and Main
respectively. In order to define the time series flow rate (\(\text{m}^3/\text{s}\)) we read the discharge data from ACTINFLW.csv
. This inflow data goes monthly from 2014 to 2023. However, for this exercise actual runtime is already defined in step 2.
= pd.read_csv(data_path, sep=";")
data 'sum']= data['minor']+data['main']
data[#Average inflow and max. of the whole summed inflow data timeseries
#From 2014 - 2023
print('Average inflowQ m3/s:',data['sum'].mean())
print('Average inflowQ m3/s:',data['sum'].max)
model.flow_boundary.add(1, Point(0.0, 0.0), name='Main'),
Node(=data.time, flow_rate=data.main,
[flow_boundary.Time(time
)]
)
model.flow_boundary.add(2, Point(-3.0, 0.0), name='Minor'),
Node(=data.time, flow_rate=data.minor,
[flow_boundary.Time(time
)] )
3.1.4 Step 4: Basin node (confluence)
To schematize the confluence from the tributary we will use the Basin node. The node by itself portrays as a bucket with a certain volume of water and can be used for different purposes, such as a reservoir, a lake or in this case a confluence. Figure 2 visualizes a cross section of the confluence point in our model.
Table 1 shows the input data for the basin node profile.
Area [\(\text{m}^2\)] | Level [\(\text{m}\)] |
---|---|
\(672000.0\) | \(0.0\) |
\(5600000.0\) | \(6.0\) |
To specify the basin profile, the following code is used:
model.basin.add(3, Point(-1.5, -1), name='Conf'),
Node(=[672000, 5600000], level=[0, 6]),
[basin.Profile(area=[4]),
basin.State(level=[starttime, endtime]),
basin.Time(time
], )
3.1.5 Step 5: Tabulated rating curve
In the previous step we implemented a Basin node that functions as a confluence. Conceptually, the basin acts like a bucket of water, accumulating inflows and then releasing them. However, the model does not run if the basin is directly connected to the terminal node. This is because, for the model to function properly, we need to define a relation between the water level (\(h\)) in the basin and the outflow (\(Q\)) from the basin. This relation is defined by the Tabulated Rating Curve
and thus serves as a critical component. This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.
As the two inflows come together at the confluence, we expect, as mentioned and coded before, a discharge average of \(44.45 \text{ m}^3/\text{s}\). It is therefore expected that the confluence basin reaches a level where the outflow is equal to the inflow via the rating curve. Only then is the confluence basin in equilibrium. To ensure that inflow equals outflow (IN=OUT) and keeping in mind the maximum depth of the river is \(6 \text{ m}\), the \(Q(h)\) relationship in Table 2 will be used as input.
Water Level (\(h\)) [\(\text{m}\)] | Outflow (\(Q\)) [\(\text{m}^3/\text{s}\)] |
---|---|
\(0.0\) | \(0.0\) |
\(2.0\) | \(50.0\) |
\(5.0\) | \(200.0\) |
In Ribasim, the \(Q(h)\) relation is a linear function, so the points in between will be linearly interpolated. Figure 3 illustrates the visual process and shows a progressive increase in discharge with rising water levels. In this case this means:
- At level \(0.0\): No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.
- At level \(2.0\): Discharge is max. \(50.0 \text{ m}^3/\text{s}\). This is a bit above the average discharge rate, corresponding to the water level where normal flow conditions are established.
- At level \(5.0\): Discharge rate reaches \(200.0 \text{ m}^3/\text{s}\). This discharge rate occurs at the water level during wet periods, indicating higher flow capacity.
Taking this into account, add the Tabulated Rating Curve
as follows:
model.tabulated_rating_curve.add(4, Point(-1.5, -1.5), name='MainConf'),
Node(
[tabulated_rating_curve.Static(=[0.0, 2, 5],
level=[0.0, 50, 200],
flow_rate
)
] )
3.1.6 Step 6: Terminal node
Finally all the water will discharge into the ocean. Schematize this with the terminal node as it portrays the end point of the model. Besides the node number/name and location, no further input is needed.
5, Point(-1.5, -3.0), name="Terminal")) model.terminal.add(Node(
3.1.7 Step 7: Defining edges
Implement the connections (edges) between the nodes, in the following order:
- Flow boundaries to the basin;
- Basin to the rating curve;
- Tabulated rating curve to the terminal.
1], model.basin[3])
model.edge.add(model.flow_boundary[2], model.basin[3])
model.edge.add(model.flow_boundary[3], model.tabulated_rating_curve[4])
model.edge.add(model.basin[4], model.terminal[5]) model.edge.add(model.tabulated_rating_curve[
3.1.8 Step 8: Visualization and model execution
Plot the schematization, write the model configuration to the TOML
file. Name the output file Crystal_1.1/ribasim.toml
:
model.plot()
= model_dir/ "Crystal_1.1/ribasim.toml"
toml_path
model.write(toml_path)= base_dir / "ribasim_windows/ribasim.exe" rib_path
The schematization should look like Figure 4.
After writing model.write a subfolder Crystal_1.1
is created, which contains the model input data and configuration:
- ribasim.toml: The model configuration
- database.gpkg: A geopackage containing the shapes of your schematization and the input data of the nodes used.
Now run the model:
=True) subprocess.run([rib_path, toml_path], check
3.1.9 Step 9: Post-processing results
Read the arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:
= pd.read_feather(model_dir / "Crystal_1.1/results/basin.arrow")
df_basin
# Create pivot tables and plot for basin data
= df_basin.pivot_table(
df_basin_wide ="time", columns="node_id", values=["storage", "level"]
index
)
# Skip the first timestep as it’s the initialization step
= df_basin_wide.iloc[1:]
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 Over Time')
plt.title(
plt.show()
# Plot flow data
# Read the data from feather format
= pd.read_feather(model_dir / "Crystal_1.1/results/flow.arrow")
df_flow # Create 'edge' and 'flow_m3d' columns
"edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
df_flow[
# Create a pivot table
= df_flow.pivot_table(index="time", columns="edge", values="flow_rate")
pivot_flow
# Skip the first timestep
= pivot_flow.iloc[1:]
pivot_flow
= ['-', '--', '-', '-.']
line_styles = len(line_styles)
num_styles
= plt.subplots(figsize=(12, 6))
fig, ax for i, column in enumerate(pivot_flow.columns):
=ax, linestyle=line_styles[i % num_styles],linewidth=1.5, alpha=0.8)
pivot_flow[column].plot(ax
# Set labels and title
'Time')
ax.set_xlabel('Flow [m³/s]')
ax.set_ylabel(=(1.15, 1), title="Edge")
ax.legend(bbox_to_anchor'Flow Over Time')
plt.title(True)
plt.grid( plt.show()
Figure 5 shows the storage and levels in the basin node.
In this configuration the basin node is designed to ensure that inflow equals outflow, effectively simulating a controlled junction where water flow is managed rather than stored. To accurately represent the relationship between water levels and discharge rates at this confluence, a rating curve node is implemented. This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence. Since the basin node is functioning as a confluence rather than a storage reservoir, the simulated water levels and storage trends will closely follow the inflow patterns. This is because there is no net change in storage; all incoming water is balanced by outgoing flow.
Figure 6 shows the discharges in \(\text{m}^3/\text{s}\) on each edge. Edge (3,4) represents the flow from the confluence to the tabulated rating curve and edge (4,5) represents the flow from the tabulated rating curve to the terminal. Both show the same discharge over time. Which is expected in a natural flow environment, as what is coming into the confluence must come out.
3.2 Modual 1.2 - Irrigation demand
Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation. In a conventional irrigation setup, some water is diverted from the Main River through a canal, with a portion of it eventually returning to the main river (see Figure 7).
For this update schematization, we need to incorporate three additional nodes:
- Basin: Represents a cross-sectional point where water is diverted.
- User Demand: Represents the irrigation demand.
- Tabulates Rating Curve: Defines the remaining water flow from the main river at the diversion point.
3.2.1 Step 1: Setup the model & adjust Import Packages
Copy and paste the python script Crystal_1.1
and rename it Crystal_1.2
. De import modules remain the same, except a demand needs to be added and if you want to have a more interactive plot then importing plotly
can be useful.
import shutilfrom
import pathlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from ribasim import Allocation, Model, Node # The main library used for river basin modeling.
from ribasim.nodes import (
flow_boundary,
basin,
tabulated_rating_curve,
user_demand,
terminal
)from shapely.geometry import Point
import subprocess # For running the model
import plotly.express as px
3.2.2 Step 2: Add a second Basin node
Schematically we are dealing with two basins. To keep the order of flow from upstream to downstream it is recommended to adjust the node_id numbers accordingly. In this case node_id = 3
will be node_id = 4
. Basin 3 will portray as the point in the river where the diversion takes place, getting the name Div
. Its profile area at this intersection is slightly smaller than at the confluence.
model.basin.add(3, Point(-0.75, -0.5), name='Div'),
Node(=[500000, 5000000], level=[0, 6]),
[basin.Profile(area=[3]),
basin.State(level=[starttime, endtime]),
basin.Time(time
],
)
model.basin.add(4, Point(-1.5, -1), name='Conf'),
Node(=[672000, 5600000], level=[0, 6]),
[basin.Profile(area=[4]),
basin.State(level=[starttime, endtime]),
basin.Time(time
], )
3.2.3 Step 3: Add the irrigation demand
A big farm company needs to apply irrigation to its field starting from April to September. The irrigated field is \(> 17000 \text{ ha}\) and requires around \(5 \text{ mm/day}\). In this case the farm company diverts from the main river an average flow rate of \(10 \text{ m}^3/\text{s}\) and \(12 \text{ m}^3/\text{s}\) during spring and summer, respectively. Start of irrigation takes place on the 1st of April until the end of August. The farmer taps water through a canal (demand).
For now, let’s assume the return flow remains \(0.0\) (return_factor
). Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river. The user demand node interpolates the demand values. Thus the following code needs to be implemented:
model.user_demand.add(6, Point(-1.5, 1.0), name='IrrA'),
Node(
[user_demand.Time(=[0.0, 0.0, 10, 12, 12, 0.0],
demand=0,
return_factor=0,
min_level=1,
priority=[starttime, "2022-03-31", "2022-04-01", "2022-07-01", "2022-09-30", "2022-10-01"]
time
)
] )
3.2.4 Step 4: Add a tabulated rating curve
The second Tabulated Rating Curve node will simulate the rest of the water that is left after diverting a part from the main river to the farm field. The rest of the water will flow naturally towards the confluence:
model.tabulated_rating_curve.add(7, Point(-1.125, -0.75), name='MainDiv'),
Node(
[tabulated_rating_curve.Static(=[0.0, 1.5, 5],
level=[0.0, 45, 200],
flow_rate
)
] )
It is up to the user to renumber the ID’s of the nodes. Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.
3.2.5 Step 5: Adjust the terminal node id and edges
Adjust the terminal node id. Since we added more nodes we have more edges. Add and adjust the edges:
8, Point(-1.5, -3.0), name="Terminal"))
model.terminal.add(Node(
1], model.basin[3])
model.edge.add(model.flow_boundary[2], model.basin[4])
model.edge.add(model.flow_boundary[3], model.user_demand[6])
model.edge.add(model.basin[6], model.basin[4])
model.edge.add(model.user_demand[3], model.tabulated_rating_curve[7])
model.edge.add(model.basin[7], model.basin[4])
model.edge.add(model.tabulated_rating_curve[4], model.tabulated_rating_curve[5])
model.edge.add(model.basin[5], model.terminal[8]) model.edge.add(model.tabulated_rating_curve[
3.2.6 Step 6: Plot model and run
Plot the schematization and run the model. This time the new outputs should be written in a new folder called Crystal_1.2
:
model.plot()
= model_dir/ "Crystal_1.2/ribasim.toml"
toml_path
model.write(toml_path)= base_dir / "ribasim_windows/ribasim.exe"
rib_path
=True) subprocess.run([rib_path, toml_path], check
The schematization should look like Figure 8.
3.2.7 Step 7: Name the edges and basins
The names of each nodes are defined and saved in the geopackage. However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe.
# Dictionary mapping node_ids to names
= {
edge_names 1,3): 'Main',
(2,4): 'Minor',
(3,6): 'IrrA Demand',
(6,4): 'IrrA Drain',
(3,7): 'Div2Main',
(7,4): 'Main2Conf',
(4,5): 'Conf2TRC',
(5,8): 'TRC2Term',
(
}
# Dictionary mapping basins (node_ids) to names
= {
node_names 3: 'Div',
4: 'Conf',
}
3.2.8 Step 8: Plot and compare the basin results
Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4).
= df_basin_wide.xs('Div', axis=1, level=1, drop_level=False)
df_basin_div = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False)
df_basin_conf
def plot_basin_data(ax, ax_twin, df_basin, level_color='b', storage_color='r', title='Basin'):
# Plot level data
for idx, column in enumerate(df_basin["level"].columns):
"level"][column],
ax.plot(df_basin.index, df_basin[='-', color=level_color,
linestyle=f'Level - {column}')
label
# Plot storage data
for idx, column in enumerate(df_basin["storage"].columns):
"storage"][column],
ax_twin.plot(df_basin.index, df_basin[='--', color=storage_color,
linestyle=f'Storage - {column}')
label
'Level [m]', color=level_color)
ax.set_ylabel('Storage [m³]', color=storage_color)
ax_twin.set_ylabel(
='y', labelcolor=level_color)
ax.tick_params(axis='y', labelcolor=storage_color)
ax_twin.tick_params(axis
ax.set_title(title)
# Combine legends from both axes
= ax.get_legend_handles_labels()
lines, labels = ax_twin.get_legend_handles_labels()
lines_twin, labels_twin + lines_twin, labels + labels_twin, loc='upper left')
ax.legend(lines
# Create subplots
= plt.subplots(2, 1, figsize=(12, 12), sharex=True)
fig, (ax1, ax3)
# Plot Div basin data
= ax1.twinx() # Secondary y-axis for storage
ax2 ='Div Basin Level and Storage over Time')
plot_basin_data(ax1, ax2, df_basin_div, title
# Plot Conf basin data
= ax3.twinx() # Secondary y-axis for storage
ax4 ='Conf Basin Level and Storage over Time')
plot_basin_data(ax3, ax4, df_basin_conf, title
# Common X label
'Time')
ax3.set_xlabel(
# Adjust layout to fit labels
fig.tight_layout() plt.show()
Figure 9 illustrates the water levels and storage capacities for each basin. At the diverted section, where the profile is narrower than at the confluence, we anticipate lower storage and water levels compared to the confluence section.
When compared to the natural flow conditions, where no water is abstracted for irrigation (See Crystal 1.1), there is a noticeable decrease in both storage and water levels at the confluence downstream. This reduction is attributed to the irrigation demand upstream with no return flow, which decreases the amount of available water in the main river, resulting in lower water levels at the confluence.
3.2.9 Step 9: Plot and compare the flow results
Plot the flow results in an interactive plotting tool.
= pd.read_feather(model_dir / "Crystal_1.2/results/flow.arrow")
df_flow "edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
df_flow["name"] = df_flow["edge"].map(edge_names)
df_flow[
# Plot the flow data, interactive plot with Plotly
= df_flow.pivot_table(index="time", columns="name", values="flow_rate").reset_index()
pivot_flow = px.line(pivot_flow, x="time", y=pivot_flow.columns[1:], title="Flow Over Time [m3/s]")
fig
='Edge')
fig.update_layout(legend_title_text/ "Crystal_1.2/plot_edges.html")
fig.write_html(model_dir fig.show()
The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (Figure 10).
When selecting only the flow demanded by the User Demand node, or in other words the supply for irrigation increases at times when it is required (Figure 11) and the return flow remains zero, as the assumption defined before was that there is no drain.
Figure 12 shows the flow to the ocean (Terminal). Compared to Crystal 1.1 the flow has decreased during the irrigated period. Indicating the impact of irrigation without any drain.
4 Modual 2 – Reservoirs and Public Water Supply
Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See Figure 13). 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.
4.1 Modual 2.1 – Reservoir
4.1.1 Step 1: Add a basin
This time the basin 3 will function as a reservoir instead of a diversion, meaning it’s storage and levels will play an important role for the users (the city and the farmer). The reservoir has a max. area of \(32.3 \text{ km}^2\) and a max. depth of \(7 \text{ m}\). The profile of basin 3 should change to:
model.basin.add(3, Point(-0.75, -0.5), name='Rsv'),
Node(=[20000000, 32300000], level=[0, 7]),
[basin.Profile(area=[3.5]),
basin.State(level=[starttime, endtime]),
basin.Time(time
], )
4.1.2 Step 2: Adjust the code
Adjust the naming of the basin in the dictionary mapping and the saving file should be Crystal_2.1
instead of *_1.2
.
= model_dir/ "Crystal_2.1/ribasim.toml"
toml_path
model.write(toml_path)= base_dir / "ribasim_windows/ribasim.exe" rib_path
# Dictionary mapping node_ids to names
= {
edge_names 1,3): 'Main',
(2,4): 'Minor',
(3,6): 'IrrA Demand',
(6,4): 'IrrA Drain',
(3,7): 'Rsv2Main',
(7,4): 'Main2Conf',
(4,5): 'Conf2TRC',
(5,8): 'TRC2Term',
(
}
# Dictionary mapping basins (node_ids) to names
= {
node_names 3: 'Rsv',
4: 'Conf',
}
= pd.read_feather(model_dir / "Crystal_2.1/results/basin.arrow") df_basin
# Create pivot tables and plot for basin data
= df_basin_wide.xs('Rsv', axis=1, level=1, drop_level=False)
df_basin_rsv = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False) df_basin_conf
# Plot Rsv basin data
= ax1.twinx() # Secondary y-axis for storage
ax2 ='Reservoir Level and Storage Over Time') plot_basin_data(ax1, ax2, df_basin_rsv, title
# Sample data loading and preparation
= pd.read_feather(model_dir / "Crystal_2.1/results/flow.arrow")
df_flow "edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))
df_flow["name"] = df_flow["edge"].map(edge_names)
df_flow[
# Plot the flow data, interactive plot with Plotly
= df_flow.pivot_table(index="time", columns="name", values="flow_rate").reset_index()
pivot_flow = px.line(pivot_flow, x="time", y=pivot_flow.columns[1:], title="Flow Over Time [m3/s]")
fig
='Edge')
fig.update_layout(legend_title_text/ "Crystal_2.1/plot_edges.html")
fig.write_html(model_dir fig.show()
4.1.3 Step 3: Plotting results
Figure 14 illustrates the new storage and water level at the reservoir. As expected, after increasing the profile of basin 3 to mimic the reservoir, its storage capacity increased as well.
4.2 Module 2.2 – Public Water Supply
4.2.1 Step 1: Rename the saving files
Rename the files to Crystal_2.2
4.2.2 Step 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(9, Point(0.0, -0.25), name='PWS'),
Node(
[user_demand.Time(=[0.07, 0.08, 0.09, 0.10, 0.12, 0.14, 0.15, 0.14, 0.12, 0.10, 0.09, 0.08], # Total demand in m³/s
demand=0.6,
return_factor=0,
min_level=1,
priority=[
time
starttime,"2022-02-01",
"2022-03-01",
"2022-04-01",
"2022-05-01",
"2022-06-01",
"2022-07-01",
"2022-08-01",
"2022-09-01",
"2022-10-01",
"2022-11-01",
"2022-12-01"
]
)] )
4.2.3 Step 3: Add the edges
The connection between the reservoir and the demand node needs to be defined:
1], model.basin[3])
model.edge.add(model.flow_boundary[2], model.basin[4])
model.edge.add(model.flow_boundary[3], model.user_demand[6])
model.edge.add(model.basin[3], model.user_demand[9])
model.edge.add(model.basin[6], model.basin[4])
model.edge.add(model.user_demand[9], model.basin[4])
model.edge.add(model.user_demand[3], model.tabulated_rating_curve[7])
model.edge.add(model.basin[7], model.basin[4])
model.edge.add(model.tabulated_rating_curve[4], model.tabulated_rating_curve[5])
model.edge.add(model.basin[5], model.terminal[8]) model.edge.add(model.tabulated_rating_curve[
4.2.4 Step 4: Adjust the name dictionaries
# Dictionary mapping node_ids to names
= {
edge_names 1,3): 'Main',
(2,4): 'Minor',
(3,6): 'IrrA Demand',
(6,4): 'IrrA Drain',
(3,9): 'PWS Demand',
(9,4): 'PWS Return',
(3,7): 'Rsv2Main',
(7,4): 'Main2Conf',
(4,5): 'Conf2TRC',
(5,8): 'TRC2Term',
( }
4.2.5 Step 5: Check the simulated demands
Figure 15 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node. Figure 16 shows the downstream flow to the ocean. The impact is clear. Due to the demands upstream (irrigation and public water supply) an expected decrease of discharge is shown downstream.