From 6bd801f89ffa4e412f10626d7e74cbb953510c44 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 11:32:26 +0200 Subject: [PATCH 01/19] Bring back the tutorials section --- docs/_quarto.yml | 9 +++++++-- docs/{guide => tutorial}/quickstart.qmd | 0 2 files changed, 7 insertions(+), 2 deletions(-) rename docs/{guide => tutorial}/quickstart.qmd (100%) diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 741e0fe6b..f10c146f0 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -10,8 +10,10 @@ website: left: - text: "Overview" file: index.qmd + - text: "Tutorials" + file: tutorial/quickstart.qmd - text: "How-to guides" - file: guide/quickstart.qmd + file: guide/examples.ipynb - text: "Concepts" file: concept/concept.qmd - text: "Reference" @@ -31,9 +33,12 @@ website: - changelog.qmd - known_issues.qmd + - title: "Tutorials" + contents: + - tutorial/quickstart.qmd + - title: "How-to guides" contents: - - guide/quickstart.qmd - guide/examples.ipynb - guide/qgis.qmd - guide/coupling.qmd diff --git a/docs/guide/quickstart.qmd b/docs/tutorial/quickstart.qmd similarity index 100% rename from docs/guide/quickstart.qmd rename to docs/tutorial/quickstart.qmd From 0cfa88f3f1b81d91e2354f37a75669edb6b965e9 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 11:42:52 +0200 Subject: [PATCH 02/19] Address Claude's comments plus a few more --- docs/tutorial/quickstart.qmd | 48 ++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/docs/tutorial/quickstart.qmd b/docs/tutorial/quickstart.qmd index 1b90e0c05..ae63b1cb8 100644 --- a/docs/tutorial/quickstart.qmd +++ b/docs/tutorial/quickstart.qmd @@ -25,6 +25,7 @@ By the end of the guide, users will be able to: - **Analyze Simulation Results**: Use built-in tools to analyze and interpret the results of your simulations. # Starting RIBASIM + ## System requirements Before installing Ribasim, ensure your system meets the following requirements: @@ -49,7 +50,7 @@ Usage: ribasim For more information, try '--help'. ``` -4. We use a command line interface (CLI) to install our Ribasim packages. To install Ribasim open PowerShell or Windows command prompt and write: +4. We use a command line interface (CLI) to install our Ribasim package. To install Ribasim open PowerShell or Windows command prompt and write: ```sh conda install ribasim @@ -59,14 +60,15 @@ or ```sh mamba install ribasim ``` + ## 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. +Additionally, your Python model (`.py`) and the results will also be saved in this folder. -# Modual 1 - Crystal River Basin +# Module 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 @fig-crystal-basin). 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. @@ -81,16 +83,15 @@ After this module the user will be able to: - Generate overview of results - Evaluate the simulation results -## Modual 1.1 - Natural Flow +## Module 1.1 - Natural Flow + ### 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: ```python -import shutil from pathlib import Path -import pathlib import matplotlib.pyplot as plt import numpy as np import pandas as pd @@ -104,6 +105,7 @@ from ribasim.nodes import ( from shapely.geometry import Point import subprocess # For running the model ``` + ### 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: ```python @@ -119,6 +121,7 @@ model = Model( crs="EPSG:4326", ) ``` + ### 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`. @@ -128,10 +131,10 @@ However, for this exercise actual runtime is already defined in step 2. ```python data = pd.read_csv(data_path, sep=";") data['sum']= data['minor']+data['main'] -#Average inflow and max. of the whole summed inflow data timeseries -#From 2014 - 2023 +# Average and max inflow 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) +print('Maximum inflowQ m3/s:',data['sum'].max) model.flow_boundary.add( Node(1, Point(0.0, 0.0), name='Main'), @@ -145,6 +148,7 @@ model.flow_boundary.add( )] ) ``` + ### 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. @@ -171,6 +175,7 @@ model.basin.add( ], ) ``` + ### 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. @@ -214,6 +219,7 @@ model.tabulated_rating_curve.add( ] ) ``` + ### 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. @@ -222,12 +228,13 @@ Besides the node number/name and location, no further input is needed. ```python model.terminal.add(Node(5, Point(-1.5, -3.0), name="Terminal")) ``` + ### Step 7: Defining edges Implement the connections (edges) between the nodes, in the following order: -1. Flow boundaries to the basin; -2. Basin to the rating curve; -3. Tabulated rating curve to the terminal. +1. Flow boundaries to the basin; +2. Basin to the rating curve; +3. Tabulated rating curve to the terminal. ```python model.edge.add(model.flow_boundary[1], model.basin[3]) @@ -235,6 +242,7 @@ model.edge.add(model.flow_boundary[2], model.basin[3]) model.edge.add(model.basin[3], model.tabulated_rating_curve[4]) model.edge.add(model.tabulated_rating_curve[4], model.terminal[5]) ``` + ### 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`: @@ -341,7 +349,7 @@ Which is expected in a natural flow environment, as what is coming into the conf ![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align="left" #fig-sim2} -## Modual 1.2 - Irrigation demand +## Module 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 @fig-irrigation). @@ -354,13 +362,11 @@ For this update schematization, we need to incorporate three additional nodes: - User Demand: Represents the irrigation demand. - Tabulates Rating Curve: Defines the remaining water flow from the main river at the diversion point. -### Step 1: Setup the model & adjust Import Packages +### 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. ```python -import shutilfrom -import pathlib import matplotlib.pyplot as plt import numpy as np import pandas as pd @@ -468,7 +474,7 @@ This time the new outputs should be written in a new folder called `Crystal_1.2` ```python model.plot() -toml_path = model_dir/ "Crystal_1.2/ribasim.toml" +toml_path = model_dir / "Crystal_1.2/ribasim.toml" model.write(toml_path) rib_path = base_dir / "ribasim_windows/ribasim.exe" @@ -550,7 +556,6 @@ plot_basin_data(ax3, ax4, df_basin_conf, title='Conf Basin Level and Storage ove # Common X label ax3.set_xlabel('Time') - fig.tight_layout() # Adjust layout to fit labels plt.show() ``` @@ -595,14 +600,14 @@ Indicating the impact of irrigation without any drain. ![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align="left" #fig-sim6} -# Modual 2 – Reservoirs and Public Water Supply +# Module 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 @fig-reservoir). 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. ![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align="left" #fig-reservoir} -## Modual 2.1 – Reservoir +## Module 2.1 – Reservoir ### 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}$. @@ -695,7 +700,8 @@ To represents the total flow rate or abstraction rate required to meet the water model.user_demand.add( Node(9, Point(0.0, -0.25), name='PWS'), [user_demand.Time( - demand=[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 + # Total demand in m³/s + demand=[0.07, 0.08, 0.09, 0.10, 0.12, 0.14, 0.15, 0.14, 0.12, 0.10, 0.09, 0.08], return_factor=0.6, min_level=0, priority=1, From 35038dc6117f1164c9f24b3635d3c58104cb6b24 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 13:00:52 +0200 Subject: [PATCH 03/19] quarto convert --- docs/_quarto.yml | 4 +- docs/tutorial/quickstart.ipynb | 786 +++++++++++++++++++++++++++++++++ 2 files changed, 788 insertions(+), 2 deletions(-) create mode 100644 docs/tutorial/quickstart.ipynb diff --git a/docs/_quarto.yml b/docs/_quarto.yml index f10c146f0..3e5630cfd 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -11,7 +11,7 @@ website: - text: "Overview" file: index.qmd - text: "Tutorials" - file: tutorial/quickstart.qmd + file: tutorial/quickstart.ipynb - text: "How-to guides" file: guide/examples.ipynb - text: "Concepts" @@ -35,7 +35,7 @@ website: - title: "Tutorials" contents: - - tutorial/quickstart.qmd + - tutorial/quickstart.ipynb - title: "How-to guides" contents: diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb new file mode 100644 index 000000000..985f35c40 --- /dev/null +++ b/docs/tutorial/quickstart.ipynb @@ -0,0 +1,786 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: \"Quick start guide\"\n", + "---\n", + "\n", + "![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align=\"left\"}\n", + "\n", + "# Introduction\n", + "Welcome to Ribasim!\n", + "This guide will help you get started with the basics of installing and using Ribasim for river basin simulation.\n", + "In this guide, the schematization of models will be implemented in Python using the Ribasim Python package.\n", + "The Ribasim package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", + "It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible.\n", + "This package is available on PyPI.\n", + "\n", + "## Learning objectives\n", + "In this guide, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", + "The guide is divided into different modules, each covering various scenarios.\n", + "These include simulating natural flow, implementing reservoirs, and observing the impact of other structures.\n", + "While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations.\n", + "By the end of the guide, users will be able to:\n", + "\n", + "- **Set Up a Basic Ribasim Model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", + "- **Evaluate the Impact of Demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", + "- **Modify and Update Models**: Learn how to update existing models with new data and changes.\n", + "- **Analyze Simulation Results**: Use built-in tools to analyze and interpret the results of your simulations.\n", + "\n", + "# Starting RIBASIM\n", + "\n", + "## System requirements\n", + "Before installing Ribasim, ensure your system meets the following requirements:\n", + "\n", + "- Operating System: Windows 10 or later, or Linux (latest distributions)\n", + "- Processor: x86-64 (64-bit)\n", + "- RAM: 4 GB minimum, 8 GB recommended\n", + "- Hard Drive: 1GB of free space\n", + "\n", + "## Installation\n", + "1. Download Ribasim: Obtain the Ribasim 9 installation package from the official website: [Ribasim - Installation](https://ribasim.org/install.html) under chapter '2 Download':\n", + "\n", + "* For Windows download: `ribasim_windows.zip`\n", + "* For Linux: `ribasim_linux.zip`\n", + "\n", + "2. 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;\n", + "3. Check installation: To check whether the installation was performed successfully, in `cmd` go to the executable path and type `ribasim` with no arguments in the command line. This will give the following message:\n", + "\n", + "```batch\n", + "error: the following required arguments were not provided:\n", + " \n", + "Usage: ribasim \n", + "For more information, try '--help'.\n", + "```\n", + "\n", + "4. We use a command line interface (CLI) to install our Ribasim package. To install Ribasim open PowerShell or Windows command prompt and write:\n", + "\n", + "```sh\n", + "conda install ribasim\n", + "```\n", + "or\n", + "\n", + "```sh\n", + "mamba install ribasim\n", + "```\n", + "\n", + "## Data preparation\n", + "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:\n", + "\n", + "- `QuickStartGuide.pdf`\n", + "- `data`: Contains data inputs such as time series needed for running the case.\n", + "Additionally, your Python model (`.py`) and the results will also be saved in this folder.\n", + "\n", + "# Module 1 - Crystal River Basin\n", + "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 @fig-crystal-basin).\n", + "An average discharge of $44.45 \\text{ m}^3/\\text{s}$ is measured at the confluence.\n", + "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", + "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", + "\n", + "![Crystal Basin based on natural flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-Basin-based-on-natural-flow.png){fig-align=\"left\" #fig-crystal-basin}\n", + "\n", + "After this module the user will be able to:\n", + "\n", + "- Build a river basin model from scratch\n", + "- Understand the functionality of the ‘demand’ and ‘basin’ nodes\n", + "- Generate overview of results\n", + "- Evaluate the simulation results\n", + "\n", + "## Module 1.1 - Natural Flow\n", + "\n", + "### Step 1: Import packages\n", + "Before building the model we need to import some modules.\n", + "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`.\n", + "Import the following modules in python:\n", + "\n", + "```python\n", + "from pathlib import Path\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "from ribasim import Allocation, Model, Node # The main library used for river basin modeling.\n", + "from ribasim.nodes import (\n", + " flow_boundary,\n", + " basin,\n", + " tabulated_rating_curve,\n", + " terminal\n", + ")\n", + "from shapely.geometry import Point\n", + "import subprocess # For running the model\n", + "```\n", + "\n", + "### Step 2: Setup paths and model configuration\n", + "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:\n", + "```python\n", + "base_dir = Path(\"c:/Ribasim\")\n", + "model_dir = base_dir / \"Crystal_Basin\"\n", + "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "\n", + "starttime = \"2022-01-01\"\n", + "endtime = \"2023-01-01\"\n", + "model = Model(\n", + " starttime=starttime,\n", + " endtime=endtime,\n", + " crs=\"EPSG:4326\",\n", + ")\n", + "```\n", + "\n", + "### Step 3: Flow boundary nodes\n", + "Crystal Basin consists of two inflow points, the tributary and the main Crystal river, we will call them `Minor` and `Main` respectively.\n", + "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", + "This inflow data goes monthly from 2014 to 2023.\n", + "However, for this exercise actual runtime is already defined in step 2.\n", + "\n", + "```python\n", + "data = pd.read_csv(data_path, sep=\";\")\n", + "data['sum']= data['minor']+data['main']\n", + "# Average and max inflow of the whole summed inflow data timeseries\n", + "# From 2014 - 2023\n", + "print('Average inflowQ m3/s:',data['sum'].mean())\n", + "print('Maximum inflowQ m3/s:',data['sum'].max)\n", + "\n", + "model.flow_boundary.add(\n", + " Node(1, Point(0.0, 0.0), name='Main'),\n", + " [flow_boundary.Time(time=data.time, flow_rate=data.main,\n", + " )]\n", + ")\n", + "\n", + "model.flow_boundary.add(\n", + " Node(2, Point(-3.0, 0.0), name='Minor'),\n", + " [flow_boundary.Time(time=data.time, flow_rate=data.minor,\n", + " )]\n", + ")\n", + "```\n", + "\n", + "### Step 4: Basin node (confluence)\n", + "To schematize the confluence from the tributary we will use the Basin node.\n", + "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.\n", + "@fig-confluence visualizes a cross section of the confluence point in our model.\n", + "\n", + "![Basin node concept for the confluence](https://s3.deltares.nl/ribasim/doc-image/quickstart/Basin-node-concept-for-the-confluence.png){fig-align=\"left\" #fig-confluence}\n", + "\n", + "@tbl-input1 shows the input data for the basin node profile.\n", + "\n", + ": Profile data for the basin node {#tbl-input1}\n", + "\n", + "| Area [$\\text{m}^2$] | Level [$\\text{m}$] |\n", + "|---------------------|--------------------|\n", + "| $672000.0$ | $0.0$ |\n", + "| $5600000.0$ | $6.0$ |\n", + "\n", + "To specify the basin profile, the following code is used:\n", + "```python\n", + "model.basin.add(\n", + " Node(3, Point(-1.5, -1), name='Conf'),\n", + " [basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "```\n", + "\n", + "### Step 5: Tabulated rating curve\n", + "In the previous step we implemented a Basin node that functions as a confluence.\n", + "Conceptually, the basin acts like a bucket of water, accumulating inflows and then releasing them.\n", + "However, the model does not run if the basin is directly connected to the terminal node.\n", + "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.\n", + "This relation is defined by the `Tabulated Rating Curve` and thus serves as a critical component.\n", + "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", + "\n", + "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}$.\n", + "It is therefore expected that the confluence basin reaches a level where the outflow is equal to the inflow via the rating curve.\n", + "Only then is the confluence basin in equilibrium.\n", + "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 @tbl-input2 will be used as input.\n", + "\n", + ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", + "\n", + "| Water Level ($h$) [$\\text{m}$] | Outflow ($Q$) [$\\text{m}^3/\\text{s}$] |\n", + "| -------------------------------|---------------------------------------|\n", + "| $0.0$ | $0.0$ |\n", + "| $2.0$ | $50.0$ |\n", + "| $5.0$ | $200.0$ |\n", + "\n", + "In Ribasim, the $Q(h)$ relation is a linear function, so the points in between will be linearly interpolated.\n", + "@fig-discharge illustrates the visual process and shows a progressive increase in discharge with rising water levels.\n", + "In this case this means:\n", + "\n", + "- At level $0.0$: No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.\n", + "- 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.\n", + "- 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.\n", + "\n", + "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", + "\n", + "Taking this into account, add the `Tabulated Rating Curve` as follows:\n", + "\n", + "```python\n", + "model.tabulated_rating_curve.add(\n", + " Node(4, Point(-1.5, -1.5), name='MainConf'),\n", + " [tabulated_rating_curve.Static(\n", + " level=[0.0, 2, 5],\n", + " flow_rate=[0.0, 50, 200],\n", + " )\n", + " ]\n", + ")\n", + "```\n", + "\n", + "### Step 6: Terminal node\n", + "Finally all the water will discharge into the ocean.\n", + "Schematize this with the terminal node as it portrays the end point of the model.\n", + "Besides the node number/name and location, no further input is needed.\n", + "\n", + "```python\n", + "model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"Terminal\"))\n", + "```\n", + "\n", + "### Step 7: Defining edges\n", + "Implement the connections (edges) between the nodes, in the following order:\n", + "\n", + "1. Flow boundaries to the basin;\n", + "2. Basin to the rating curve;\n", + "3. Tabulated rating curve to the terminal.\n", + "\n", + "```python\n", + "model.edge.add(model.flow_boundary[1], model.basin[3])\n", + "model.edge.add(model.flow_boundary[2], model.basin[3])\n", + "model.edge.add(model.basin[3], model.tabulated_rating_curve[4])\n", + "model.edge.add(model.tabulated_rating_curve[4], model.terminal[5])\n", + "```\n", + "\n", + "### Step 8: Visualization and model execution\n", + "Plot the schematization, write the model configuration to the `TOML` file.\n", + "Name the output file `Crystal_1.1/ribasim.toml`:\n", + "\n", + "```python\n", + "model.plot()\n", + "\n", + "toml_path = model_dir/ \"Crystal_1.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "```\n", + "The schematization should look like @fig-cs11.\n", + "\n", + "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", + "\n", + "After writing model.write a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", + "\n", + "- ribasim.toml: The model configuration\n", + "- database.gpkg: A geopackage containing the shapes of your schematization and the input data of the nodes used.\n", + "\n", + "Now run the model:\n", + "```python\n", + "subprocess.run([rib_path, toml_path], check=True)\n", + "```\n", + "\n", + "### Step 9: Post-processing results\n", + "Read the arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:\n", + "\n", + "```python\n", + "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_wide = df_basin.pivot_table(\n", + "index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", + ")\n", + "\n", + "# Skip the first timestep as it’s the initialization step\n", + "df_basin_wide = df_basin_wide.iloc[1:]\n", + "\n", + "# Plot level and storage on the same graph with dual y-axes\n", + "fig, ax1 = plt.subplots(figsize=(12, 6))\n", + "\n", + "# Plot level on the primary y-axis\n", + "color = 'b'\n", + "ax1.set_xlabel('Time')\n", + "ax1.set_ylabel('Level [m]', color=color)\n", + "ax1.plot(df_basin_wide.index, df_basin_wide[\"level\"], color=color)\n", + "ax1.tick_params(axis='y', labelcolor=color)\n", + "\n", + "# Create a secondary y-axis for storage\n", + "ax2 = ax1.twinx()\n", + "color = 'r'\n", + "ax2.set_ylabel('Storage [m³]', color='r')\n", + "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"],linestyle='--', color=color)\n", + "ax2.tick_params(axis='y', labelcolor=color)\n", + "\n", + "fig.tight_layout() # Adjust layout to fit labels\n", + "plt.title('Basin Level and Storage Over Time')\n", + "plt.show()\n", + "\n", + "\n", + "# Plot flow data\n", + "# Read the data from feather format\n", + "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", + "# Create 'edge' and 'flow_m3d' columns\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "\n", + "# Create a pivot table\n", + "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_rate\")\n", + "\n", + "# Skip the first timestep\n", + "pivot_flow = pivot_flow.iloc[1:]\n", + "\n", + "line_styles = ['-', '--', '-', '-.']\n", + "num_styles = len(line_styles)\n", + "\n", + "fig, ax = plt.subplots(figsize=(12, 6))\n", + "for i, column in enumerate(pivot_flow.columns):\n", + " pivot_flow[column].plot(ax=ax, linestyle=line_styles[i % num_styles],linewidth=1.5, alpha=0.8)\n", + "\n", + "# Set labels and title\n", + "ax.set_xlabel('Time')\n", + "ax.set_ylabel('Flow [m³/s]')\n", + "ax.legend(bbox_to_anchor=(1.15, 1), title=\"Edge\")\n", + "plt.title('Flow Over Time')\n", + "plt.grid(True)\n", + "plt.show()\n", + "```\n", + "\n", + "@fig-sim1 shows the storage and levels in the basin node.\n", + "\n", + "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.\n", + "To accurately represent the relationship between water levels and discharge rates at this confluence, a rating curve node is implemented.\n", + "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", + "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.\n", + "This is because there is no net change in storage; all incoming water is balanced by outgoing flow.\n", + "\n", + "![Simulated basin level and storage](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-level-and-storage.png){fig-align=\"left\" #fig-sim1}\n", + "\n", + "@fig-sim2 shows the discharges in $\\text{m}^3/\\text{s}$ on each edge.\n", + "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.\n", + "Both show the same discharge over time.\n", + "Which is expected in a natural flow environment, as what is coming into the confluence must come out.\n", + "\n", + "![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align=\"left\" #fig-sim2}\n", + "\n", + "## Module 1.2 - Irrigation demand\n", + "\n", + "Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation.\n", + "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 @fig-irrigation).\n", + "\n", + "![Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-irrigation}\n", + "\n", + "For this update schematization, we need to incorporate three additional nodes:\n", + "\n", + "- Basin: Represents a cross-sectional point where water is diverted.\n", + "- User Demand: Represents the irrigation demand.\n", + "- Tabulates Rating Curve: Defines the remaining water flow from the main river at the diversion point.\n", + "\n", + "### Step 1: Setup the model & adjust import packages\n", + "Copy and paste the python script `Crystal_1.1` and rename it `Crystal_1.2`.\n", + "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.\n", + "\n", + "```python\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "from ribasim import Allocation, Model, Node # The main library used for river basin modeling.\n", + "from ribasim.nodes import (\n", + " flow_boundary,\n", + " basin,\n", + " tabulated_rating_curve,\n", + " user_demand,\n", + " terminal\n", + ")\n", + "from shapely.geometry import Point\n", + "import subprocess # For running the model\n", + "import plotly.express as px\n", + "```\n", + "\n", + "### Step 2: Add a second Basin node\n", + "Schematically we are dealing with two basins.\n", + "To keep the order of flow from upstream to downstream it is recommended to adjust the node_id numbers accordingly.\n", + "In this case `node_id = 3` will be `node_id = 4`.\n", + "Basin 3 will portray as the point in the river where the diversion takes place, getting the name `Div`.\n", + "Its profile area at this intersection is slightly smaller than at the confluence.\n", + "\n", + "```python\n", + "model.basin.add(\n", + " Node(3, Point(-0.75, -0.5), name='Div'),\n", + " [basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", + " basin.State(level=[3]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "\n", + "model.basin.add(\n", + " Node(4, Point(-1.5, -1), name='Conf'),\n", + " [basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "```\n", + "\n", + "### Step 3: Add the irrigation demand\n", + "A big farm company needs to apply irrigation to its field starting from April to September.\n", + "The irrigated field is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", + "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.\n", + "Start of irrigation takes place on the 1st of April until the end of August.\n", + "The farmer taps water through a canal (demand).\n", + "\n", + "For now, let’s assume the return flow remains $0.0$ (`return_factor`).\n", + "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", + "The user demand node interpolates the demand values. Thus the following code needs to be implemented:\n", + "\n", + "```python\n", + "model.user_demand.add(\n", + " Node(6, Point(-1.5, 1.0), name='IrrA'),\n", + " [user_demand.Time(\n", + " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", + " return_factor=0,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[starttime, \"2022-03-31\", \"2022-04-01\", \"2022-07-01\", \"2022-09-30\", \"2022-10-01\"]\n", + " )\n", + " ]\n", + ")\n", + "```\n", + "\n", + "### Step 4: Add a tabulated rating curve\n", + "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.\n", + "The rest of the water will flow naturally towards the confluence:\n", + "\n", + "```python\n", + "model.tabulated_rating_curve.add(\n", + " Node(7, Point(-1.125, -0.75), name='MainDiv'),\n", + " [tabulated_rating_curve.Static(\n", + " level=[0.0, 1.5, 5],\n", + " flow_rate=[0.0, 45, 200],\n", + " )\n", + " ]\n", + ")\n", + "```\n", + "\n", + "It is up to the user to renumber the ID’s of the nodes.\n", + "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", + "\n", + "### Step 5: Adjust the terminal node id and edges\n", + "Adjust the terminal node id.\n", + "Since we added more nodes we have more edges. Add and adjust the edges:\n", + "\n", + "```python\n", + "model.terminal.add(Node(8, Point(-1.5, -3.0), name=\"Terminal\"))\n", + "\n", + "model.edge.add(model.flow_boundary[1], model.basin[3])\n", + "model.edge.add(model.flow_boundary[2], model.basin[4])\n", + "model.edge.add(model.basin[3], model.user_demand[6])\n", + "model.edge.add(model.user_demand[6], model.basin[4])\n", + "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", + "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", + "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", + "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])\n", + "```\n", + "### Step 6: Plot model and run\n", + "Plot the schematization and run the model.\n", + "This time the new outputs should be written in a new folder called `Crystal_1.2`:\n", + "\n", + "```python\n", + "model.plot()\n", + "\n", + "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", + "model.write(toml_path)\n", + "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "\n", + "subprocess.run([rib_path, toml_path], check=True)\n", + "```\n", + "\n", + "The schematization should look like @fig-cs12.\n", + "\n", + "![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-cs12}\n", + "\n", + "### Step 7: Name the edges and basins\n", + "The names of each nodes are defined and saved in the geopackage.\n", + "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe.\n", + "\n", + "```python\n", + "# Dictionary mapping node_ids to names\n", + "edge_names = {\n", + " (1,3): 'Main',\n", + " (2,4): 'Minor',\n", + " (3,6): 'IrrA Demand',\n", + " (6,4): 'IrrA Drain',\n", + " (3,7): 'Div2Main',\n", + " (7,4): 'Main2Conf',\n", + " (4,5): 'Conf2TRC',\n", + " (5,8): 'TRC2Term',\n", + "}\n", + "\n", + "# Dictionary mapping basins (node_ids) to names\n", + "node_names = {\n", + " 3: 'Div',\n", + " 4: 'Conf',\n", + "}\n", + "```\n", + "\n", + "### Step 8: Plot and compare the basin results\n", + "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4).\n", + "\n", + "```python\n", + "\n", + "df_basin_div = df_basin_wide.xs('Div', axis=1, level=1, drop_level=False)\n", + "df_basin_conf = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False)\n", + "\n", + "def plot_basin_data(ax, ax_twin, df_basin, level_color='b', storage_color='r', title='Basin'):\n", + " # Plot level data\n", + " for idx, column in enumerate(df_basin[\"level\"].columns):\n", + " ax.plot(df_basin.index, df_basin[\"level\"][column],\n", + " linestyle='-', color=level_color,\n", + " label=f'Level - {column}')\n", + "\n", + " # Plot storage data\n", + " for idx, column in enumerate(df_basin[\"storage\"].columns):\n", + " ax_twin.plot(df_basin.index, df_basin[\"storage\"][column],\n", + " linestyle='--', color=storage_color,\n", + " label=f'Storage - {column}')\n", + "\n", + " ax.set_ylabel('Level [m]', color=level_color)\n", + " ax_twin.set_ylabel('Storage [m³]', color=storage_color)\n", + "\n", + " ax.tick_params(axis='y', labelcolor=level_color)\n", + " ax_twin.tick_params(axis='y', labelcolor=storage_color)\n", + "\n", + " ax.set_title(title)\n", + "\n", + " # Combine legends from both axes\n", + " lines, labels = ax.get_legend_handles_labels()\n", + " lines_twin, labels_twin = ax_twin.get_legend_handles_labels()\n", + " ax.legend(lines + lines_twin, labels + labels_twin, loc='upper left')\n", + "\n", + "# Create subplots\n", + "fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True)\n", + "\n", + "# Plot Div basin data\n", + "ax2 = ax1.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax1, ax2, df_basin_div, title='Div Basin Level and Storage over Time')\n", + "\n", + "# Plot Conf basin data\n", + "ax4 = ax3.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax3, ax4, df_basin_conf, title='Conf Basin Level and Storage over Time')\n", + "\n", + "# Common X label\n", + "ax3.set_xlabel('Time')\n", + "fig.tight_layout() # Adjust layout to fit labels\n", + "plt.show()\n", + "```\n", + "\n", + "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", + "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.\n", + "\n", + "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.\n", + "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.\n", + "\n", + "![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align=\"left\" #fig-sim3}\n", + "\n", + "### Step 9: Plot and compare the flow results\n", + "Plot the flow results in an interactive plotting tool.\n", + "\n", + "```python\n", + "df_flow = pd.read_feather(model_dir / \"Crystal_1.2/results/flow.arrow\")\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", + "\n", + "# Plot the flow data, interactive plot with Plotly\n", + "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"name\", values=\"flow_rate\").reset_index()\n", + "fig = px.line(pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\")\n", + "\n", + "fig.update_layout(legend_title_text='Edge')\n", + "fig.write_html(model_dir/ \"Crystal_1.2/plot_edges.html\")\n", + "fig.show()\n", + "\n", + "```\n", + "\n", + "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", + "\n", + "![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align=\"left\" #fig-sim4}\n", + "\n", + "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 (@fig-sim5) and the return flow remains zero, as the assumption defined before was that there is no drain.\n", + "\n", + "![Supplied irrigation and return flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Supplied-irrigation-and-return-flow.png){fig-align=\"left\" #fig-sim5}\n", + "\n", + "@fig-sim6 shows the flow to the ocean (Terminal).\n", + "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", + "Indicating the impact of irrigation without any drain.\n", + "\n", + "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}\n", + "\n", + "# Module 2 – Reservoirs and Public Water Supply\n", + "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", + "The reservoir is to help ensure a reliable supply during dry periods.\n", + "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal Basin.\n", + "\n", + "![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align=\"left\" #fig-reservoir}\n", + "\n", + "## Module 2.1 – Reservoir\n", + "### Step 1: Add a basin\n", + "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).\n", + "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", + "The profile of basin 3 should change to:\n", + "\n", + "```python\n", + "model.basin.add(\n", + " Node(3, Point(-0.75, -0.5), name='Rsv'),\n", + " [basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", + " basin.State(level=[3.5]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "```\n", + "\n", + "### Step 2: Adjust the code\n", + "Adjust the naming of the basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`.\n", + "\n", + "```python\n", + "toml_path = model_dir/ \"Crystal_2.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "```\n", + "\n", + "```python\n", + "# Dictionary mapping node_ids to names\n", + "edge_names = {\n", + " (1,3): 'Main',\n", + " (2,4): 'Minor',\n", + " (3,6): 'IrrA Demand',\n", + " (6,4): 'IrrA Drain',\n", + " (3,7): 'Rsv2Main',\n", + " (7,4): 'Main2Conf',\n", + " (4,5): 'Conf2TRC',\n", + " (5,8): 'TRC2Term',\n", + "}\n", + "\n", + "# Dictionary mapping basins (node_ids) to names\n", + "node_names = {\n", + " 3: 'Rsv',\n", + " 4: 'Conf',\n", + "}\n", + "\n", + "df_basin = pd.read_feather(model_dir / \"Crystal_2.1/results/basin.arrow\")\n", + "```\n", + "\n", + "```python\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_rsv = df_basin_wide.xs('Rsv', axis=1, level=1, drop_level=False)\n", + "df_basin_conf = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False)\n", + "```\n", + "\n", + "```python\n", + "# Plot Rsv basin data\n", + "ax2 = ax1.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax1, ax2, df_basin_rsv, title='Reservoir Level and Storage Over Time')\n", + "```\n", + "\n", + "```python\n", + "# Sample data loading and preparation\n", + "df_flow = pd.read_feather(model_dir / \"Crystal_2.1/results/flow.arrow\")\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", + "\n", + "# Plot the flow data, interactive plot with Plotly\n", + "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"name\", values=\"flow_rate\").reset_index()\n", + "fig = px.line(pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\")\n", + "\n", + "fig.update_layout(legend_title_text='Edge')\n", + "fig.write_html(model_dir/ \"Crystal_2.1/plot_edges.html\")\n", + "fig.show()\n", + "```\n", + "\n", + "### Step 3: Plotting results\n", + "@fig-sim7 illustrates the new storage and water level at the reservoir.\n", + "As expected, after increasing the profile of basin 3 to mimic the reservoir, its storage capacity increased as well.\n", + "\n", + "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align=\"left\" #fig-sim7}\n", + "\n", + "## Module 2.2 – Public Water Supply\n", + "\n", + "### Step 1: Rename the saving files\n", + "Rename the files to `Crystal_2.2`\n", + "\n", + "### Step 2: Add a demand node\n", + "$50.000$ people live in Crystal City.\n", + "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%$.\n", + "\n", + "```python\n", + "model.user_demand.add(\n", + " Node(9, Point(0.0, -0.25), name='PWS'),\n", + " [user_demand.Time(\n", + " # Total demand in m³/s\n", + " demand=[0.07, 0.08, 0.09, 0.10, 0.12, 0.14, 0.15, 0.14, 0.12, 0.10, 0.09, 0.08],\n", + " return_factor=0.6,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[\n", + " starttime,\n", + " \"2022-02-01\",\n", + " \"2022-03-01\",\n", + " \"2022-04-01\",\n", + " \"2022-05-01\",\n", + " \"2022-06-01\",\n", + " \"2022-07-01\",\n", + " \"2022-08-01\",\n", + " \"2022-09-01\",\n", + " \"2022-10-01\",\n", + " \"2022-11-01\",\n", + " \"2022-12-01\"\n", + " ]\n", + " )]\n", + ")\n", + "```\n", + "\n", + "### Step 3: Add the edges\n", + "The connection between the reservoir and the demand node needs to be defined:\n", + "\n", + "```python\n", + "model.edge.add(model.flow_boundary[1], model.basin[3])\n", + "model.edge.add(model.flow_boundary[2], model.basin[4])\n", + "model.edge.add(model.basin[3], model.user_demand[6])\n", + "model.edge.add(model.basin[3], model.user_demand[9])\n", + "model.edge.add(model.user_demand[6], model.basin[4])\n", + "model.edge.add(model.user_demand[9], model.basin[4])\n", + "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", + "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", + "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", + "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])\n", + "```\n", + "\n", + "### Step 4: Adjust the name dictionaries\n", + "\n", + "```python\n", + "# Dictionary mapping node_ids to names\n", + "edge_names = {\n", + " (1,3): 'Main',\n", + " (2,4): 'Minor',\n", + " (3,6): 'IrrA Demand',\n", + " (6,4): 'IrrA Drain',\n", + " (3,9): 'PWS Demand',\n", + " (9,4): 'PWS Return',\n", + " (3,7): 'Rsv2Main',\n", + " (7,4): 'Main2Conf',\n", + " (4,5): 'Conf2TRC',\n", + " (5,8): 'TRC2Term',\n", + "}\n", + "```\n", + "### Step 5: Check the simulated demands\n", + "@fig-sim8 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node.\n", + "@fig-sim9 shows the downstream flow to the ocean.\n", + "The impact is clear.\n", + "Due to the demands upstream (irrigation and public water supply) an expected decrease of discharge is shown downstream.\n", + "\n", + "![Simulated flows to and from the city](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-to-and-from-the-city.png){fig-align=\"left\" #fig-sim8}\n", + "\n", + "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels-reservoir.png){fig-align=\"left\" #fig-sim9}" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 4c1a50e7c453f50226fac5b43758190bced1325d Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 13:02:07 +0200 Subject: [PATCH 04/19] Remove QMD --- docs/tutorial/quickstart.qmd | 767 ----------------------------------- 1 file changed, 767 deletions(-) delete mode 100644 docs/tutorial/quickstart.qmd diff --git a/docs/tutorial/quickstart.qmd b/docs/tutorial/quickstart.qmd deleted file mode 100644 index ae63b1cb8..000000000 --- a/docs/tutorial/quickstart.qmd +++ /dev/null @@ -1,767 +0,0 @@ ---- -title: "Quick start guide" ---- - -![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align="left"} - -# 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. - -## 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. - -# Starting RIBASIM - -## 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 - -## Installation -1. Download Ribasim: Obtain the Ribasim 9 installation package from the official website: [Ribasim - Installation](https://ribasim.org/install.html) under chapter '2 Download': - -* For Windows download: `ribasim_windows.zip` -* For Linux: `ribasim_linux.zip` - -2. 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; -3. Check installation: To check whether the installation was performed successfully, in `cmd` go to the executable path and type `ribasim` with no arguments in the command line. This will give the following message: - -```batch -error: the following required arguments were not provided: - -Usage: ribasim -For more information, try '--help'. -``` - -4. We use a command line interface (CLI) to install our Ribasim package. To install Ribasim open PowerShell or Windows command prompt and write: - -```sh -conda install ribasim -``` -or - -```sh -mamba install ribasim -``` - -## 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 the results will also be saved in this folder. - -# Module 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 @fig-crystal-basin). -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. - -![Crystal Basin based on natural flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-Basin-based-on-natural-flow.png){fig-align="left" #fig-crystal-basin} - -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 - -## Module 1.1 - Natural Flow - -### 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: - -```python -from pathlib import Path -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 -``` - -### 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: -```python -base_dir = Path("c:/Ribasim") -model_dir = base_dir / "Crystal_Basin" -data_path = model_dir / "data/input/ACTINFLW.csv" - -starttime = "2022-01-01" -endtime = "2023-01-01" -model = Model( - starttime=starttime, - endtime=endtime, - crs="EPSG:4326", -) -``` - -### 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. - -```python -data = pd.read_csv(data_path, sep=";") -data['sum']= data['minor']+data['main'] -# Average and max inflow of the whole summed inflow data timeseries -# From 2014 - 2023 -print('Average inflowQ m3/s:',data['sum'].mean()) -print('Maximum inflowQ m3/s:',data['sum'].max) - -model.flow_boundary.add( - Node(1, Point(0.0, 0.0), name='Main'), - [flow_boundary.Time(time=data.time, flow_rate=data.main, - )] -) - -model.flow_boundary.add( - Node(2, Point(-3.0, 0.0), name='Minor'), - [flow_boundary.Time(time=data.time, flow_rate=data.minor, - )] -) -``` - -### 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. -@fig-confluence visualizes a cross section of the confluence point in our model. - -![Basin node concept for the confluence](https://s3.deltares.nl/ribasim/doc-image/quickstart/Basin-node-concept-for-the-confluence.png){fig-align="left" #fig-confluence} - -@tbl-input1 shows the input data for the basin node profile. - -: Profile data for the basin node {#tbl-input1} - -| 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: -```python -model.basin.add( - Node(3, Point(-1.5, -1), name='Conf'), - [basin.Profile(area=[672000, 5600000], level=[0, 6]), - basin.State(level=[4]), - basin.Time(time=[starttime, endtime]), - ], -) -``` - -### 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 @tbl-input2 will be used as input. - -: Input data for the Tabulated Rating Curve {#tbl-input2} - -| 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. -@fig-discharge 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. - -![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align="left" #fig-discharge} - -Taking this into account, add the `Tabulated Rating Curve` as follows: - -```python -model.tabulated_rating_curve.add( - Node(4, Point(-1.5, -1.5), name='MainConf'), - [tabulated_rating_curve.Static( - level=[0.0, 2, 5], - flow_rate=[0.0, 50, 200], - ) - ] -) -``` - -### 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. - -```python -model.terminal.add(Node(5, Point(-1.5, -3.0), name="Terminal")) -``` - -### Step 7: Defining edges -Implement the connections (edges) between the nodes, in the following order: - -1. Flow boundaries to the basin; -2. Basin to the rating curve; -3. Tabulated rating curve to the terminal. - -```python -model.edge.add(model.flow_boundary[1], model.basin[3]) -model.edge.add(model.flow_boundary[2], model.basin[3]) -model.edge.add(model.basin[3], model.tabulated_rating_curve[4]) -model.edge.add(model.tabulated_rating_curve[4], model.terminal[5]) -``` - -### 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`: - -```python -model.plot() - -toml_path = model_dir/ "Crystal_1.1/ribasim.toml" -model.write(toml_path) -rib_path = base_dir / "ribasim_windows/ribasim.exe" -``` -The schematization should look like @fig-cs11. - -![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align="left" #fig-cs11} - -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: -```python -subprocess.run([rib_path, toml_path], check=True) -``` - -### 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: - -```python -df_basin = pd.read_feather(model_dir / "Crystal_1.1/results/basin.arrow") - -# Create pivot tables and plot for basin data -df_basin_wide = df_basin.pivot_table( -index="time", columns="node_id", values=["storage", "level"] -) - -# Skip the first timestep as it’s the initialization step -df_basin_wide = df_basin_wide.iloc[1:] - -# Plot level and storage on the same graph with dual y-axes -fig, ax1 = plt.subplots(figsize=(12, 6)) - -# Plot level on the primary y-axis -color = 'b' -ax1.set_xlabel('Time') -ax1.set_ylabel('Level [m]', color=color) -ax1.plot(df_basin_wide.index, df_basin_wide["level"], color=color) -ax1.tick_params(axis='y', labelcolor=color) - -# Create a secondary y-axis for storage -ax2 = ax1.twinx() -color = 'r' -ax2.set_ylabel('Storage [m³]', color='r') -ax2.plot(df_basin_wide.index, df_basin_wide["storage"],linestyle='--', color=color) -ax2.tick_params(axis='y', labelcolor=color) - -fig.tight_layout() # Adjust layout to fit labels -plt.title('Basin Level and Storage Over Time') -plt.show() - - -# Plot flow data -# Read the data from feather format -df_flow = pd.read_feather(model_dir / "Crystal_1.1/results/flow.arrow") -# Create 'edge' and 'flow_m3d' columns -df_flow["edge"] = list(zip(df_flow.from_node_id, df_flow.to_node_id)) - -# Create a pivot table -pivot_flow = df_flow.pivot_table(index="time", columns="edge", values="flow_rate") - -# Skip the first timestep -pivot_flow = pivot_flow.iloc[1:] - -line_styles = ['-', '--', '-', '-.'] -num_styles = len(line_styles) - -fig, ax = plt.subplots(figsize=(12, 6)) -for i, column in enumerate(pivot_flow.columns): - pivot_flow[column].plot(ax=ax, linestyle=line_styles[i % num_styles],linewidth=1.5, alpha=0.8) - -# Set labels and title -ax.set_xlabel('Time') -ax.set_ylabel('Flow [m³/s]') -ax.legend(bbox_to_anchor=(1.15, 1), title="Edge") -plt.title('Flow Over Time') -plt.grid(True) -plt.show() -``` - -@fig-sim1 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. - -![Simulated basin level and storage](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-level-and-storage.png){fig-align="left" #fig-sim1} - -@fig-sim2 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. - -![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align="left" #fig-sim2} - -## Module 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 @fig-irrigation). - -![Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-irrigation.png){fig-align="left" #fig-irrigation} - -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. - -### 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. - -```python -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 -``` - -### 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. - -```python -model.basin.add( - Node(3, Point(-0.75, -0.5), name='Div'), - [basin.Profile(area=[500000, 5000000], level=[0, 6]), - basin.State(level=[3]), - basin.Time(time=[starttime, endtime]), - ], -) - -model.basin.add( - Node(4, Point(-1.5, -1), name='Conf'), - [basin.Profile(area=[672000, 5600000], level=[0, 6]), - basin.State(level=[4]), - basin.Time(time=[starttime, endtime]), - ], -) -``` - -### 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: - -```python -model.user_demand.add( - Node(6, Point(-1.5, 1.0), name='IrrA'), - [user_demand.Time( - demand=[0.0, 0.0, 10, 12, 12, 0.0], - return_factor=0, - min_level=0, - priority=1, - time=[starttime, "2022-03-31", "2022-04-01", "2022-07-01", "2022-09-30", "2022-10-01"] - ) - ] -) -``` - -### 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: - -```python -model.tabulated_rating_curve.add( - Node(7, Point(-1.125, -0.75), name='MainDiv'), - [tabulated_rating_curve.Static( - level=[0.0, 1.5, 5], - flow_rate=[0.0, 45, 200], - ) - ] -) -``` - -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. - -### 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: - -```python -model.terminal.add(Node(8, Point(-1.5, -3.0), name="Terminal")) - -model.edge.add(model.flow_boundary[1], model.basin[3]) -model.edge.add(model.flow_boundary[2], model.basin[4]) -model.edge.add(model.basin[3], model.user_demand[6]) -model.edge.add(model.user_demand[6], model.basin[4]) -model.edge.add(model.basin[3], model.tabulated_rating_curve[7]) -model.edge.add(model.tabulated_rating_curve[7], model.basin[4]) -model.edge.add(model.basin[4], model.tabulated_rating_curve[5]) -model.edge.add(model.tabulated_rating_curve[5], model.terminal[8]) -``` -### 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`: - -```python -model.plot() - -toml_path = model_dir / "Crystal_1.2/ribasim.toml" -model.write(toml_path) -rib_path = base_dir / "ribasim_windows/ribasim.exe" - -subprocess.run([rib_path, toml_path], check=True) -``` - -The schematization should look like @fig-cs12. - -![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align="left" #fig-cs12} - -### 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. - -```python -# 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', -} -``` - -### 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). - -```python - -df_basin_div = df_basin_wide.xs('Div', axis=1, level=1, drop_level=False) -df_basin_conf = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False) - -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): - ax.plot(df_basin.index, df_basin["level"][column], - linestyle='-', color=level_color, - label=f'Level - {column}') - - # Plot storage data - for idx, column in enumerate(df_basin["storage"].columns): - ax_twin.plot(df_basin.index, df_basin["storage"][column], - linestyle='--', color=storage_color, - label=f'Storage - {column}') - - ax.set_ylabel('Level [m]', color=level_color) - ax_twin.set_ylabel('Storage [m³]', color=storage_color) - - ax.tick_params(axis='y', labelcolor=level_color) - ax_twin.tick_params(axis='y', labelcolor=storage_color) - - ax.set_title(title) - - # Combine legends from both axes - lines, labels = ax.get_legend_handles_labels() - lines_twin, labels_twin = ax_twin.get_legend_handles_labels() - ax.legend(lines + lines_twin, labels + labels_twin, loc='upper left') - -# Create subplots -fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True) - -# Plot Div basin data -ax2 = ax1.twinx() # Secondary y-axis for storage -plot_basin_data(ax1, ax2, df_basin_div, title='Div Basin Level and Storage over Time') - -# Plot Conf basin data -ax4 = ax3.twinx() # Secondary y-axis for storage -plot_basin_data(ax3, ax4, df_basin_conf, title='Conf Basin Level and Storage over Time') - -# Common X label -ax3.set_xlabel('Time') -fig.tight_layout() # Adjust layout to fit labels -plt.show() -``` - -@fig-sim3 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. - -![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align="left" #fig-sim3} - -### Step 9: Plot and compare the flow results -Plot the flow results in an interactive plotting tool. - -```python -df_flow = 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) - -# Plot the flow data, interactive plot with Plotly -pivot_flow = df_flow.pivot_table(index="time", columns="name", values="flow_rate").reset_index() -fig = px.line(pivot_flow, x="time", y=pivot_flow.columns[1:], title="Flow Over Time [m3/s]") - -fig.update_layout(legend_title_text='Edge') -fig.write_html(model_dir/ "Crystal_1.2/plot_edges.html") -fig.show() - -``` - -The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4). - -![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align="left" #fig-sim4} - -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 (@fig-sim5) and the return flow remains zero, as the assumption defined before was that there is no drain. - -![Supplied irrigation and return flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Supplied-irrigation-and-return-flow.png){fig-align="left" #fig-sim5} - -@fig-sim6 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. - -![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align="left" #fig-sim6} - -# Module 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 @fig-reservoir). -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. - -![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align="left" #fig-reservoir} - -## Module 2.1 – Reservoir -### 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: - -```python -model.basin.add( - Node(3, Point(-0.75, -0.5), name='Rsv'), - [basin.Profile(area=[20000000, 32300000], level=[0, 7]), - basin.State(level=[3.5]), - basin.Time(time=[starttime, endtime]), - ], -) -``` - -### 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`. - -```python -toml_path = model_dir/ "Crystal_2.1/ribasim.toml" -model.write(toml_path) -rib_path = base_dir / "ribasim_windows/ribasim.exe" -``` - -```python -# 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', -} - -df_basin = pd.read_feather(model_dir / "Crystal_2.1/results/basin.arrow") -``` - -```python -# Create pivot tables and plot for basin data -df_basin_rsv = df_basin_wide.xs('Rsv', axis=1, level=1, drop_level=False) -df_basin_conf = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False) -``` - -```python -# Plot Rsv basin data -ax2 = ax1.twinx() # Secondary y-axis for storage -plot_basin_data(ax1, ax2, df_basin_rsv, title='Reservoir Level and Storage Over Time') -``` - -```python -# Sample data loading and preparation -df_flow = 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) - -# Plot the flow data, interactive plot with Plotly -pivot_flow = df_flow.pivot_table(index="time", columns="name", values="flow_rate").reset_index() -fig = px.line(pivot_flow, x="time", y=pivot_flow.columns[1:], title="Flow Over Time [m3/s]") - -fig.update_layout(legend_title_text='Edge') -fig.write_html(model_dir/ "Crystal_2.1/plot_edges.html") -fig.show() -``` - -### Step 3: Plotting results -@fig-sim7 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. - -![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align="left" #fig-sim7} - -## Module 2.2 – Public Water Supply - -### Step 1: Rename the saving files -Rename the files to `Crystal_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%$. - -```python -model.user_demand.add( - Node(9, Point(0.0, -0.25), name='PWS'), - [user_demand.Time( - # Total demand in m³/s - demand=[0.07, 0.08, 0.09, 0.10, 0.12, 0.14, 0.15, 0.14, 0.12, 0.10, 0.09, 0.08], - return_factor=0.6, - min_level=0, - priority=1, - 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" - ] - )] -) -``` - -### Step 3: Add the edges -The connection between the reservoir and the demand node needs to be defined: - -```python -model.edge.add(model.flow_boundary[1], model.basin[3]) -model.edge.add(model.flow_boundary[2], model.basin[4]) -model.edge.add(model.basin[3], model.user_demand[6]) -model.edge.add(model.basin[3], model.user_demand[9]) -model.edge.add(model.user_demand[6], model.basin[4]) -model.edge.add(model.user_demand[9], model.basin[4]) -model.edge.add(model.basin[3], model.tabulated_rating_curve[7]) -model.edge.add(model.tabulated_rating_curve[7], model.basin[4]) -model.edge.add(model.basin[4], model.tabulated_rating_curve[5]) -model.edge.add(model.tabulated_rating_curve[5], model.terminal[8]) -``` - -### Step 4: Adjust the name dictionaries - -```python -# 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', -} -``` -### Step 5: Check the simulated demands -@fig-sim8 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node. -@fig-sim9 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. - -![Simulated flows to and from the city](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-to-and-from-the-city.png){fig-align="left" #fig-sim8} - -![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels-reservoir.png){fig-align="left" #fig-sim9} From 46f3f5fce153d77428d4ce00849371fb0679dc8e Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 13:21:09 +0200 Subject: [PATCH 05/19] Split up cells --- docs/tutorial/quickstart.ipynb | 924 +++++++++++++++++++++++---------- 1 file changed, 651 insertions(+), 273 deletions(-) diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb index 985f35c40..b95932b6e 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/quickstart.ipynb @@ -28,8 +28,13 @@ "- **Set Up a Basic Ribasim Model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", "- **Evaluate the Impact of Demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", "- **Modify and Update Models**: Learn how to update existing models with new data and changes.\n", - "- **Analyze Simulation Results**: Use built-in tools to analyze and interpret the results of your simulations.\n", - "\n", + "- **Analyze Simulation Results**: Use built-in tools to analyze and interpret the results of your simulations." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "# Starting RIBASIM\n", "\n", "## System requirements\n", @@ -72,7 +77,13 @@ "\n", "- `QuickStartGuide.pdf`\n", "- `data`: Contains data inputs such as time series needed for running the case.\n", - "Additionally, your Python model (`.py`) and the results will also be saved in this folder.\n", + "Additionally, your Python model (`.py`) and the results will also be saved in this folder." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "# Module 1 - Crystal River Basin\n", "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 @fig-crystal-basin).\n", @@ -94,27 +105,40 @@ "### Step 1: Import packages\n", "Before building the model we need to import some modules.\n", "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`.\n", - "Import the following modules in python:\n", - "\n", - "```python\n", + "Import the following modules in python:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess # For running the model\n", "from pathlib import Path\n", + "\n", "import matplotlib.pyplot as plt\n", - "import numpy as np\n", "import pandas as pd\n", - "from ribasim import Allocation, Model, Node # The main library used for river basin modeling.\n", - "from ribasim.nodes import (\n", - " flow_boundary,\n", - " basin,\n", - " tabulated_rating_curve,\n", - " terminal\n", - ")\n", - "from shapely.geometry import Point\n", - "import subprocess # For running the model\n", - "```\n", + "from ribasim import Model, Node # The main library used for river basin modeling.\n", + "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve\n", + "from shapely.geometry import Point" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "### Step 2: Setup paths and model configuration\n", - "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:\n", - "```python\n", + "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:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "base_dir = Path(\"c:/Ribasim\")\n", "model_dir = base_dir / \"Crystal_Basin\"\n", "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", @@ -125,35 +149,62 @@ " starttime=starttime,\n", " endtime=endtime,\n", " crs=\"EPSG:4326\",\n", - ")\n", - "```\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 3: Flow boundary nodes\n", "Crystal Basin consists of two inflow points, the tributary and the main Crystal river, we will call them `Minor` and `Main` respectively.\n", "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", "This inflow data goes monthly from 2014 to 2023.\n", "However, for this exercise actual runtime is already defined in step 2.\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "data = pd.read_csv(data_path, sep=\";\")\n", - "data['sum']= data['minor']+data['main']\n", + "data[\"sum\"] = data[\"minor\"] + data[\"main\"]\n", "# Average and max inflow of the whole summed inflow data timeseries\n", "# From 2014 - 2023\n", - "print('Average inflowQ m3/s:',data['sum'].mean())\n", - "print('Maximum inflowQ m3/s:',data['sum'].max)\n", + "print(\"Average inflowQ m3/s:\", data[\"sum\"].mean())\n", + "print(\"Maximum inflowQ m3/s:\", data[\"sum\"].max)\n", "\n", "model.flow_boundary.add(\n", - " Node(1, Point(0.0, 0.0), name='Main'),\n", - " [flow_boundary.Time(time=data.time, flow_rate=data.main,\n", - " )]\n", + " Node(1, Point(0.0, 0.0), name=\"Main\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.main,\n", + " )\n", + " ],\n", ")\n", "\n", "model.flow_boundary.add(\n", - " Node(2, Point(-3.0, 0.0), name='Minor'),\n", - " [flow_boundary.Time(time=data.time, flow_rate=data.minor,\n", - " )]\n", - ")\n", - "```\n", + " Node(2, Point(-3.0, 0.0), name=\"Minor\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.minor,\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 4: Basin node (confluence)\n", "To schematize the confluence from the tributary we will use the Basin node.\n", @@ -171,17 +222,29 @@ "| $672000.0$ | $0.0$ |\n", "| $5600000.0$ | $6.0$ |\n", "\n", - "To specify the basin profile, the following code is used:\n", - "```python\n", + "To specify the basin profile, the following code is used:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.basin.add(\n", - " Node(3, Point(-1.5, -1), name='Conf'),\n", - " [basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", - " basin.State(level=[4]),\n", - " basin.Time(time=[starttime, endtime]),\n", + " Node(3, Point(-1.5, -1), name=\"Conf\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", " ],\n", - ")\n", - "```\n", - "\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "### Step 5: Tabulated rating curve\n", "In the previous step we implemented a Basin node that functions as a confluence.\n", "Conceptually, the basin acts like a bucket of water, accumulating inflows and then releasing them.\n", @@ -214,26 +277,53 @@ "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", "\n", "Taking this into account, add the `Tabulated Rating Curve` as follows:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.tabulated_rating_curve.add(\n", - " Node(4, Point(-1.5, -1.5), name='MainConf'),\n", - " [tabulated_rating_curve.Static(\n", - " level=[0.0, 2, 5],\n", - " flow_rate=[0.0, 50, 200],\n", + " Node(4, Point(-1.5, -1.5), name=\"MainConf\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 2, 5],\n", + " flow_rate=[0.0, 50, 200],\n", " )\n", - " ]\n", - ")\n", - "```\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 6: Terminal node\n", "Finally all the water will discharge into the ocean.\n", "Schematize this with the terminal node as it portrays the end point of the model.\n", "Besides the node number/name and location, no further input is needed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"Terminal\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", - "```python\n", - "model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"Terminal\"))\n", - "```\n", "\n", "### Step 7: Defining edges\n", "Implement the connections (edges) between the nodes, in the following order:\n", @@ -241,25 +331,51 @@ "1. Flow boundaries to the basin;\n", "2. Basin to the rating curve;\n", "3. Tabulated rating curve to the terminal.\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.edge.add(model.flow_boundary[1], model.basin[3])\n", "model.edge.add(model.flow_boundary[2], model.basin[3])\n", "model.edge.add(model.basin[3], model.tabulated_rating_curve[4])\n", - "model.edge.add(model.tabulated_rating_curve[4], model.terminal[5])\n", - "```\n", + "model.edge.add(model.tabulated_rating_curve[4], model.terminal[5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 8: Visualization and model execution\n", "Plot the schematization, write the model configuration to the `TOML` file.\n", "Name the output file `Crystal_1.1/ribasim.toml`:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.plot()\n", "\n", - "toml_path = model_dir/ \"Crystal_1.1/ribasim.toml\"\n", + "toml_path = model_dir / \"Crystal_1.1/ribasim.toml\"\n", "model.write(toml_path)\n", - "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", - "```\n", + "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "The schematization should look like @fig-cs11.\n", "\n", "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", @@ -269,20 +385,38 @@ "- ribasim.toml: The model configuration\n", "- database.gpkg: A geopackage containing the shapes of your schematization and the input data of the nodes used.\n", "\n", - "Now run the model:\n", - "```python\n", - "subprocess.run([rib_path, toml_path], check=True)\n", - "```\n", - "\n", + "Now run the model:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subprocess.run([rib_path, toml_path], check=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "### Step 9: Post-processing results\n", "Read the arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", "\n", "# Create pivot tables and plot for basin data\n", "df_basin_wide = df_basin.pivot_table(\n", - "index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", + " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", ")\n", "\n", "# Skip the first timestep as it’s the initialization step\n", @@ -292,24 +426,30 @@ "fig, ax1 = plt.subplots(figsize=(12, 6))\n", "\n", "# Plot level on the primary y-axis\n", - "color = 'b'\n", - "ax1.set_xlabel('Time')\n", - "ax1.set_ylabel('Level [m]', color=color)\n", + "color = \"b\"\n", + "ax1.set_xlabel(\"Time\")\n", + "ax1.set_ylabel(\"Level [m]\", color=color)\n", "ax1.plot(df_basin_wide.index, df_basin_wide[\"level\"], color=color)\n", - "ax1.tick_params(axis='y', labelcolor=color)\n", + "ax1.tick_params(axis=\"y\", labelcolor=color)\n", "\n", "# Create a secondary y-axis for storage\n", "ax2 = ax1.twinx()\n", - "color = 'r'\n", - "ax2.set_ylabel('Storage [m³]', color='r')\n", - "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"],linestyle='--', color=color)\n", - "ax2.tick_params(axis='y', labelcolor=color)\n", + "color = \"r\"\n", + "ax2.set_ylabel(\"Storage [m³]\", color=\"r\")\n", + "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"], linestyle=\"--\", color=color)\n", + "ax2.tick_params(axis=\"y\", labelcolor=color)\n", "\n", "fig.tight_layout() # Adjust layout to fit labels\n", - "plt.title('Basin Level and Storage Over Time')\n", - "plt.show()\n", - "\n", - "\n", + "plt.title(\"Basin Level and Storage Over Time\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Plot flow data\n", "# Read the data from feather format\n", "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", @@ -322,21 +462,29 @@ "# Skip the first timestep\n", "pivot_flow = pivot_flow.iloc[1:]\n", "\n", - "line_styles = ['-', '--', '-', '-.']\n", + "line_styles = [\"-\", \"--\", \"-\", \"-.\"]\n", "num_styles = len(line_styles)\n", "\n", "fig, ax = plt.subplots(figsize=(12, 6))\n", "for i, column in enumerate(pivot_flow.columns):\n", - " pivot_flow[column].plot(ax=ax, linestyle=line_styles[i % num_styles],linewidth=1.5, alpha=0.8)\n", + " pivot_flow[column].plot(\n", + " ax=ax, linestyle=line_styles[i % num_styles], linewidth=1.5, alpha=0.8\n", + " )\n", "\n", "# Set labels and title\n", - "ax.set_xlabel('Time')\n", - "ax.set_ylabel('Flow [m³/s]')\n", + "ax.set_xlabel(\"Time\")\n", + "ax.set_ylabel(\"Flow [m³/s]\")\n", "ax.legend(bbox_to_anchor=(1.15, 1), title=\"Edge\")\n", - "plt.title('Flow Over Time')\n", + "plt.title(\"Flow Over Time\")\n", "plt.grid(True)\n", - "plt.show()\n", - "```\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "@fig-sim1 shows the storage and levels in the basin node.\n", "\n", @@ -371,23 +519,30 @@ "### Step 1: Setup the model & adjust import packages\n", "Copy and paste the python script `Crystal_1.1` and rename it `Crystal_1.2`.\n", "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.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess # For running the model\n", "\n", - "```python\n", "import matplotlib.pyplot as plt\n", - "import numpy as np\n", "import pandas as pd\n", - "from ribasim import Allocation, Model, Node # The main library used for river basin modeling.\n", - "from ribasim.nodes import (\n", - " flow_boundary,\n", - " basin,\n", - " tabulated_rating_curve,\n", - " user_demand,\n", - " terminal\n", - ")\n", - "from shapely.geometry import Point\n", - "import subprocess # For running the model\n", "import plotly.express as px\n", - "```\n", + "from ribasim import Model, Node # The main library used for river basin modeling.\n", + "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve, user_demand\n", + "from shapely.geometry import Point" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 2: Add a second Basin node\n", "Schematically we are dealing with two basins.\n", @@ -395,24 +550,39 @@ "In this case `node_id = 3` will be `node_id = 4`.\n", "Basin 3 will portray as the point in the river where the diversion takes place, getting the name `Div`.\n", "Its profile area at this intersection is slightly smaller than at the confluence.\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name='Div'),\n", - " [basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", - " basin.State(level=[3]),\n", - " basin.Time(time=[starttime, endtime]),\n", + " Node(3, Point(-0.75, -0.5), name=\"Div\"),\n", + " [\n", + " basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", + " basin.State(level=[3]),\n", + " basin.Time(time=[starttime, endtime]),\n", " ],\n", ")\n", "\n", "model.basin.add(\n", - " Node(4, Point(-1.5, -1), name='Conf'),\n", - " [basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", - " basin.State(level=[4]),\n", - " basin.Time(time=[starttime, endtime]),\n", + " Node(4, Point(-1.5, -1), name=\"Conf\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", " ],\n", - ")\n", - "```\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 3: Add the irrigation demand\n", "A big farm company needs to apply irrigation to its field starting from April to September.\n", @@ -424,44 +594,86 @@ "For now, let’s assume the return flow remains $0.0$ (`return_factor`).\n", "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", "The user demand node interpolates the demand values. Thus the following code needs to be implemented:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.user_demand.add(\n", - " Node(6, Point(-1.5, 1.0), name='IrrA'),\n", - " [user_demand.Time(\n", - " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", - " return_factor=0,\n", - " min_level=0,\n", - " priority=1,\n", - " time=[starttime, \"2022-03-31\", \"2022-04-01\", \"2022-07-01\", \"2022-09-30\", \"2022-10-01\"]\n", + " Node(6, Point(-1.5, 1.0), name=\"IrrA\"),\n", + " [\n", + " user_demand.Time(\n", + " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", + " return_factor=0,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[\n", + " starttime,\n", + " \"2022-03-31\",\n", + " \"2022-04-01\",\n", + " \"2022-07-01\",\n", + " \"2022-09-30\",\n", + " \"2022-10-01\",\n", + " ],\n", " )\n", - " ]\n", - ")\n", - "```\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 4: Add a tabulated rating curve\n", "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.\n", "The rest of the water will flow naturally towards the confluence:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.tabulated_rating_curve.add(\n", - " Node(7, Point(-1.125, -0.75), name='MainDiv'),\n", - " [tabulated_rating_curve.Static(\n", - " level=[0.0, 1.5, 5],\n", - " flow_rate=[0.0, 45, 200],\n", + " Node(7, Point(-1.125, -0.75), name=\"MainDiv\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 1.5, 5],\n", + " flow_rate=[0.0, 45, 200],\n", " )\n", - " ]\n", - ")\n", - "```\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", - "It is up to the user to renumber the ID’s of the nodes.\n", + "It is up to the user to renumber the IDs of the nodes.\n", "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", "\n", "### Step 5: Adjust the terminal node id and edges\n", "Adjust the terminal node id.\n", "Since we added more nodes we have more edges. Add and adjust the edges:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.terminal.add(Node(8, Point(-1.5, -3.0), name=\"Terminal\"))\n", "\n", "model.edge.add(model.flow_boundary[1], model.basin[3])\n", @@ -471,21 +683,40 @@ "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", - "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])\n", - "```\n", + "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "### Step 6: Plot model and run\n", "Plot the schematization and run the model.\n", "This time the new outputs should be written in a new folder called `Crystal_1.2`:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.plot()\n", "\n", "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", "model.write(toml_path)\n", "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", "\n", - "subprocess.run([rib_path, toml_path], check=True)\n", - "```\n", + "subprocess.run([rib_path, toml_path], check=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "The schematization should look like @fig-cs12.\n", "\n", @@ -494,77 +725,114 @@ "### Step 7: Name the edges and basins\n", "The names of each nodes are defined and saved in the geopackage.\n", "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe.\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Dictionary mapping node_ids to names\n", "edge_names = {\n", - " (1,3): 'Main',\n", - " (2,4): 'Minor',\n", - " (3,6): 'IrrA Demand',\n", - " (6,4): 'IrrA Drain',\n", - " (3,7): 'Div2Main',\n", - " (7,4): 'Main2Conf',\n", - " (4,5): 'Conf2TRC',\n", - " (5,8): 'TRC2Term',\n", + " (1, 3): \"Main\",\n", + " (2, 4): \"Minor\",\n", + " (3, 6): \"IrrA Demand\",\n", + " (6, 4): \"IrrA Drain\",\n", + " (3, 7): \"Div2Main\",\n", + " (7, 4): \"Main2Conf\",\n", + " (4, 5): \"Conf2TRC\",\n", + " (5, 8): \"TRC2Term\",\n", "}\n", "\n", "# Dictionary mapping basins (node_ids) to names\n", "node_names = {\n", - " 3: 'Div',\n", - " 4: 'Conf',\n", - "}\n", - "```\n", + " 3: \"Div\",\n", + " 4: \"Conf\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 8: Plot and compare the basin results\n", "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_basin_div = df_basin_wide.xs(\"Div\", axis=1, level=1, drop_level=False)\n", + "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", "\n", - "```python\n", - "\n", - "df_basin_div = df_basin_wide.xs('Div', axis=1, level=1, drop_level=False)\n", - "df_basin_conf = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False)\n", "\n", - "def plot_basin_data(ax, ax_twin, df_basin, level_color='b', storage_color='r', title='Basin'):\n", + "def plot_basin_data(\n", + " ax, ax_twin, df_basin, level_color=\"b\", storage_color=\"r\", title=\"Basin\"\n", + "):\n", " # Plot level data\n", " for idx, column in enumerate(df_basin[\"level\"].columns):\n", - " ax.plot(df_basin.index, df_basin[\"level\"][column],\n", - " linestyle='-', color=level_color,\n", - " label=f'Level - {column}')\n", + " ax.plot(\n", + " df_basin.index,\n", + " df_basin[\"level\"][column],\n", + " linestyle=\"-\",\n", + " color=level_color,\n", + " label=f\"Level - {column}\",\n", + " )\n", "\n", " # Plot storage data\n", " for idx, column in enumerate(df_basin[\"storage\"].columns):\n", - " ax_twin.plot(df_basin.index, df_basin[\"storage\"][column],\n", - " linestyle='--', color=storage_color,\n", - " label=f'Storage - {column}')\n", + " ax_twin.plot(\n", + " df_basin.index,\n", + " df_basin[\"storage\"][column],\n", + " linestyle=\"--\",\n", + " color=storage_color,\n", + " label=f\"Storage - {column}\",\n", + " )\n", "\n", - " ax.set_ylabel('Level [m]', color=level_color)\n", - " ax_twin.set_ylabel('Storage [m³]', color=storage_color)\n", + " ax.set_ylabel(\"Level [m]\", color=level_color)\n", + " ax_twin.set_ylabel(\"Storage [m³]\", color=storage_color)\n", "\n", - " ax.tick_params(axis='y', labelcolor=level_color)\n", - " ax_twin.tick_params(axis='y', labelcolor=storage_color)\n", + " ax.tick_params(axis=\"y\", labelcolor=level_color)\n", + " ax_twin.tick_params(axis=\"y\", labelcolor=storage_color)\n", "\n", " ax.set_title(title)\n", "\n", " # Combine legends from both axes\n", " lines, labels = ax.get_legend_handles_labels()\n", " lines_twin, labels_twin = ax_twin.get_legend_handles_labels()\n", - " ax.legend(lines + lines_twin, labels + labels_twin, loc='upper left')\n", + " ax.legend(lines + lines_twin, labels + labels_twin, loc=\"upper left\")\n", + "\n", "\n", "# Create subplots\n", "fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True)\n", "\n", "# Plot Div basin data\n", "ax2 = ax1.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax1, ax2, df_basin_div, title='Div Basin Level and Storage over Time')\n", + "plot_basin_data(ax1, ax2, df_basin_div, title=\"Div Basin Level and Storage over Time\")\n", "\n", "# Plot Conf basin data\n", "ax4 = ax3.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax3, ax4, df_basin_conf, title='Conf Basin Level and Storage over Time')\n", + "plot_basin_data(ax3, ax4, df_basin_conf, title=\"Conf Basin Level and Storage over Time\")\n", "\n", "# Common X label\n", - "ax3.set_xlabel('Time')\n", + "ax3.set_xlabel(\"Time\")\n", "fig.tight_layout() # Adjust layout to fit labels\n", - "plt.show()\n", - "```\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", "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.\n", @@ -576,21 +844,37 @@ "\n", "### Step 9: Plot and compare the flow results\n", "Plot the flow results in an interactive plotting tool.\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "df_flow = pd.read_feather(model_dir / \"Crystal_1.2/results/flow.arrow\")\n", "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", "\n", "# Plot the flow data, interactive plot with Plotly\n", - "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"name\", values=\"flow_rate\").reset_index()\n", - "fig = px.line(pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\")\n", + "pivot_flow = df_flow.pivot_table(\n", + " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", + ").reset_index()\n", + "fig = px.line(\n", + " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", + ")\n", "\n", - "fig.update_layout(legend_title_text='Edge')\n", - "fig.write_html(model_dir/ \"Crystal_1.2/plot_edges.html\")\n", - "fig.show()\n", + "fig.update_layout(legend_title_text=\"Edge\")\n", + "fig.write_html(model_dir / \"Crystal_1.2/plot_edges.html\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", - "```\n", "\n", "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", "\n", @@ -604,7 +888,13 @@ "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", "Indicating the impact of irrigation without any drain.\n", "\n", - "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}\n", + "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "# Module 2 – Reservoirs and Public Water Supply\n", "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", @@ -618,74 +908,104 @@ "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).\n", "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", "The profile of basin 3 should change to:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name='Rsv'),\n", - " [basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", - " basin.State(level=[3.5]),\n", - " basin.Time(time=[starttime, endtime]),\n", + " Node(3, Point(-0.75, -0.5), name=\"Rsv\"),\n", + " [\n", + " basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", + " basin.State(level=[3.5]),\n", + " basin.Time(time=[starttime, endtime]),\n", " ],\n", - ")\n", - "```\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 2: Adjust the code\n", "Adjust the naming of the basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`.\n", - "\n", - "```python\n", - "toml_path = model_dir/ \"Crystal_2.1/ribasim.toml\"\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "toml_path = model_dir / \"Crystal_2.1/ribasim.toml\"\n", "model.write(toml_path)\n", "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", - "```\n", "\n", - "```python\n", "# Dictionary mapping node_ids to names\n", "edge_names = {\n", - " (1,3): 'Main',\n", - " (2,4): 'Minor',\n", - " (3,6): 'IrrA Demand',\n", - " (6,4): 'IrrA Drain',\n", - " (3,7): 'Rsv2Main',\n", - " (7,4): 'Main2Conf',\n", - " (4,5): 'Conf2TRC',\n", - " (5,8): 'TRC2Term',\n", + " (1, 3): \"Main\",\n", + " (2, 4): \"Minor\",\n", + " (3, 6): \"IrrA Demand\",\n", + " (6, 4): \"IrrA Drain\",\n", + " (3, 7): \"Rsv2Main\",\n", + " (7, 4): \"Main2Conf\",\n", + " (4, 5): \"Conf2TRC\",\n", + " (5, 8): \"TRC2Term\",\n", "}\n", "\n", "# Dictionary mapping basins (node_ids) to names\n", "node_names = {\n", - " 3: 'Rsv',\n", - " 4: 'Conf',\n", + " 3: \"Rsv\",\n", + " 4: \"Conf\",\n", "}\n", "\n", "df_basin = pd.read_feather(model_dir / \"Crystal_2.1/results/basin.arrow\")\n", - "```\n", "\n", - "```python\n", "# Create pivot tables and plot for basin data\n", - "df_basin_rsv = df_basin_wide.xs('Rsv', axis=1, level=1, drop_level=False)\n", - "df_basin_conf = df_basin_wide.xs('Conf', axis=1, level=1, drop_level=False)\n", - "```\n", + "df_basin_rsv = df_basin_wide.xs(\"Rsv\", axis=1, level=1, drop_level=False)\n", + "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", "\n", - "```python\n", "# Plot Rsv basin data\n", "ax2 = ax1.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax1, ax2, df_basin_rsv, title='Reservoir Level and Storage Over Time')\n", - "```\n", - "\n", - "```python\n", + "plot_basin_data(ax1, ax2, df_basin_rsv, title=\"Reservoir Level and Storage Over Time\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Sample data loading and preparation\n", "df_flow = pd.read_feather(model_dir / \"Crystal_2.1/results/flow.arrow\")\n", "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", "\n", "# Plot the flow data, interactive plot with Plotly\n", - "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"name\", values=\"flow_rate\").reset_index()\n", - "fig = px.line(pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\")\n", + "pivot_flow = df_flow.pivot_table(\n", + " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", + ").reset_index()\n", + "fig = px.line(\n", + " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", + ")\n", + "\n", + "fig.update_layout(legend_title_text=\"Edge\")\n", + "fig.write_html(model_dir / \"Crystal_2.1/plot_edges.html\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", - "fig.update_layout(legend_title_text='Edge')\n", - "fig.write_html(model_dir/ \"Crystal_2.1/plot_edges.html\")\n", - "fig.show()\n", - "```\n", "\n", "### Step 3: Plotting results\n", "@fig-sim7 illustrates the new storage and water level at the reservoir.\n", @@ -701,38 +1021,73 @@ "### Step 2: Add a demand node\n", "$50.000$ people live in Crystal City.\n", "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%$.\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.user_demand.add(\n", - " Node(9, Point(0.0, -0.25), name='PWS'),\n", - " [user_demand.Time(\n", - " # Total demand in m³/s\n", - " demand=[0.07, 0.08, 0.09, 0.10, 0.12, 0.14, 0.15, 0.14, 0.12, 0.10, 0.09, 0.08],\n", - " return_factor=0.6,\n", - " min_level=0,\n", - " priority=1,\n", - " time=[\n", - " starttime,\n", - " \"2022-02-01\",\n", - " \"2022-03-01\",\n", - " \"2022-04-01\",\n", - " \"2022-05-01\",\n", - " \"2022-06-01\",\n", - " \"2022-07-01\",\n", - " \"2022-08-01\",\n", - " \"2022-09-01\",\n", - " \"2022-10-01\",\n", - " \"2022-11-01\",\n", - " \"2022-12-01\"\n", - " ]\n", - " )]\n", - ")\n", - "```\n", + " Node(9, Point(0.0, -0.25), name=\"PWS\"),\n", + " [\n", + " user_demand.Time(\n", + " # Total demand in m³/s\n", + " demand=[\n", + " 0.07,\n", + " 0.08,\n", + " 0.09,\n", + " 0.10,\n", + " 0.12,\n", + " 0.14,\n", + " 0.15,\n", + " 0.14,\n", + " 0.12,\n", + " 0.10,\n", + " 0.09,\n", + " 0.08,\n", + " ],\n", + " return_factor=0.6,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[\n", + " starttime,\n", + " \"2022-02-01\",\n", + " \"2022-03-01\",\n", + " \"2022-04-01\",\n", + " \"2022-05-01\",\n", + " \"2022-06-01\",\n", + " \"2022-07-01\",\n", + " \"2022-08-01\",\n", + " \"2022-09-01\",\n", + " \"2022-10-01\",\n", + " \"2022-11-01\",\n", + " \"2022-12-01\",\n", + " ],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", "### Step 3: Add the edges\n", "The connection between the reservoir and the demand node needs to be defined:\n", - "\n", - "```python\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "model.edge.add(model.flow_boundary[1], model.basin[3])\n", "model.edge.add(model.flow_boundary[2], model.basin[4])\n", "model.edge.add(model.basin[3], model.user_demand[6])\n", @@ -742,26 +1097,45 @@ "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", - "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])\n", - "```\n", + "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", - "### Step 4: Adjust the name dictionaries\n", "\n", - "```python\n", + "### Step 4: Adjust the name dictionaries\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Dictionary mapping node_ids to names\n", "edge_names = {\n", - " (1,3): 'Main',\n", - " (2,4): 'Minor',\n", - " (3,6): 'IrrA Demand',\n", - " (6,4): 'IrrA Drain',\n", - " (3,9): 'PWS Demand',\n", - " (9,4): 'PWS Return',\n", - " (3,7): 'Rsv2Main',\n", - " (7,4): 'Main2Conf',\n", - " (4,5): 'Conf2TRC',\n", - " (5,8): 'TRC2Term',\n", - "}\n", - "```\n", + " (1, 3): \"Main\",\n", + " (2, 4): \"Minor\",\n", + " (3, 6): \"IrrA Demand\",\n", + " (6, 4): \"IrrA Drain\",\n", + " (3, 9): \"PWS Demand\",\n", + " (9, 4): \"PWS Return\",\n", + " (3, 7): \"Rsv2Main\",\n", + " (7, 4): \"Main2Conf\",\n", + " (4, 5): \"Conf2TRC\",\n", + " (5, 8): \"TRC2Term\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "### Step 5: Check the simulated demands\n", "@fig-sim8 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node.\n", "@fig-sim9 shows the downstream flow to the ocean.\n", @@ -779,6 +1153,10 @@ "display_name": "Python 3", "language": "python", "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.5" } }, "nbformat": 4, From 0f15bdecf24b0a319817d2ffff1c3eb66d53da29 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 13:48:37 +0200 Subject: [PATCH 06/19] Remove section numbering from headers --- docs/install.qmd | 3 +- docs/tutorial/quickstart.ipynb | 211 +++++++++------------------------ 2 files changed, 61 insertions(+), 153 deletions(-) diff --git a/docs/install.qmd b/docs/install.qmd index 650776765..e5ecaba9a 100644 --- a/docs/install.qmd +++ b/docs/install.qmd @@ -101,7 +101,8 @@ One can also use Ribasim Python to build entire models from base data, such that setup is fully reproducible. The Ribasim Python package is [registered in PyPI](https://pypi.org/project/ribasim/) and [conda-forge](https://prefix.dev/channels/conda-forge/packages/ribasim) and can therefore be installed with [pip](https://docs.python.org/3/installing/index.html), [conda](https://docs.conda.io/) or [pixi](https://pixi.sh/): -``` + +```sh pip install ribasim ``` diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb index b95932b6e..f830bb759 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/quickstart.ipynb @@ -12,14 +12,14 @@ "\n", "# Introduction\n", "Welcome to Ribasim!\n", - "This guide will help you get started with the basics of installing and using Ribasim for river basin simulation.\n", - "In this guide, the schematization of models will be implemented in Python using the Ribasim Python package.\n", + "This guide will help you get started with the basics of using Ribasim for river basin simulation.\n", + "In this tutorial, the schematization of models will be implemented in Python using the Ribasim Python package.\n", "The Ribasim package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", "It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible.\n", "This package is available on PyPI.\n", "\n", "## Learning objectives\n", - "In this guide, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", + "In this tutorial, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", "The guide is divided into different modules, each covering various scenarios.\n", "These include simulating natural flow, implementing reservoirs, and observing the impact of other structures.\n", "While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations.\n", @@ -28,51 +28,11 @@ "- **Set Up a Basic Ribasim Model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", "- **Evaluate the Impact of Demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", "- **Modify and Update Models**: Learn how to update existing models with new data and changes.\n", - "- **Analyze Simulation Results**: Use built-in tools to analyze and interpret the results of your simulations." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Starting RIBASIM\n", - "\n", - "## System requirements\n", - "Before installing Ribasim, ensure your system meets the following requirements:\n", - "\n", - "- Operating System: Windows 10 or later, or Linux (latest distributions)\n", - "- Processor: x86-64 (64-bit)\n", - "- RAM: 4 GB minimum, 8 GB recommended\n", - "- Hard Drive: 1GB of free space\n", - "\n", - "## Installation\n", - "1. Download Ribasim: Obtain the Ribasim 9 installation package from the official website: [Ribasim - Installation](https://ribasim.org/install.html) under chapter '2 Download':\n", + "- **Analyze Simulation Results**: Use built-in tools to analyze and interpret the results of your simulations.\n", "\n", - "* For Windows download: `ribasim_windows.zip`\n", - "* For Linux: `ribasim_linux.zip`\n", + "## Prerequisites\n", + "First install the latest release of Ribasim as documented in [the installation guide](/install.qmd).\n", "\n", - "2. 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;\n", - "3. Check installation: To check whether the installation was performed successfully, in `cmd` go to the executable path and type `ribasim` with no arguments in the command line. This will give the following message:\n", - "\n", - "```batch\n", - "error: the following required arguments were not provided:\n", - " \n", - "Usage: ribasim \n", - "For more information, try '--help'.\n", - "```\n", - "\n", - "4. We use a command line interface (CLI) to install our Ribasim package. To install Ribasim open PowerShell or Windows command prompt and write:\n", - "\n", - "```sh\n", - "conda install ribasim\n", - "```\n", - "or\n", - "\n", - "```sh\n", - "mamba install ribasim\n", - "```\n", - "\n", - "## Data preparation\n", "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:\n", "\n", "- `QuickStartGuide.pdf`\n", @@ -85,7 +45,7 @@ "metadata": {}, "source": [ "\n", - "# Module 1 - Crystal River Basin\n", + "# Crystal River Basin\n", "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 @fig-crystal-basin).\n", "An average discharge of $44.45 \\text{ m}^3/\\text{s}$ is measured at the confluence.\n", "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", @@ -100,9 +60,9 @@ "- Generate overview of results\n", "- Evaluate the simulation results\n", "\n", - "## Module 1.1 - Natural Flow\n", + "## Natural Flow\n", "\n", - "### Step 1: Import packages\n", + "### Import packages\n", "Before building the model we need to import some modules.\n", "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`.\n", "Import the following modules in python:" @@ -158,7 +118,7 @@ "source": [ "\n", "\n", - "### Step 3: Flow boundary nodes\n", + "### Flow boundary nodes\n", "Crystal Basin consists of two inflow points, the tributary and the main Crystal river, we will call them `Minor` and `Main` respectively.\n", "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", "This inflow data goes monthly from 2014 to 2023.\n", @@ -206,7 +166,7 @@ "source": [ "\n", "\n", - "### Step 4: Basin node (confluence)\n", + "### Basin node (confluence)\n", "To schematize the confluence from the tributary we will use the Basin node.\n", "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.\n", "@fig-confluence visualizes a cross section of the confluence point in our model.\n", @@ -245,7 +205,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Step 5: Tabulated rating curve\n", + "### Tabulated rating curve\n", "In the previous step we implemented a Basin node that functions as a confluence.\n", "Conceptually, the basin acts like a bucket of water, accumulating inflows and then releasing them.\n", "However, the model does not run if the basin is directly connected to the terminal node.\n", @@ -301,13 +261,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 6: Terminal node\n", + "### Terminal node\n", "Finally all the water will discharge into the ocean.\n", "Schematize this with the terminal node as it portrays the end point of the model.\n", - "Besides the node number/name and location, no further input is needed.\n", - "\n" + "Besides the node number/name and location, no further input is needed." ] }, { @@ -323,15 +280,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 7: Defining edges\n", + "### Defining edges\n", "Implement the connections (edges) between the nodes, in the following order:\n", "\n", "1. Flow boundaries to the basin;\n", "2. Basin to the rating curve;\n", - "3. Tabulated rating curve to the terminal.\n", - "\n" + "3. Tabulated rating curve to the terminal." ] }, { @@ -350,12 +304,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 8: Visualization and model execution\n", + "### Visualization and model execution\n", "Plot the schematization, write the model configuration to the `TOML` file.\n", - "Name the output file `Crystal_1.1/ribasim.toml`:\n", - "\n" + "Name the output file `Crystal_1.1/ribasim.toml`:" ] }, { @@ -401,9 +352,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Step 9: Post-processing results\n", - "Read the arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:\n", - "\n" + "### Post-processing results\n", + "Read the arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:" ] }, { @@ -484,8 +434,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "@fig-sim1 shows the storage and levels in the basin node.\n", "\n", "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.\n", @@ -503,7 +451,7 @@ "\n", "![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align=\"left\" #fig-sim2}\n", "\n", - "## Module 1.2 - Irrigation demand\n", + "## Irrigation demand\n", "\n", "Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation.\n", "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 @fig-irrigation).\n", @@ -516,10 +464,9 @@ "- User Demand: Represents the irrigation demand.\n", "- Tabulates Rating Curve: Defines the remaining water flow from the main river at the diversion point.\n", "\n", - "### Step 1: Setup the model & adjust import packages\n", + "### Setup the model & adjust import packages\n", "Copy and paste the python script `Crystal_1.1` and rename it `Crystal_1.2`.\n", - "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.\n", - "\n" + "The 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." ] }, { @@ -542,15 +489,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 2: Add a second Basin node\n", + "### Add a second Basin node\n", "Schematically we are dealing with two basins.\n", "To keep the order of flow from upstream to downstream it is recommended to adjust the node_id numbers accordingly.\n", "In this case `node_id = 3` will be `node_id = 4`.\n", "Basin 3 will portray as the point in the river where the diversion takes place, getting the name `Div`.\n", - "Its profile area at this intersection is slightly smaller than at the confluence.\n", - "\n" + "Its profile area at this intersection is slightly smaller than at the confluence." ] }, { @@ -582,9 +526,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 3: Add the irrigation demand\n", + "### Add the irrigation demand\n", "A big farm company needs to apply irrigation to its field starting from April to September.\n", "The irrigated field is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", "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.\n", @@ -593,8 +535,7 @@ "\n", "For now, let’s assume the return flow remains $0.0$ (`return_factor`).\n", "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", - "The user demand node interpolates the demand values. Thus the following code needs to be implemented:\n", - "\n" + "The user demand node interpolates the demand values. Thus the following code needs to be implemented:" ] }, { @@ -628,12 +569,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 4: Add a tabulated rating curve\n", - "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.\n", - "The rest of the water will flow naturally towards the confluence:\n", - "\n" + "### Add a TabulatedRatingCurve\n", + "The second TabulatedRatingCurve node will simulate the rest of the water that is left after diverting a part from the main river to the farm field.\n", + "The rest of the water will flow naturally towards the confluence:" ] }, { @@ -657,15 +595,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "It is up to the user to renumber the IDs of the nodes.\n", "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", "\n", - "### Step 5: Adjust the terminal node id and edges\n", - "Adjust the terminal node id.\n", - "Since we added more nodes we have more edges. Add and adjust the edges:\n", - "\n" + "### Adjust the Terminal node ID and edges\n", + "Adjust the Terminal node ID.\n", + "Since we added more nodes we have more edges. Add and adjust the edges:" ] }, { @@ -690,11 +625,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "### Step 6: Plot model and run\n", + "### Plot model and run\n", "Plot the schematization and run the model.\n", - "This time the new outputs should be written in a new folder called `Crystal_1.2`:\n", - "\n" + "This time the new outputs should be written in a new folder called `Crystal_1.2`:" ] }, { @@ -716,16 +649,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "The schematization should look like @fig-cs12.\n", "\n", "![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-cs12}\n", "\n", - "### Step 7: Name the edges and basins\n", + "### Name the edges and basins\n", "The names of each nodes are defined and saved in the geopackage.\n", - "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe.\n", - "\n" + "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe." ] }, { @@ -757,11 +687,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 8: Plot and compare the basin results\n", - "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4).\n", - "\n" + "### Plot and compare the basin results\n", + "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4)." ] }, { @@ -832,8 +759,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", "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.\n", "\n", @@ -842,9 +767,8 @@ "\n", "![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align=\"left\" #fig-sim3}\n", "\n", - "### Step 9: Plot and compare the flow results\n", - "Plot the flow results in an interactive plotting tool.\n", - "\n" + "### Plot and compare the flow results\n", + "Plot the flow results in an interactive plotting tool." ] }, { @@ -874,8 +798,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", "\n", "![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align=\"left\" #fig-sim4}\n", @@ -888,27 +810,25 @@ "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", "Indicating the impact of irrigation without any drain.\n", "\n", - "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}\n" + "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "# Module 2 – Reservoirs and Public Water Supply\n", + "# Reservoirs and Public Water Supply\n", "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", "The reservoir is to help ensure a reliable supply during dry periods.\n", "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal Basin.\n", "\n", "![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align=\"left\" #fig-reservoir}\n", "\n", - "## Module 2.1 – Reservoir\n", - "### Step 1: Add a basin\n", - "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).\n", + "## Reservoir\n", + "### Add a Basin\n", + "This time 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).\n", "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", - "The profile of basin 3 should change to:\n", - "\n" + "The profile of Basin #3 should change to:" ] }, { @@ -931,11 +851,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 2: Adjust the code\n", - "Adjust the naming of the basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`.\n", - "\n" + "### Adjust the code\n", + "Adjust the naming of the Basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`." ] }, { @@ -1005,23 +922,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 3: Plotting results\n", + "### Plotting results\n", "@fig-sim7 illustrates the new storage and water level at the reservoir.\n", "As expected, after increasing the profile of basin 3 to mimic the reservoir, its storage capacity increased as well.\n", "\n", "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align=\"left\" #fig-sim7}\n", "\n", - "## Module 2.2 – Public Water Supply\n", + "## Public Water Supply\n", "\n", - "### Step 1: Rename the saving files\n", + "### Rename the saving files\n", "Rename the files to `Crystal_2.2`\n", "\n", - "### Step 2: Add a demand node\n", - "$50.000$ people live in Crystal City.\n", - "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%$.\n", - "\n" + "### Add a demand node\n", + "50.000 people live in Crystal City.\n", + "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%." ] }, { @@ -1075,11 +989,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 3: Add the edges\n", - "The connection between the reservoir and the demand node needs to be defined:\n", - "\n" + "### Add the edges\n", + "The connection between the reservoir and the demand node needs to be defined:" ] }, { @@ -1104,10 +1015,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Step 4: Adjust the name dictionaries\n", - "\n" + "### Adjust the name dictionaries" ] }, { @@ -1135,8 +1043,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "### Step 5: Check the simulated demands\n", + "### Check the simulated demands\n", "@fig-sim8 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node.\n", "@fig-sim9 shows the downstream flow to the ocean.\n", "The impact is clear.\n", From 1c37a07b123437a36099faef815bfb0c9aec3263 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 14:26:23 +0200 Subject: [PATCH 07/19] edits --- docs/tutorial/quickstart.ipynb | 67 +++++++++++++++++----------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb index f830bb759..bfa281658 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/quickstart.ipynb @@ -6,29 +6,33 @@ "source": [ "---\n", "title: \"Quick start guide\"\n", - "---\n", - "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align=\"left\"}\n", "\n", "# Introduction\n", "Welcome to Ribasim!\n", - "This guide will help you get started with the basics of using Ribasim for river basin simulation.\n", - "In this tutorial, the schematization of models will be implemented in Python using the Ribasim Python package.\n", - "The Ribasim package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", + "This tutorial will help you get started with the basics of using Ribasim for river basin simulation.\n", + "In this tutorial, the schematization of models is done in Python using the Ribasim Python package.\n", + "The Ribasim Python package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", "It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible.\n", - "This package is available on PyPI.\n", "\n", "## Learning objectives\n", "In this tutorial, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", "The guide is divided into different modules, each covering various scenarios.\n", "These include simulating natural flow, implementing reservoirs, and observing the impact of other structures.\n", "While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations.\n", - "By the end of the guide, users will be able to:\n", + "By the end of the tutorial, users will be able to:\n", "\n", - "- **Set Up a Basic Ribasim Model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", - "- **Evaluate the Impact of Demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", - "- **Modify and Update Models**: Learn how to update existing models with new data and changes.\n", - "- **Analyze Simulation Results**: Use built-in tools to analyze and interpret the results of your simulations.\n", + "- **Set up a basic Ribasim model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", + "- **Evaluate the impact of demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", + "- **Modify and update models**: Learn how to update existing models with new data and changes.\n", + "- **Analyze simulation results**: Use built-in tools to analyze and interpret the results of your simulations.\n", "\n", "## Prerequisites\n", "First install the latest release of Ribasim as documented in [the installation guide](/install.qmd).\n", @@ -44,10 +48,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", "# Crystal River Basin\n", - "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 @fig-crystal-basin).\n", - "An average discharge of $44.45 \\text{ m}^3/\\text{s}$ is measured at the confluence.\n", + "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 @fig-crystal-basin).\n", + "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", "\n", @@ -56,16 +59,16 @@ "After this module the user will be able to:\n", "\n", "- Build a river basin model from scratch\n", - "- Understand the functionality of the ‘demand’ and ‘basin’ nodes\n", + "- Understand the functionality of the Demand and Basin nodes\n", "- Generate overview of results\n", "- Evaluate the simulation results\n", "\n", - "## Natural Flow\n", + "## Natural flow\n", "\n", "### Import packages\n", "Before building the model we need to import some modules.\n", - "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`.\n", - "Import the following modules in python:" + "Open your favorite Python editor (Visual Studio Code, Jupyter, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", + "Import the following modules in Python:" ] }, { @@ -79,7 +82,7 @@ "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", - "from ribasim import Model, Node # The main library used for river basin modeling.\n", + "from ribasim import Model, Node\n", "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve\n", "from shapely.geometry import Point" ] @@ -88,9 +91,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "### Step 2: Setup paths and model configuration\n", - "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:\n" + "### Setup paths and model configuration\n", + "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.\n", + "The coordinate reference system (CRS) is also required, and set to [EPSG:4326](https://epsg.io/4326), which means all coordinates are interpreted as latitude and longitude values.\n", + "The CRS is important for correctly placing Ribasim models on the map, but since this is a fictional model, it is not important." ] }, { @@ -99,7 +103,7 @@ "metadata": {}, "outputs": [], "source": [ - "base_dir = Path(\"c:/Ribasim\")\n", + "base_dir = Path(\"c:/bin/ribasim\")\n", "model_dir = base_dir / \"Crystal_Basin\"\n", "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", "\n", @@ -116,14 +120,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "### Flow boundary nodes\n", - "Crystal Basin consists of two inflow points, the tributary and the main Crystal river, we will call them `Minor` and `Main` respectively.\n", + "### FlowBoundary nodes\n", + "The Crystal basin consists of two inflow points, the tributary and the main Crystal river, we will call them Minor and Main respectively.\n", "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", - "This inflow data goes monthly from 2014 to 2023.\n", - "However, for this exercise actual runtime is already defined in step 2.\n", - "\n" + "This is a monthly inflow timeseries from 2014 to 2023.\n", + "The used simulation period is defined by the `starttime` and `endtime` of the model, not by the input timeseries." ] }, { @@ -533,7 +534,7 @@ "Start of irrigation takes place on the 1st of April until the end of August.\n", "The farmer taps water through a canal (demand).\n", "\n", - "For now, let’s assume the return flow remains $0.0$ (`return_factor`).\n", + "For now, let's assume the return flow remains $0.0$ (`return_factor`).\n", "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", "The user demand node interpolates the demand values. Thus the following code needs to be implemented:" ] @@ -934,8 +935,8 @@ "Rename the files to `Crystal_2.2`\n", "\n", "### Add a demand node\n", - "50.000 people live in Crystal City.\n", - "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%." + "$50.000$ people live in Crystal City.\n", + "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%$." ] }, { From e66d4c475dd81ec763b51b8ad4a8a934f9b63c30 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 15:35:35 +0200 Subject: [PATCH 08/19] Progress --- docs/tutorial/quickstart.ipynb | 116 ++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb index bfa281658..06f4860f1 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/quickstart.ipynb @@ -141,7 +141,7 @@ "print(\"Maximum inflowQ m3/s:\", data[\"sum\"].max)\n", "\n", "model.flow_boundary.add(\n", - " Node(1, Point(0.0, 0.0), name=\"Main\"),\n", + " Node(1, Point(0.0, 0.0), name=\"main\"),\n", " [\n", " flow_boundary.Time(\n", " time=data.time,\n", @@ -151,30 +151,30 @@ ")\n", "\n", "model.flow_boundary.add(\n", - " Node(2, Point(-3.0, 0.0), name=\"Minor\"),\n", + " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", " [\n", " flow_boundary.Time(\n", " time=data.time,\n", " flow_rate=data.minor,\n", " )\n", " ],\n", - ")" + ")\n", + "\n", + "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "### Basin node (confluence)\n", "To schematize the confluence from the tributary we will use the Basin node.\n", - "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.\n", + "The node by itself portrays as water storage with a certain volume of water and can be used for different purposes, such as a reservoir, river reach, lake or in this case a confluence.\n", "@fig-confluence visualizes a cross section of the confluence point in our model.\n", "\n", "![Basin node concept for the confluence](https://s3.deltares.nl/ribasim/doc-image/quickstart/Basin-node-concept-for-the-confluence.png){fig-align=\"left\" #fig-confluence}\n", "\n", - "@tbl-input1 shows the input data for the basin node profile.\n", + "@tbl-input1 shows the input data for the Basin node profile.\n", "\n", ": Profile data for the basin node {#tbl-input1}\n", "\n", @@ -183,7 +183,11 @@ "| $672000.0$ | $0.0$ |\n", "| $5600000.0$ | $6.0$ |\n", "\n", - "To specify the basin profile, the following code is used:\n" + "Whilst in this case the level starts at $0.0$ and therefore happens to be the same as the depth, it should never be interpreted as a depth.\n", + "All water levels in Ribasim are assumed to be with respect to a shared reference datum, like mean sea level (MSL).\n", + "The first water level in the profile is the height of the Basin bottom above this reference datum.\n", + "\n", + "To specify the Basin profile, the following code is used:" ] }, { @@ -193,7 +197,7 @@ "outputs": [], "source": [ "model.basin.add(\n", - " Node(3, Point(-1.5, -1), name=\"Conf\"),\n", + " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", " [\n", " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", " basin.State(level=[4]),\n", @@ -206,18 +210,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Tabulated rating curve\n", + "### TabulatedRatingCurve\n", "In the previous step we implemented a Basin node that functions as a confluence.\n", - "Conceptually, the basin acts like a bucket of water, accumulating inflows and then releasing them.\n", - "However, the model does not run if the basin is directly connected to the terminal node.\n", - "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.\n", - "This relation is defined by the `Tabulated Rating Curve` and thus serves as a critical component.\n", + "Conceptually, the Basin acts as a store of water, accumulating inflows and then releasing them.\n", + "A Basin cannot directly connect to another Basin, because the rules for water exchange between them need to be defined.\n", + "Connector nodes take care of this.\n", + "The first such node we introduce is the TabulatedRatingCurve.\n", + "It defines a relation between the water level ($h$) in the Basin and the outflow ($Q$) from the Basin.\n", "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", "\n", - "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}$.\n", + "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{m}^3/\\text{s}$.\n", "It is therefore expected that the confluence basin reaches a level where the outflow is equal to the inflow via the rating curve.\n", - "Only then is the confluence basin in equilibrium.\n", - "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 @tbl-input2 will be used as input.\n", + "Only then is the confluence Basin in equilibrium.\n", + "To ensure that inflow equals outflow and keeping in mind the maximum depth of the river is $6 \\text{m}$, the $Q(h)$ relationship in @tbl-input2 will be used as input.\n", "\n", ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", "\n", @@ -227,18 +232,17 @@ "| $2.0$ | $50.0$ |\n", "| $5.0$ | $200.0$ |\n", "\n", - "In Ribasim, the $Q(h)$ relation is a linear function, so the points in between will be linearly interpolated.\n", + "In Ribasim, the $Q(h)$ relation is a piecewise linear function, so the points in between will be linearly interpolated.\n", "@fig-discharge illustrates the visual process and shows a progressive increase in discharge with rising water levels.\n", "In this case this means:\n", "\n", "- At level $0.0$: No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.\n", - "- 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.\n", - "- 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.\n", + "- At level $2.0$: Discharge is $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.\n", + "- 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.\n", "\n", "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", "\n", - "Taking this into account, add the `Tabulated Rating Curve` as follows:\n", - "\n" + "Taking this into account, add the `TabulatedRatingCurve` as follows:" ] }, { @@ -248,7 +252,7 @@ "outputs": [], "source": [ "model.tabulated_rating_curve.add(\n", - " Node(4, Point(-1.5, -1.5), name=\"MainConf\"),\n", + " Node(4, Point(-1.5, -1.5), name=\"Confluence\"),\n", " [\n", " tabulated_rating_curve.Static(\n", " level=[0.0, 2, 5],\n", @@ -264,7 +268,7 @@ "source": [ "### Terminal node\n", "Finally all the water will discharge into the ocean.\n", - "Schematize this with the terminal node as it portrays the end point of the model.\n", + "We schematize this with the Terminal node, as it portrays the end point of the model, that can receive but not give water.\n", "Besides the node number/name and location, no further input is needed." ] }, @@ -274,7 +278,7 @@ "metadata": {}, "outputs": [], "source": [ - "model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"Terminal\"))" + "model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" ] }, { @@ -282,11 +286,7 @@ "metadata": {}, "source": [ "### Defining edges\n", - "Implement the connections (edges) between the nodes, in the following order:\n", - "\n", - "1. Flow boundaries to the basin;\n", - "2. Basin to the rating curve;\n", - "3. Tabulated rating curve to the terminal." + "Implement the connections (edges) between the nodes." ] }, { @@ -306,7 +306,23 @@ "metadata": {}, "source": [ "### Visualization and model execution\n", - "Plot the schematization, write the model configuration to the `TOML` file.\n", + "Plot the schematization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write the model configuration to the `TOML` file.\n", "Name the output file `Crystal_1.1/ribasim.toml`:" ] }, @@ -316,11 +332,9 @@ "metadata": {}, "outputs": [], "source": [ - "model.plot()\n", - "\n", "toml_path = model_dir / \"Crystal_1.1/ribasim.toml\"\n", "model.write(toml_path)\n", - "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"" + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"" ] }, { @@ -332,12 +346,12 @@ "\n", "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", "\n", - "After writing model.write a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", + "After running `model.write` a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", "\n", "- ribasim.toml: The model configuration\n", - "- database.gpkg: A geopackage containing the shapes of your schematization and the input data of the nodes used.\n", + "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used.\n", "\n", - "Now run the model:\n" + "Now run the model:" ] }, { @@ -346,7 +360,9 @@ "metadata": {}, "outputs": [], "source": [ - "subprocess.run([rib_path, toml_path], check=True)" + "result = subprocess.run([cli_path, toml_path], capture_output=True, encoding=\"utf-8\")\n", + "print(result.stderr)\n", + "result.check_returncode()" ] }, { @@ -354,7 +370,7 @@ "metadata": {}, "source": [ "### Post-processing results\n", - "Read the arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:" + "Read the Arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:" ] }, { @@ -370,7 +386,7 @@ " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", ")\n", "\n", - "# Skip the first timestep as it’s the initialization step\n", + "# Skip the first timestep as it is the initialization step\n", "df_basin_wide = df_basin_wide.iloc[1:]\n", "\n", "# Plot level and storage on the same graph with dual y-axes\n", @@ -402,7 +418,7 @@ "outputs": [], "source": [ "# Plot flow data\n", - "# Read the data from feather format\n", + "# Read the flow results\n", "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", "# Create 'edge' and 'flow_m3d' columns\n", "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", @@ -435,9 +451,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "@fig-sim1 shows the storage and levels in the basin node.\n", + "@fig-sim1 shows the storage and levels in the Basin node.\n", "\n", - "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.\n", + "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.\n", "To accurately represent the relationship between water levels and discharge rates at this confluence, a rating curve node is implemented.\n", "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", "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.\n", @@ -641,9 +657,9 @@ "\n", "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", "model.write(toml_path)\n", - "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", "\n", - "subprocess.run([rib_path, toml_path], check=True)" + "subprocess.run([cli_path, toml_path], check=True)" ] }, { @@ -864,7 +880,7 @@ "source": [ "toml_path = model_dir / \"Crystal_2.1/ribasim.toml\"\n", "model.write(toml_path)\n", - "rib_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", "\n", "# Dictionary mapping node_ids to names\n", "edge_names = {\n", @@ -1063,7 +1079,15 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", "version": "3.12.5" } }, From 8355c1f835a9f247eaa2e20e4ca3740c031827a5 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 17:17:45 +0200 Subject: [PATCH 09/19] progress --- docs/tutorial/quickstart.ipynb | 111 ++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 43 deletions(-) diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb index 06f4860f1..711ae0b39 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/quickstart.ipynb @@ -82,8 +82,9 @@ "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", + "import plotly.express as px\n", "from ribasim import Model, Node\n", - "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve\n", + "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve, user_demand\n", "from shapely.geometry import Point" ] }, @@ -133,15 +134,62 @@ "metadata": {}, "outputs": [], "source": [ - "data = pd.read_csv(data_path, sep=\";\")\n", - "data[\"sum\"] = data[\"minor\"] + data[\"main\"]\n", - "# Average and max inflow of the whole summed inflow data timeseries\n", + "pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pd.DataFrame(\n", + " {\n", + " \"time\": pd.date_range(\n", + " start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\", name=\"time\"\n", + " ),\n", + " \"main\": [\n", + " 74.7,\n", + " 57.9,\n", + " 63.2,\n", + " 183.9,\n", + " 91.8,\n", + " 47.5,\n", + " 32.6,\n", + " 27.6,\n", + " 26.5,\n", + " 25.1,\n", + " 39.3,\n", + " 37.8,\n", + " 57.9,\n", + " ],\n", + " \"minor\": [\n", + " 16.3,\n", + " 3.8,\n", + " 3.0,\n", + " 37.6,\n", + " 18.2,\n", + " 11.1,\n", + " 12.9,\n", + " 12.2,\n", + " 11.2,\n", + " 10.8,\n", + " 15.1,\n", + " 14.3,\n", + " 11.8,\n", + " ],\n", + " }\n", + ")\n", + "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", + "display(data)\n", + "\n", + "# Average and max inflow of the total inflow data timeseries\n", "# From 2014 - 2023\n", - "print(\"Average inflowQ m3/s:\", data[\"sum\"].mean())\n", - "print(\"Maximum inflowQ m3/s:\", data[\"sum\"].max)\n", + "print(\"Average inflow [m3/s]:\", data[\"total\"].mean())\n", + "print(\"Maximum inflow [m3/s]:\", data[\"total\"].max())\n", "\n", - "model.flow_boundary.add(\n", - " Node(1, Point(0.0, 0.0), name=\"main\"),\n", + "main = model.flow_boundary.add(\n", + " Node(geometry=Point(0.0, 0.0), name=\"main\"),\n", " [\n", " flow_boundary.Time(\n", " time=data.time,\n", @@ -150,8 +198,8 @@ " ],\n", ")\n", "\n", - "model.flow_boundary.add(\n", - " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", + "minor = model.flow_boundary.add(\n", + " Node(geometry=Point(-3.0, 0.0), name=\"minor\"),\n", " [\n", " flow_boundary.Time(\n", " time=data.time,\n", @@ -160,7 +208,8 @@ " ],\n", ")\n", "\n", - "data" + "print(main)\n", + "print(minor)" ] }, { @@ -453,8 +502,7 @@ "source": [ "@fig-sim1 shows the storage and levels in the Basin node.\n", "\n", - "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.\n", - "To accurately represent the relationship between water levels and discharge rates at this confluence, a rating curve node is implemented.\n", + "To accurately represent the relationship between water levels and discharge rates at this confluence, a TabulatedRatingCurve is used.\n", "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", "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.\n", "This is because there is no net change in storage; all incoming water is balanced by outgoing flow.\n", @@ -471,35 +519,15 @@ "## Irrigation demand\n", "\n", "Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation.\n", - "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 @fig-irrigation).\n", + "Water is diverted from the main river through an irrigation canal, with a portion of it eventually returning to the main river (see @fig-irrigation).\n", "\n", "![Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-irrigation}\n", "\n", - "For this update schematization, we need to incorporate three additional nodes:\n", + "For this schematization update, we need to incorporate three additional nodes:\n", "\n", "- Basin: Represents a cross-sectional point where water is diverted.\n", - "- User Demand: Represents the irrigation demand.\n", - "- Tabulates Rating Curve: Defines the remaining water flow from the main river at the diversion point.\n", - "\n", - "### Setup the model & adjust import packages\n", - "Copy and paste the python script `Crystal_1.1` and rename it `Crystal_1.2`.\n", - "The 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." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import subprocess # For running the model\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", - "import plotly.express as px\n", - "from ribasim import Model, Node # The main library used for river basin modeling.\n", - "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve, user_demand\n", - "from shapely.geometry import Point" + "- UserDemand: Represents the irrigation demand.\n", + "- TabulatedRatingCurve: Defines the remaining water flow from the main river at the diversion point." ] }, { @@ -507,10 +535,7 @@ "metadata": {}, "source": [ "### Add a second Basin node\n", - "Schematically we are dealing with two basins.\n", - "To keep the order of flow from upstream to downstream it is recommended to adjust the node_id numbers accordingly.\n", - "In this case `node_id = 3` will be `node_id = 4`.\n", - "Basin 3 will portray as the point in the river where the diversion takes place, getting the name `Div`.\n", + "Basin #3 will portray as the point in the river where the diversion takes place, getting the name `diversion`.\n", "Its profile area at this intersection is slightly smaller than at the confluence." ] }, @@ -521,7 +546,7 @@ "outputs": [], "source": [ "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name=\"Div\"),\n", + " Node(3, Point(-0.75, -0.5), name=\"diversion\"),\n", " [\n", " basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", " basin.State(level=[3]),\n", @@ -530,7 +555,7 @@ ")\n", "\n", "model.basin.add(\n", - " Node(4, Point(-1.5, -1), name=\"Conf\"),\n", + " Node(4, Point(-1.5, -1), name=\"confluence\"),\n", " [\n", " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", " basin.State(level=[4]),\n", From d25c5eb7a5d9e9e617627b363136488df36f1a5f Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Mon, 9 Sep 2024 17:43:40 +0200 Subject: [PATCH 10/19] Progress --- docs/tutorial/quickstart.ipynb | 55 ++++++++-------------------------- 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb index 711ae0b39..3af74d9c7 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/quickstart.ipynb @@ -143,43 +143,11 @@ "metadata": {}, "outputs": [], "source": [ - "data = pd.DataFrame(\n", - " {\n", - " \"time\": pd.date_range(\n", - " start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\", name=\"time\"\n", - " ),\n", - " \"main\": [\n", - " 74.7,\n", - " 57.9,\n", - " 63.2,\n", - " 183.9,\n", - " 91.8,\n", - " 47.5,\n", - " 32.6,\n", - " 27.6,\n", - " 26.5,\n", - " 25.1,\n", - " 39.3,\n", - " 37.8,\n", - " 57.9,\n", - " ],\n", - " \"minor\": [\n", - " 16.3,\n", - " 3.8,\n", - " 3.0,\n", - " 37.6,\n", - " 18.2,\n", - " 11.1,\n", - " 12.9,\n", - " 12.2,\n", - " 11.2,\n", - " 10.8,\n", - " 15.1,\n", - " 14.3,\n", - " 11.8,\n", - " ],\n", - " }\n", - ")\n", + "data = pd.DataFrame({\n", + " \"time\": pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\"),\n", + " \"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],\n", + " \"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]\n", + "}) # fmt: skip\n", "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", "display(data)\n", "\n", @@ -245,8 +213,8 @@ "metadata": {}, "outputs": [], "source": [ - "model.basin.add(\n", - " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", + "confluence = model.basin.add(\n", + " Node(geometry=Point(-1.5, -1), name=\"confluence\"),\n", " [\n", " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", " basin.State(level=[4]),\n", @@ -269,9 +237,10 @@ "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", "\n", "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{m}^3/\\text{s}$.\n", - "It is therefore expected that the confluence basin reaches a level where the outflow is equal to the inflow via the rating curve.\n", + "It is therefore expected that the confluence Basin goes towards a level where the outflow is equal to the inflow via the rating curve.\n", "Only then is the confluence Basin in equilibrium.\n", - "To ensure that inflow equals outflow and keeping in mind the maximum depth of the river is $6 \\text{m}$, the $Q(h)$ relationship in @tbl-input2 will be used as input.\n", + "The maximum depth of the river is $6 \\text{m}$, and the maximum inflow is $221.5 \\text{m}^3/\\text{s}$\n", + "The $Q(h)$ relationship in @tbl-input2 allows such inflows with reasonable water levels.\n", "\n", ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", "\n", @@ -300,8 +269,8 @@ "metadata": {}, "outputs": [], "source": [ - "model.tabulated_rating_curve.add(\n", - " Node(4, Point(-1.5, -1.5), name=\"Confluence\"),\n", + "weir = model.tabulated_rating_curve.add(\n", + " Node(geometry=Point(-1.5, -1.5), name=\"weir\"),\n", " [\n", " tabulated_rating_curve.Static(\n", " level=[0.0, 2, 5],\n", From 9b89f7d7ba2fa5390b60da7240d4d0bb13410f3a Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 10 Sep 2024 12:53:20 +0200 Subject: [PATCH 11/19] Start splitting up tutorials --- docs/_quarto.yml | 7 +- docs/tutorial/irrigation-demand.ipynb | 382 ++++++ docs/tutorial/natural-flow.ipynb | 506 ++++++++ ...kstart.ipynb => public-water-supply.ipynb} | 51 +- docs/tutorial/reservoir.ipynb | 1095 +++++++++++++++++ 5 files changed, 2016 insertions(+), 25 deletions(-) create mode 100644 docs/tutorial/irrigation-demand.ipynb create mode 100644 docs/tutorial/natural-flow.ipynb rename docs/tutorial/{quickstart.ipynb => public-water-supply.ipynb} (98%) create mode 100644 docs/tutorial/reservoir.ipynb diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 3e5630cfd..c0e911611 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -11,7 +11,7 @@ website: - text: "Overview" file: index.qmd - text: "Tutorials" - file: tutorial/quickstart.ipynb + file: tutorial/natural-flow.ipynb - text: "How-to guides" file: guide/examples.ipynb - text: "Concepts" @@ -35,7 +35,10 @@ website: - title: "Tutorials" contents: - - tutorial/quickstart.ipynb + - tutorial/natural-flow.ipynb + - tutorial/irrigation-demand.ipynb + - tutorial/reservoir.ipynb + - tutorial/public-water-supply.ipynb - title: "How-to guides" contents: diff --git a/docs/tutorial/irrigation-demand.ipynb b/docs/tutorial/irrigation-demand.ipynb new file mode 100644 index 000000000..6ee762df9 --- /dev/null +++ b/docs/tutorial/irrigation-demand.ipynb @@ -0,0 +1,382 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess # For running the model\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from ribasim import Node\n", + "from ribasim.nodes import basin, tabulated_rating_curve" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Irrigation demand\n", + "\n", + "Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation.\n", + "Water is diverted from the main river through an irrigation canal, with a portion of it eventually returning to the main river (see @fig-irrigation).\n", + "\n", + "![Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-irrigation}\n", + "\n", + "For this schematization update, we need to incorporate three additional nodes:\n", + "\n", + "- Basin: Represents a cross-sectional point where water is diverted.\n", + "- UserDemand: Represents the irrigation demand.\n", + "- TabulatedRatingCurve: Defines the remaining water flow from the main river at the diversion point." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a second Basin node\n", + "Basin #3 will portray as the point in the river where the diversion takes place, getting the name `diversion`.\n", + "Its profile area at this intersection is slightly smaller than at the confluence." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.basin.add(\n", + " Node(3, Point(-0.75, -0.5), name=\"diversion\"),\n", + " [\n", + " basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", + " basin.State(level=[3]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "\n", + "model.basin.add(\n", + " Node(4, Point(-1.5, -1), name=\"confluence\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add the irrigation demand\n", + "A big farm company needs to apply irrigation to its field starting from April to September.\n", + "The irrigated field is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", + "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.\n", + "Start of irrigation takes place on the 1st of April until the end of August.\n", + "The farmer taps water through a canal (demand).\n", + "\n", + "For now, let's assume the return flow remains $0.0$ (`return_factor`).\n", + "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", + "The user demand node interpolates the demand values. Thus the following code needs to be implemented:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.user_demand.add(\n", + " Node(6, Point(-1.5, 1.0), name=\"IrrA\"),\n", + " [\n", + " user_demand.Time(\n", + " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", + " return_factor=0,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[\n", + " starttime,\n", + " \"2022-03-31\",\n", + " \"2022-04-01\",\n", + " \"2022-07-01\",\n", + " \"2022-09-30\",\n", + " \"2022-10-01\",\n", + " ],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a TabulatedRatingCurve\n", + "The second TabulatedRatingCurve node will simulate the rest of the water that is left after diverting a part from the main river to the farm field.\n", + "The rest of the water will flow naturally towards the confluence:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.tabulated_rating_curve.add(\n", + " Node(7, Point(-1.125, -0.75), name=\"MainDiv\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 1.5, 5],\n", + " flow_rate=[0.0, 45, 200],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is up to the user to renumber the IDs of the nodes.\n", + "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", + "\n", + "### Adjust the Terminal node ID and edges\n", + "Adjust the Terminal node ID.\n", + "Since we added more nodes we have more edges. Add and adjust the edges:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.terminal.add(Node(8, Point(-1.5, -3.0), name=\"Terminal\"))\n", + "\n", + "model.edge.add(model.flow_boundary[1], model.basin[3])\n", + "model.edge.add(model.flow_boundary[2], model.basin[4])\n", + "model.edge.add(model.basin[3], model.user_demand[6])\n", + "model.edge.add(model.user_demand[6], model.basin[4])\n", + "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", + "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", + "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", + "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot model and run\n", + "Plot the schematization and run the model.\n", + "This time the new outputs should be written in a new folder called `Crystal_1.2`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.plot()\n", + "\n", + "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "\n", + "subprocess.run([cli_path, toml_path], check=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The schematization should look like @fig-cs12.\n", + "\n", + "![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-cs12}\n", + "\n", + "### Name the edges and basins\n", + "The names of each nodes are defined and saved in the geopackage.\n", + "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Dictionary mapping node_ids to names\n", + "edge_names = {\n", + " (1, 3): \"Main\",\n", + " (2, 4): \"Minor\",\n", + " (3, 6): \"IrrA Demand\",\n", + " (6, 4): \"IrrA Drain\",\n", + " (3, 7): \"Div2Main\",\n", + " (7, 4): \"Main2Conf\",\n", + " (4, 5): \"Conf2TRC\",\n", + " (5, 8): \"TRC2Term\",\n", + "}\n", + "\n", + "# Dictionary mapping basins (node_ids) to names\n", + "node_names = {\n", + " 3: \"Div\",\n", + " 4: \"Conf\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot and compare the basin results\n", + "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_basin_div = df_basin_wide.xs(\"Div\", axis=1, level=1, drop_level=False)\n", + "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", + "\n", + "\n", + "def plot_basin_data(\n", + " ax, ax_twin, df_basin, level_color=\"b\", storage_color=\"r\", title=\"Basin\"\n", + "):\n", + " # Plot level data\n", + " for idx, column in enumerate(df_basin[\"level\"].columns):\n", + " ax.plot(\n", + " df_basin.index,\n", + " df_basin[\"level\"][column],\n", + " linestyle=\"-\",\n", + " color=level_color,\n", + " label=f\"Level - {column}\",\n", + " )\n", + "\n", + " # Plot storage data\n", + " for idx, column in enumerate(df_basin[\"storage\"].columns):\n", + " ax_twin.plot(\n", + " df_basin.index,\n", + " df_basin[\"storage\"][column],\n", + " linestyle=\"--\",\n", + " color=storage_color,\n", + " label=f\"Storage - {column}\",\n", + " )\n", + "\n", + " ax.set_ylabel(\"Level [m]\", color=level_color)\n", + " ax_twin.set_ylabel(\"Storage [m³]\", color=storage_color)\n", + "\n", + " ax.tick_params(axis=\"y\", labelcolor=level_color)\n", + " ax_twin.tick_params(axis=\"y\", labelcolor=storage_color)\n", + "\n", + " ax.set_title(title)\n", + "\n", + " # Combine legends from both axes\n", + " lines, labels = ax.get_legend_handles_labels()\n", + " lines_twin, labels_twin = ax_twin.get_legend_handles_labels()\n", + " ax.legend(lines + lines_twin, labels + labels_twin, loc=\"upper left\")\n", + "\n", + "\n", + "# Create subplots\n", + "fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True)\n", + "\n", + "# Plot Div basin data\n", + "ax2 = ax1.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax1, ax2, df_basin_div, title=\"Div Basin Level and Storage over Time\")\n", + "\n", + "# Plot Conf basin data\n", + "ax4 = ax3.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax3, ax4, df_basin_conf, title=\"Conf Basin Level and Storage over Time\")\n", + "\n", + "# Common X label\n", + "ax3.set_xlabel(\"Time\")\n", + "fig.tight_layout() # Adjust layout to fit labels\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", + "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.\n", + "\n", + "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.\n", + "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.\n", + "\n", + "![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align=\"left\" #fig-sim3}\n", + "\n", + "### Plot and compare the flow results\n", + "Plot the flow results in an interactive plotting tool." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_flow = pd.read_feather(model_dir / \"Crystal_1.2/results/flow.arrow\")\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", + "\n", + "# Plot the flow data, interactive plot with Plotly\n", + "pivot_flow = df_flow.pivot_table(\n", + " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", + ").reset_index()\n", + "fig = px.line(\n", + " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", + ")\n", + "\n", + "fig.update_layout(legend_title_text=\"Edge\")\n", + "fig.write_html(model_dir / \"Crystal_1.2/plot_edges.html\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", + "\n", + "![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align=\"left\" #fig-sim4}\n", + "\n", + "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 (@fig-sim5) and the return flow remains zero, as the assumption defined before was that there is no drain.\n", + "\n", + "![Supplied irrigation and return flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Supplied-irrigation-and-return-flow.png){fig-align=\"left\" #fig-sim5}\n", + "\n", + "@fig-sim6 shows the flow to the ocean (Terminal).\n", + "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", + "Indicating the impact of irrigation without any drain.\n", + "\n", + "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb new file mode 100644 index 000000000..8341a5f1f --- /dev/null +++ b/docs/tutorial/natural-flow.ipynb @@ -0,0 +1,506 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: \"Quick start guide\"\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align=\"left\"}\n", + "\n", + "# Introduction\n", + "Welcome to Ribasim!\n", + "This tutorial will help you get started with the basics of using Ribasim for river basin simulation.\n", + "In this tutorial, the schematization of models is done in Python using the Ribasim Python package.\n", + "The Ribasim Python package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", + "It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible.\n", + "\n", + "## Learning objectives\n", + "In this tutorial, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", + "The guide is divided into different modules, each covering various scenarios.\n", + "These include simulating natural flow, implementing reservoirs, and observing the impact of other structures.\n", + "While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations.\n", + "By the end of the tutorial, users will be able to:\n", + "\n", + "- **Set up a basic Ribasim model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", + "- **Evaluate the impact of demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", + "- **Modify and update models**: Learn how to update existing models with new data and changes.\n", + "- **Analyze simulation results**: Use built-in tools to analyze and interpret the results of your simulations.\n", + "\n", + "## Prerequisites\n", + "First install the latest release of Ribasim as documented in [the installation guide](/install.qmd).\n", + "\n", + "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:\n", + "\n", + "- `QuickStartGuide.pdf`\n", + "- `data`: Contains data inputs such as time series needed for running the case.\n", + "Additionally, your Python model (`.py`) and the results will also be saved in this folder." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Crystal River Basin\n", + "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 @fig-crystal-basin).\n", + "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", + "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", + "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", + "\n", + "![Crystal Basin based on natural flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-Basin-based-on-natural-flow.png){fig-align=\"left\" #fig-crystal-basin}\n", + "\n", + "After this module the user will be able to:\n", + "\n", + "- Build a river basin model from scratch\n", + "- Understand the functionality of the Demand and Basin nodes\n", + "- Generate overview of results\n", + "- Evaluate the simulation results\n", + "\n", + "## Natural flow\n", + "\n", + "### Import packages\n", + "Before building the model we need to import some modules.\n", + "Open your favorite Python editor (Visual Studio Code, Jupyter, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", + "Import the following modules in Python:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess # For running the model\n", + "from pathlib import Path\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from ribasim import Model, Node\n", + "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup paths and model configuration\n", + "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.\n", + "The coordinate reference system (CRS) is also required, and set to [EPSG:4326](https://epsg.io/4326), which means all coordinates are interpreted as latitude and longitude values.\n", + "The CRS is important for correctly placing Ribasim models on the map, but since this is a fictional model, it is not important." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "base_dir = Path(\"c:/bin/ribasim\")\n", + "model_dir = base_dir / \"Crystal_Basin\"\n", + "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "\n", + "starttime = \"2022-01-01\"\n", + "endtime = \"2023-01-01\"\n", + "model = Model(\n", + " starttime=starttime,\n", + " endtime=endtime,\n", + " crs=\"EPSG:4326\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### FlowBoundary nodes\n", + "The Crystal basin consists of two inflow points, the tributary and the main Crystal river, we will call them Minor and Main respectively.\n", + "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", + "This is a monthly inflow timeseries from 2014 to 2023.\n", + "The used simulation period is defined by the `starttime` and `endtime` of the model, not by the input timeseries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pd.DataFrame({\n", + " \"time\": pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\"),\n", + " \"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],\n", + " \"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]\n", + "}) # fmt: skip\n", + "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", + "display(data)\n", + "\n", + "# Average and max inflow of the total inflow data timeseries\n", + "# From 2014 - 2023\n", + "print(\"Average inflow [m3/s]:\", data[\"total\"].mean())\n", + "print(\"Maximum inflow [m3/s]:\", data[\"total\"].max())\n", + "\n", + "main = model.flow_boundary.add(\n", + " Node(1, Point(0.0, 0.0), name=\"main\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.main,\n", + " )\n", + " ],\n", + ")\n", + "\n", + "minor = model.flow_boundary.add(\n", + " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.minor,\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basin node (confluence)\n", + "To schematize the confluence from the tributary we will use the Basin node.\n", + "The node by itself portrays as water storage with a certain volume of water and can be used for different purposes, such as a reservoir, river reach, lake or in this case a confluence.\n", + "@fig-confluence visualizes a cross section of the confluence point in our model.\n", + "\n", + "![Basin node concept for the confluence](https://s3.deltares.nl/ribasim/doc-image/quickstart/Basin-node-concept-for-the-confluence.png){fig-align=\"left\" #fig-confluence}\n", + "\n", + "@tbl-input1 shows the input data for the Basin node profile.\n", + "\n", + ": Profile data for the basin node {#tbl-input1}\n", + "\n", + "| Area [$\\text{m}^2$] | Level [$\\text{m}$] |\n", + "|---------------------|--------------------|\n", + "| $672000.0$ | $0.0$ |\n", + "| $5600000.0$ | $6.0$ |\n", + "\n", + "Whilst in this case the level starts at $0.0$ and therefore happens to be the same as the depth, it should never be interpreted as a depth.\n", + "All water levels in Ribasim are assumed to be with respect to a shared reference datum, like mean sea level (MSL).\n", + "The first water level in the profile is the height of the Basin bottom above this reference datum.\n", + "\n", + "To specify the Basin profile, the following code is used:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "confluence = model.basin.add(\n", + " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### TabulatedRatingCurve\n", + "In the previous step we implemented a Basin node that functions as a confluence.\n", + "Conceptually, the Basin acts as a store of water, accumulating inflows and then releasing them.\n", + "A Basin cannot directly connect to another Basin, because the rules for water exchange between them need to be defined.\n", + "Connector nodes take care of this.\n", + "The first such node we introduce is the TabulatedRatingCurve.\n", + "It defines a relation between the water level ($h$) in the Basin and the outflow ($Q$) from the Basin.\n", + "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", + "\n", + "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{m}^3/\\text{s}$.\n", + "It is therefore expected that the confluence Basin goes towards a level where the outflow is equal to the inflow via the rating curve.\n", + "Only then is the confluence Basin in equilibrium.\n", + "The maximum depth of the river is $6 \\text{m}$, and the maximum inflow is $221.5 \\text{m}^3/\\text{s}$\n", + "The $Q(h)$ relationship in @tbl-input2 allows such inflows with reasonable water levels.\n", + "\n", + ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", + "\n", + "| Water Level ($h$) [$\\text{m}$] | Outflow ($Q$) [$\\text{m}^3/\\text{s}$] |\n", + "| -------------------------------|---------------------------------------|\n", + "| $0.0$ | $0.0$ |\n", + "| $2.0$ | $50.0$ |\n", + "| $5.0$ | $200.0$ |\n", + "\n", + "In Ribasim, the $Q(h)$ relation is a piecewise linear function, so the points in between will be linearly interpolated.\n", + "@fig-discharge illustrates the visual process and shows a progressive increase in discharge with rising water levels.\n", + "In this case this means:\n", + "\n", + "- At level $0.0$: No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.\n", + "- At level $2.0$: Discharge is $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.\n", + "- 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.\n", + "\n", + "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", + "\n", + "Taking this into account, add the `TabulatedRatingCurve` as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "weir = model.tabulated_rating_curve.add(\n", + " Node(4, Point(-1.5, -1.5), name=\"weir\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 2, 5],\n", + " flow_rate=[0.0, 50, 200],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Terminal node\n", + "Finally all the water will discharge into the sea.\n", + "We schematize this with the Terminal node, as it portrays the end point of the model, that can receive but not give water.\n", + "Besides the node number/name and location, no further input is needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sea = model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Defining edges\n", + "Implement the connections (edges) between the nodes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.edge.add(main, confluence)\n", + "model.edge.add(minor, confluence)\n", + "model.edge.add(confluence, weir)\n", + "model.edge.add(weir, sea)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualization and model execution\n", + "Plot the schematization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.plot();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write the model configuration to the `TOML` file.\n", + "Name the output file `Crystal_1.1/ribasim.toml`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "toml_path = model_dir / \"Crystal_1.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The schematization should look like @fig-cs11.\n", + "\n", + "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", + "\n", + "After running `model.write` a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", + "\n", + "- ribasim.toml: The model configuration\n", + "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used.\n", + "\n", + "Now run the model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = subprocess.run([cli_path, toml_path], capture_output=True, encoding=\"utf-8\")\n", + "print(result.stderr)\n", + "result.check_returncode()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Post-processing results\n", + "Read the Arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_wide = df_basin.pivot_table(\n", + " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", + ")\n", + "\n", + "# Skip the first timestep as it is the initialization step\n", + "df_basin_wide = df_basin_wide.iloc[1:]\n", + "\n", + "# Plot level and storage on the same graph with dual y-axes\n", + "fig, ax1 = plt.subplots(figsize=(12, 6))\n", + "\n", + "# Plot level on the primary y-axis\n", + "color = \"b\"\n", + "ax1.set_xlabel(\"Time\")\n", + "ax1.set_ylabel(\"Level [m]\", color=color)\n", + "ax1.plot(df_basin_wide.index, df_basin_wide[\"level\"], color=color)\n", + "ax1.tick_params(axis=\"y\", labelcolor=color)\n", + "\n", + "# Create a secondary y-axis for storage\n", + "ax2 = ax1.twinx()\n", + "color = \"r\"\n", + "ax2.set_ylabel(\"Storage [m³]\", color=\"r\")\n", + "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"], linestyle=\"--\", color=color)\n", + "ax2.tick_params(axis=\"y\", labelcolor=color)\n", + "\n", + "fig.tight_layout() # Adjust layout to fit labels\n", + "plt.title(\"Basin Level and Storage Over Time\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot flow data\n", + "# Read the flow results\n", + "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", + "# Create 'edge' and 'flow_m3d' columns\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "\n", + "# Create a pivot table\n", + "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_rate\")\n", + "\n", + "# Skip the first timestep\n", + "pivot_flow = pivot_flow.iloc[1:]\n", + "\n", + "line_styles = [\"-\", \"--\", \"-\", \"-.\"]\n", + "num_styles = len(line_styles)\n", + "\n", + "fig, ax = plt.subplots(figsize=(12, 6))\n", + "for i, column in enumerate(pivot_flow.columns):\n", + " pivot_flow[column].plot(\n", + " ax=ax, linestyle=line_styles[i % num_styles], linewidth=1.5, alpha=0.8\n", + " )\n", + "\n", + "# Set labels and title\n", + "ax.set_xlabel(\"Time\")\n", + "ax.set_ylabel(\"Flow [m³/s]\")\n", + "ax.legend(bbox_to_anchor=(1.15, 1), title=\"Edge\")\n", + "plt.title(\"Flow Over Time\")\n", + "plt.grid(True)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "@fig-sim1 shows the storage and levels in the Basin node.\n", + "\n", + "To accurately represent the relationship between water levels and discharge rates at this confluence, a TabulatedRatingCurve is used.\n", + "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", + "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.\n", + "This is because there is no net change in storage; all incoming water is balanced by outgoing flow.\n", + "\n", + "![Simulated basin level and storage](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-level-and-storage.png){fig-align=\"left\" #fig-sim1}\n", + "\n", + "@fig-sim2 shows the discharges in $\\text{m}^3/\\text{s}$ on each edge.\n", + "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.\n", + "Both show the same discharge over time.\n", + "Which is expected in a natural flow environment, as what is coming into the confluence must come out.\n", + "\n", + "![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align=\"left\" #fig-sim2}" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/public-water-supply.ipynb similarity index 98% rename from docs/tutorial/quickstart.ipynb rename to docs/tutorial/public-water-supply.ipynb index 3af74d9c7..31727694a 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/public-water-supply.ipynb @@ -1,5 +1,20 @@ { "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess # For running the model\n", + "from pathlib import Path\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from ribasim import Model, Node\n", + "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -77,15 +92,8 @@ "metadata": {}, "outputs": [], "source": [ - "import subprocess # For running the model\n", - "from pathlib import Path\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", "import plotly.express as px\n", - "from ribasim import Model, Node\n", - "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve, user_demand\n", - "from shapely.geometry import Point" + "from ribasim.nodes import user_demand" ] }, { @@ -157,7 +165,7 @@ "print(\"Maximum inflow [m3/s]:\", data[\"total\"].max())\n", "\n", "main = model.flow_boundary.add(\n", - " Node(geometry=Point(0.0, 0.0), name=\"main\"),\n", + " Node(1, Point(0.0, 0.0), name=\"main\"),\n", " [\n", " flow_boundary.Time(\n", " time=data.time,\n", @@ -167,17 +175,14 @@ ")\n", "\n", "minor = model.flow_boundary.add(\n", - " Node(geometry=Point(-3.0, 0.0), name=\"minor\"),\n", + " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", " [\n", " flow_boundary.Time(\n", " time=data.time,\n", " flow_rate=data.minor,\n", " )\n", " ],\n", - ")\n", - "\n", - "print(main)\n", - "print(minor)" + ")" ] }, { @@ -214,7 +219,7 @@ "outputs": [], "source": [ "confluence = model.basin.add(\n", - " Node(geometry=Point(-1.5, -1), name=\"confluence\"),\n", + " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", " [\n", " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", " basin.State(level=[4]),\n", @@ -270,7 +275,7 @@ "outputs": [], "source": [ "weir = model.tabulated_rating_curve.add(\n", - " Node(geometry=Point(-1.5, -1.5), name=\"weir\"),\n", + " Node(4, Point(-1.5, -1.5), name=\"weir\"),\n", " [\n", " tabulated_rating_curve.Static(\n", " level=[0.0, 2, 5],\n", @@ -285,7 +290,7 @@ "metadata": {}, "source": [ "### Terminal node\n", - "Finally all the water will discharge into the ocean.\n", + "Finally all the water will discharge into the sea.\n", "We schematize this with the Terminal node, as it portrays the end point of the model, that can receive but not give water.\n", "Besides the node number/name and location, no further input is needed." ] @@ -296,7 +301,7 @@ "metadata": {}, "outputs": [], "source": [ - "model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" + "sea = model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" ] }, { @@ -313,10 +318,10 @@ "metadata": {}, "outputs": [], "source": [ - "model.edge.add(model.flow_boundary[1], model.basin[3])\n", - "model.edge.add(model.flow_boundary[2], model.basin[3])\n", - "model.edge.add(model.basin[3], model.tabulated_rating_curve[4])\n", - "model.edge.add(model.tabulated_rating_curve[4], model.terminal[5])" + "model.edge.add(main, confluence)\n", + "model.edge.add(minor, confluence)\n", + "model.edge.add(confluence, weir)\n", + "model.edge.add(weir, sea)" ] }, { @@ -333,7 +338,7 @@ "metadata": {}, "outputs": [], "source": [ - "model.plot()" + "model.plot();" ] }, { diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb new file mode 100644 index 000000000..31727694a --- /dev/null +++ b/docs/tutorial/reservoir.ipynb @@ -0,0 +1,1095 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess # For running the model\n", + "from pathlib import Path\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from ribasim import Model, Node\n", + "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: \"Quick start guide\"\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align=\"left\"}\n", + "\n", + "# Introduction\n", + "Welcome to Ribasim!\n", + "This tutorial will help you get started with the basics of using Ribasim for river basin simulation.\n", + "In this tutorial, the schematization of models is done in Python using the Ribasim Python package.\n", + "The Ribasim Python package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", + "It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible.\n", + "\n", + "## Learning objectives\n", + "In this tutorial, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", + "The guide is divided into different modules, each covering various scenarios.\n", + "These include simulating natural flow, implementing reservoirs, and observing the impact of other structures.\n", + "While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations.\n", + "By the end of the tutorial, users will be able to:\n", + "\n", + "- **Set up a basic Ribasim model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", + "- **Evaluate the impact of demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", + "- **Modify and update models**: Learn how to update existing models with new data and changes.\n", + "- **Analyze simulation results**: Use built-in tools to analyze and interpret the results of your simulations.\n", + "\n", + "## Prerequisites\n", + "First install the latest release of Ribasim as documented in [the installation guide](/install.qmd).\n", + "\n", + "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:\n", + "\n", + "- `QuickStartGuide.pdf`\n", + "- `data`: Contains data inputs such as time series needed for running the case.\n", + "Additionally, your Python model (`.py`) and the results will also be saved in this folder." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Crystal River Basin\n", + "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 @fig-crystal-basin).\n", + "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", + "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", + "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", + "\n", + "![Crystal Basin based on natural flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-Basin-based-on-natural-flow.png){fig-align=\"left\" #fig-crystal-basin}\n", + "\n", + "After this module the user will be able to:\n", + "\n", + "- Build a river basin model from scratch\n", + "- Understand the functionality of the Demand and Basin nodes\n", + "- Generate overview of results\n", + "- Evaluate the simulation results\n", + "\n", + "## Natural flow\n", + "\n", + "### Import packages\n", + "Before building the model we need to import some modules.\n", + "Open your favorite Python editor (Visual Studio Code, Jupyter, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", + "Import the following modules in Python:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.express as px\n", + "from ribasim.nodes import user_demand" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup paths and model configuration\n", + "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.\n", + "The coordinate reference system (CRS) is also required, and set to [EPSG:4326](https://epsg.io/4326), which means all coordinates are interpreted as latitude and longitude values.\n", + "The CRS is important for correctly placing Ribasim models on the map, but since this is a fictional model, it is not important." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "base_dir = Path(\"c:/bin/ribasim\")\n", + "model_dir = base_dir / \"Crystal_Basin\"\n", + "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "\n", + "starttime = \"2022-01-01\"\n", + "endtime = \"2023-01-01\"\n", + "model = Model(\n", + " starttime=starttime,\n", + " endtime=endtime,\n", + " crs=\"EPSG:4326\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### FlowBoundary nodes\n", + "The Crystal basin consists of two inflow points, the tributary and the main Crystal river, we will call them Minor and Main respectively.\n", + "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", + "This is a monthly inflow timeseries from 2014 to 2023.\n", + "The used simulation period is defined by the `starttime` and `endtime` of the model, not by the input timeseries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pd.DataFrame({\n", + " \"time\": pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\"),\n", + " \"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],\n", + " \"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]\n", + "}) # fmt: skip\n", + "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", + "display(data)\n", + "\n", + "# Average and max inflow of the total inflow data timeseries\n", + "# From 2014 - 2023\n", + "print(\"Average inflow [m3/s]:\", data[\"total\"].mean())\n", + "print(\"Maximum inflow [m3/s]:\", data[\"total\"].max())\n", + "\n", + "main = model.flow_boundary.add(\n", + " Node(1, Point(0.0, 0.0), name=\"main\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.main,\n", + " )\n", + " ],\n", + ")\n", + "\n", + "minor = model.flow_boundary.add(\n", + " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.minor,\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basin node (confluence)\n", + "To schematize the confluence from the tributary we will use the Basin node.\n", + "The node by itself portrays as water storage with a certain volume of water and can be used for different purposes, such as a reservoir, river reach, lake or in this case a confluence.\n", + "@fig-confluence visualizes a cross section of the confluence point in our model.\n", + "\n", + "![Basin node concept for the confluence](https://s3.deltares.nl/ribasim/doc-image/quickstart/Basin-node-concept-for-the-confluence.png){fig-align=\"left\" #fig-confluence}\n", + "\n", + "@tbl-input1 shows the input data for the Basin node profile.\n", + "\n", + ": Profile data for the basin node {#tbl-input1}\n", + "\n", + "| Area [$\\text{m}^2$] | Level [$\\text{m}$] |\n", + "|---------------------|--------------------|\n", + "| $672000.0$ | $0.0$ |\n", + "| $5600000.0$ | $6.0$ |\n", + "\n", + "Whilst in this case the level starts at $0.0$ and therefore happens to be the same as the depth, it should never be interpreted as a depth.\n", + "All water levels in Ribasim are assumed to be with respect to a shared reference datum, like mean sea level (MSL).\n", + "The first water level in the profile is the height of the Basin bottom above this reference datum.\n", + "\n", + "To specify the Basin profile, the following code is used:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "confluence = model.basin.add(\n", + " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### TabulatedRatingCurve\n", + "In the previous step we implemented a Basin node that functions as a confluence.\n", + "Conceptually, the Basin acts as a store of water, accumulating inflows and then releasing them.\n", + "A Basin cannot directly connect to another Basin, because the rules for water exchange between them need to be defined.\n", + "Connector nodes take care of this.\n", + "The first such node we introduce is the TabulatedRatingCurve.\n", + "It defines a relation between the water level ($h$) in the Basin and the outflow ($Q$) from the Basin.\n", + "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", + "\n", + "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{m}^3/\\text{s}$.\n", + "It is therefore expected that the confluence Basin goes towards a level where the outflow is equal to the inflow via the rating curve.\n", + "Only then is the confluence Basin in equilibrium.\n", + "The maximum depth of the river is $6 \\text{m}$, and the maximum inflow is $221.5 \\text{m}^3/\\text{s}$\n", + "The $Q(h)$ relationship in @tbl-input2 allows such inflows with reasonable water levels.\n", + "\n", + ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", + "\n", + "| Water Level ($h$) [$\\text{m}$] | Outflow ($Q$) [$\\text{m}^3/\\text{s}$] |\n", + "| -------------------------------|---------------------------------------|\n", + "| $0.0$ | $0.0$ |\n", + "| $2.0$ | $50.0$ |\n", + "| $5.0$ | $200.0$ |\n", + "\n", + "In Ribasim, the $Q(h)$ relation is a piecewise linear function, so the points in between will be linearly interpolated.\n", + "@fig-discharge illustrates the visual process and shows a progressive increase in discharge with rising water levels.\n", + "In this case this means:\n", + "\n", + "- At level $0.0$: No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.\n", + "- At level $2.0$: Discharge is $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.\n", + "- 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.\n", + "\n", + "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", + "\n", + "Taking this into account, add the `TabulatedRatingCurve` as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "weir = model.tabulated_rating_curve.add(\n", + " Node(4, Point(-1.5, -1.5), name=\"weir\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 2, 5],\n", + " flow_rate=[0.0, 50, 200],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Terminal node\n", + "Finally all the water will discharge into the sea.\n", + "We schematize this with the Terminal node, as it portrays the end point of the model, that can receive but not give water.\n", + "Besides the node number/name and location, no further input is needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sea = model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Defining edges\n", + "Implement the connections (edges) between the nodes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.edge.add(main, confluence)\n", + "model.edge.add(minor, confluence)\n", + "model.edge.add(confluence, weir)\n", + "model.edge.add(weir, sea)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualization and model execution\n", + "Plot the schematization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.plot();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write the model configuration to the `TOML` file.\n", + "Name the output file `Crystal_1.1/ribasim.toml`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "toml_path = model_dir / \"Crystal_1.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The schematization should look like @fig-cs11.\n", + "\n", + "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", + "\n", + "After running `model.write` a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", + "\n", + "- ribasim.toml: The model configuration\n", + "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used.\n", + "\n", + "Now run the model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = subprocess.run([cli_path, toml_path], capture_output=True, encoding=\"utf-8\")\n", + "print(result.stderr)\n", + "result.check_returncode()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Post-processing results\n", + "Read the Arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_wide = df_basin.pivot_table(\n", + " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", + ")\n", + "\n", + "# Skip the first timestep as it is the initialization step\n", + "df_basin_wide = df_basin_wide.iloc[1:]\n", + "\n", + "# Plot level and storage on the same graph with dual y-axes\n", + "fig, ax1 = plt.subplots(figsize=(12, 6))\n", + "\n", + "# Plot level on the primary y-axis\n", + "color = \"b\"\n", + "ax1.set_xlabel(\"Time\")\n", + "ax1.set_ylabel(\"Level [m]\", color=color)\n", + "ax1.plot(df_basin_wide.index, df_basin_wide[\"level\"], color=color)\n", + "ax1.tick_params(axis=\"y\", labelcolor=color)\n", + "\n", + "# Create a secondary y-axis for storage\n", + "ax2 = ax1.twinx()\n", + "color = \"r\"\n", + "ax2.set_ylabel(\"Storage [m³]\", color=\"r\")\n", + "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"], linestyle=\"--\", color=color)\n", + "ax2.tick_params(axis=\"y\", labelcolor=color)\n", + "\n", + "fig.tight_layout() # Adjust layout to fit labels\n", + "plt.title(\"Basin Level and Storage Over Time\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot flow data\n", + "# Read the flow results\n", + "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", + "# Create 'edge' and 'flow_m3d' columns\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "\n", + "# Create a pivot table\n", + "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_rate\")\n", + "\n", + "# Skip the first timestep\n", + "pivot_flow = pivot_flow.iloc[1:]\n", + "\n", + "line_styles = [\"-\", \"--\", \"-\", \"-.\"]\n", + "num_styles = len(line_styles)\n", + "\n", + "fig, ax = plt.subplots(figsize=(12, 6))\n", + "for i, column in enumerate(pivot_flow.columns):\n", + " pivot_flow[column].plot(\n", + " ax=ax, linestyle=line_styles[i % num_styles], linewidth=1.5, alpha=0.8\n", + " )\n", + "\n", + "# Set labels and title\n", + "ax.set_xlabel(\"Time\")\n", + "ax.set_ylabel(\"Flow [m³/s]\")\n", + "ax.legend(bbox_to_anchor=(1.15, 1), title=\"Edge\")\n", + "plt.title(\"Flow Over Time\")\n", + "plt.grid(True)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "@fig-sim1 shows the storage and levels in the Basin node.\n", + "\n", + "To accurately represent the relationship between water levels and discharge rates at this confluence, a TabulatedRatingCurve is used.\n", + "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", + "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.\n", + "This is because there is no net change in storage; all incoming water is balanced by outgoing flow.\n", + "\n", + "![Simulated basin level and storage](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-level-and-storage.png){fig-align=\"left\" #fig-sim1}\n", + "\n", + "@fig-sim2 shows the discharges in $\\text{m}^3/\\text{s}$ on each edge.\n", + "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.\n", + "Both show the same discharge over time.\n", + "Which is expected in a natural flow environment, as what is coming into the confluence must come out.\n", + "\n", + "![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align=\"left\" #fig-sim2}\n", + "\n", + "## Irrigation demand\n", + "\n", + "Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation.\n", + "Water is diverted from the main river through an irrigation canal, with a portion of it eventually returning to the main river (see @fig-irrigation).\n", + "\n", + "![Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-irrigation}\n", + "\n", + "For this schematization update, we need to incorporate three additional nodes:\n", + "\n", + "- Basin: Represents a cross-sectional point where water is diverted.\n", + "- UserDemand: Represents the irrigation demand.\n", + "- TabulatedRatingCurve: Defines the remaining water flow from the main river at the diversion point." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a second Basin node\n", + "Basin #3 will portray as the point in the river where the diversion takes place, getting the name `diversion`.\n", + "Its profile area at this intersection is slightly smaller than at the confluence." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.basin.add(\n", + " Node(3, Point(-0.75, -0.5), name=\"diversion\"),\n", + " [\n", + " basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", + " basin.State(level=[3]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "\n", + "model.basin.add(\n", + " Node(4, Point(-1.5, -1), name=\"confluence\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add the irrigation demand\n", + "A big farm company needs to apply irrigation to its field starting from April to September.\n", + "The irrigated field is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", + "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.\n", + "Start of irrigation takes place on the 1st of April until the end of August.\n", + "The farmer taps water through a canal (demand).\n", + "\n", + "For now, let's assume the return flow remains $0.0$ (`return_factor`).\n", + "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", + "The user demand node interpolates the demand values. Thus the following code needs to be implemented:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.user_demand.add(\n", + " Node(6, Point(-1.5, 1.0), name=\"IrrA\"),\n", + " [\n", + " user_demand.Time(\n", + " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", + " return_factor=0,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[\n", + " starttime,\n", + " \"2022-03-31\",\n", + " \"2022-04-01\",\n", + " \"2022-07-01\",\n", + " \"2022-09-30\",\n", + " \"2022-10-01\",\n", + " ],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a TabulatedRatingCurve\n", + "The second TabulatedRatingCurve node will simulate the rest of the water that is left after diverting a part from the main river to the farm field.\n", + "The rest of the water will flow naturally towards the confluence:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.tabulated_rating_curve.add(\n", + " Node(7, Point(-1.125, -0.75), name=\"MainDiv\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 1.5, 5],\n", + " flow_rate=[0.0, 45, 200],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is up to the user to renumber the IDs of the nodes.\n", + "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", + "\n", + "### Adjust the Terminal node ID and edges\n", + "Adjust the Terminal node ID.\n", + "Since we added more nodes we have more edges. Add and adjust the edges:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.terminal.add(Node(8, Point(-1.5, -3.0), name=\"Terminal\"))\n", + "\n", + "model.edge.add(model.flow_boundary[1], model.basin[3])\n", + "model.edge.add(model.flow_boundary[2], model.basin[4])\n", + "model.edge.add(model.basin[3], model.user_demand[6])\n", + "model.edge.add(model.user_demand[6], model.basin[4])\n", + "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", + "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", + "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", + "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot model and run\n", + "Plot the schematization and run the model.\n", + "This time the new outputs should be written in a new folder called `Crystal_1.2`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.plot()\n", + "\n", + "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "\n", + "subprocess.run([cli_path, toml_path], check=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The schematization should look like @fig-cs12.\n", + "\n", + "![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-cs12}\n", + "\n", + "### Name the edges and basins\n", + "The names of each nodes are defined and saved in the geopackage.\n", + "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Dictionary mapping node_ids to names\n", + "edge_names = {\n", + " (1, 3): \"Main\",\n", + " (2, 4): \"Minor\",\n", + " (3, 6): \"IrrA Demand\",\n", + " (6, 4): \"IrrA Drain\",\n", + " (3, 7): \"Div2Main\",\n", + " (7, 4): \"Main2Conf\",\n", + " (4, 5): \"Conf2TRC\",\n", + " (5, 8): \"TRC2Term\",\n", + "}\n", + "\n", + "# Dictionary mapping basins (node_ids) to names\n", + "node_names = {\n", + " 3: \"Div\",\n", + " 4: \"Conf\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot and compare the basin results\n", + "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_basin_div = df_basin_wide.xs(\"Div\", axis=1, level=1, drop_level=False)\n", + "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", + "\n", + "\n", + "def plot_basin_data(\n", + " ax, ax_twin, df_basin, level_color=\"b\", storage_color=\"r\", title=\"Basin\"\n", + "):\n", + " # Plot level data\n", + " for idx, column in enumerate(df_basin[\"level\"].columns):\n", + " ax.plot(\n", + " df_basin.index,\n", + " df_basin[\"level\"][column],\n", + " linestyle=\"-\",\n", + " color=level_color,\n", + " label=f\"Level - {column}\",\n", + " )\n", + "\n", + " # Plot storage data\n", + " for idx, column in enumerate(df_basin[\"storage\"].columns):\n", + " ax_twin.plot(\n", + " df_basin.index,\n", + " df_basin[\"storage\"][column],\n", + " linestyle=\"--\",\n", + " color=storage_color,\n", + " label=f\"Storage - {column}\",\n", + " )\n", + "\n", + " ax.set_ylabel(\"Level [m]\", color=level_color)\n", + " ax_twin.set_ylabel(\"Storage [m³]\", color=storage_color)\n", + "\n", + " ax.tick_params(axis=\"y\", labelcolor=level_color)\n", + " ax_twin.tick_params(axis=\"y\", labelcolor=storage_color)\n", + "\n", + " ax.set_title(title)\n", + "\n", + " # Combine legends from both axes\n", + " lines, labels = ax.get_legend_handles_labels()\n", + " lines_twin, labels_twin = ax_twin.get_legend_handles_labels()\n", + " ax.legend(lines + lines_twin, labels + labels_twin, loc=\"upper left\")\n", + "\n", + "\n", + "# Create subplots\n", + "fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True)\n", + "\n", + "# Plot Div basin data\n", + "ax2 = ax1.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax1, ax2, df_basin_div, title=\"Div Basin Level and Storage over Time\")\n", + "\n", + "# Plot Conf basin data\n", + "ax4 = ax3.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax3, ax4, df_basin_conf, title=\"Conf Basin Level and Storage over Time\")\n", + "\n", + "# Common X label\n", + "ax3.set_xlabel(\"Time\")\n", + "fig.tight_layout() # Adjust layout to fit labels\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", + "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.\n", + "\n", + "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.\n", + "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.\n", + "\n", + "![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align=\"left\" #fig-sim3}\n", + "\n", + "### Plot and compare the flow results\n", + "Plot the flow results in an interactive plotting tool." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_flow = pd.read_feather(model_dir / \"Crystal_1.2/results/flow.arrow\")\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", + "\n", + "# Plot the flow data, interactive plot with Plotly\n", + "pivot_flow = df_flow.pivot_table(\n", + " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", + ").reset_index()\n", + "fig = px.line(\n", + " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", + ")\n", + "\n", + "fig.update_layout(legend_title_text=\"Edge\")\n", + "fig.write_html(model_dir / \"Crystal_1.2/plot_edges.html\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", + "\n", + "![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align=\"left\" #fig-sim4}\n", + "\n", + "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 (@fig-sim5) and the return flow remains zero, as the assumption defined before was that there is no drain.\n", + "\n", + "![Supplied irrigation and return flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Supplied-irrigation-and-return-flow.png){fig-align=\"left\" #fig-sim5}\n", + "\n", + "@fig-sim6 shows the flow to the ocean (Terminal).\n", + "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", + "Indicating the impact of irrigation without any drain.\n", + "\n", + "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reservoirs and Public Water Supply\n", + "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", + "The reservoir is to help ensure a reliable supply during dry periods.\n", + "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal Basin.\n", + "\n", + "![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align=\"left\" #fig-reservoir}\n", + "\n", + "## Reservoir\n", + "### Add a Basin\n", + "This time 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).\n", + "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", + "The profile of Basin #3 should change to:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.basin.add(\n", + " Node(3, Point(-0.75, -0.5), name=\"Rsv\"),\n", + " [\n", + " basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", + " basin.State(level=[3.5]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adjust the code\n", + "Adjust the naming of the Basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "toml_path = model_dir / \"Crystal_2.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "\n", + "# Dictionary mapping node_ids to names\n", + "edge_names = {\n", + " (1, 3): \"Main\",\n", + " (2, 4): \"Minor\",\n", + " (3, 6): \"IrrA Demand\",\n", + " (6, 4): \"IrrA Drain\",\n", + " (3, 7): \"Rsv2Main\",\n", + " (7, 4): \"Main2Conf\",\n", + " (4, 5): \"Conf2TRC\",\n", + " (5, 8): \"TRC2Term\",\n", + "}\n", + "\n", + "# Dictionary mapping basins (node_ids) to names\n", + "node_names = {\n", + " 3: \"Rsv\",\n", + " 4: \"Conf\",\n", + "}\n", + "\n", + "df_basin = pd.read_feather(model_dir / \"Crystal_2.1/results/basin.arrow\")\n", + "\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_rsv = df_basin_wide.xs(\"Rsv\", axis=1, level=1, drop_level=False)\n", + "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", + "\n", + "# Plot Rsv basin data\n", + "ax2 = ax1.twinx() # Secondary y-axis for storage\n", + "plot_basin_data(ax1, ax2, df_basin_rsv, title=\"Reservoir Level and Storage Over Time\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Sample data loading and preparation\n", + "df_flow = pd.read_feather(model_dir / \"Crystal_2.1/results/flow.arrow\")\n", + "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", + "\n", + "# Plot the flow data, interactive plot with Plotly\n", + "pivot_flow = df_flow.pivot_table(\n", + " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", + ").reset_index()\n", + "fig = px.line(\n", + " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", + ")\n", + "\n", + "fig.update_layout(legend_title_text=\"Edge\")\n", + "fig.write_html(model_dir / \"Crystal_2.1/plot_edges.html\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plotting results\n", + "@fig-sim7 illustrates the new storage and water level at the reservoir.\n", + "As expected, after increasing the profile of basin 3 to mimic the reservoir, its storage capacity increased as well.\n", + "\n", + "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align=\"left\" #fig-sim7}\n", + "\n", + "## Public Water Supply\n", + "\n", + "### Rename the saving files\n", + "Rename the files to `Crystal_2.2`\n", + "\n", + "### Add a demand node\n", + "$50.000$ people live in Crystal City.\n", + "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%$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.user_demand.add(\n", + " Node(9, Point(0.0, -0.25), name=\"PWS\"),\n", + " [\n", + " user_demand.Time(\n", + " # Total demand in m³/s\n", + " demand=[\n", + " 0.07,\n", + " 0.08,\n", + " 0.09,\n", + " 0.10,\n", + " 0.12,\n", + " 0.14,\n", + " 0.15,\n", + " 0.14,\n", + " 0.12,\n", + " 0.10,\n", + " 0.09,\n", + " 0.08,\n", + " ],\n", + " return_factor=0.6,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[\n", + " starttime,\n", + " \"2022-02-01\",\n", + " \"2022-03-01\",\n", + " \"2022-04-01\",\n", + " \"2022-05-01\",\n", + " \"2022-06-01\",\n", + " \"2022-07-01\",\n", + " \"2022-08-01\",\n", + " \"2022-09-01\",\n", + " \"2022-10-01\",\n", + " \"2022-11-01\",\n", + " \"2022-12-01\",\n", + " ],\n", + " )\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add the edges\n", + "The connection between the reservoir and the demand node needs to be defined:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.edge.add(model.flow_boundary[1], model.basin[3])\n", + "model.edge.add(model.flow_boundary[2], model.basin[4])\n", + "model.edge.add(model.basin[3], model.user_demand[6])\n", + "model.edge.add(model.basin[3], model.user_demand[9])\n", + "model.edge.add(model.user_demand[6], model.basin[4])\n", + "model.edge.add(model.user_demand[9], model.basin[4])\n", + "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", + "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", + "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", + "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adjust the name dictionaries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Dictionary mapping node_ids to names\n", + "edge_names = {\n", + " (1, 3): \"Main\",\n", + " (2, 4): \"Minor\",\n", + " (3, 6): \"IrrA Demand\",\n", + " (6, 4): \"IrrA Drain\",\n", + " (3, 9): \"PWS Demand\",\n", + " (9, 4): \"PWS Return\",\n", + " (3, 7): \"Rsv2Main\",\n", + " (7, 4): \"Main2Conf\",\n", + " (4, 5): \"Conf2TRC\",\n", + " (5, 8): \"TRC2Term\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check the simulated demands\n", + "@fig-sim8 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node.\n", + "@fig-sim9 shows the downstream flow to the ocean.\n", + "The impact is clear.\n", + "Due to the demands upstream (irrigation and public water supply) an expected decrease of discharge is shown downstream.\n", + "\n", + "![Simulated flows to and from the city](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-to-and-from-the-city.png){fig-align=\"left\" #fig-sim8}\n", + "\n", + "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels-reservoir.png){fig-align=\"left\" #fig-sim9}" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From c42d971cc8e406f6d2194779e92adbc2c547d332 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 10 Sep 2024 14:20:53 +0200 Subject: [PATCH 12/19] Finish splitting up the tutorials --- .pre-commit-config.yaml | 1 - .vscode/settings.json | 5 +- docs/tutorial/irrigation-demand.ipynb | 40 +- docs/tutorial/natural-flow.ipynb | 3 +- docs/tutorial/public-water-supply.ipynb | 913 +--------------------- docs/tutorial/reservoir.ipynb | 983 ++---------------------- 6 files changed, 104 insertions(+), 1841 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 545ad054f..00ab53872 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,6 @@ repos: hooks: - id: check-added-large-files - id: check-case-conflict - - id: check-json - id: check-yaml - id: check-toml - id: check-merge-conflict diff --git a/.vscode/settings.json b/.vscode/settings.json index b634ff85e..3865d5886 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,8 +4,9 @@ }, "notebook.formatOnSave.enabled": false, "notebook.codeActionsOnSave": { - "source.fixAll.ruff": true, - "source.organizeImports.ruff": true + // https://github.com/astral-sh/ruff-vscode/issues/593 + "source.fixAll.ruff": "never", + "source.organizeImports.ruff": "never" }, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", diff --git a/docs/tutorial/irrigation-demand.ipynb b/docs/tutorial/irrigation-demand.ipynb index 6ee762df9..27ce12c14 100644 --- a/docs/tutorial/irrigation-demand.ipynb +++ b/docs/tutorial/irrigation-demand.ipynb @@ -7,11 +7,37 @@ "outputs": [], "source": [ "import subprocess # For running the model\n", + "from pathlib import Path\n", "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", - "from ribasim import Node\n", - "from ribasim.nodes import basin, tabulated_rating_curve" + "import plotly.express as px\n", + "from ribasim import Model, Node\n", + "from ribasim.nodes import (\n", + " basin,\n", + " tabulated_rating_curve,\n", + " user_demand,\n", + ")\n", + "from shapely.geometry import Point" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "base_dir = Path(\"c:/bin/ribasim\")\n", + "model_dir = base_dir / \"Crystal_Basin\"\n", + "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "\n", + "starttime = \"2022-01-01\"\n", + "endtime = \"2023-01-01\"\n", + "model = Model(\n", + " starttime=starttime,\n", + " endtime=endtime,\n", + " crs=\"EPSG:4326\",\n", + ")" ] }, { @@ -241,6 +267,16 @@ "metadata": {}, "outputs": [], "source": [ + "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_wide = df_basin.pivot_table(\n", + " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", + ")\n", + "\n", + "# Skip the first timestep as it is the initialization step\n", + "df_basin_wide = df_basin_wide.iloc[1:]\n", + "\n", "df_basin_div = df_basin_wide.xs(\"Div\", axis=1, level=1, drop_level=False)\n", "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", "\n", diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb index 8341a5f1f..efde4c53e 100644 --- a/docs/tutorial/natural-flow.ipynb +++ b/docs/tutorial/natural-flow.ipynb @@ -83,7 +83,8 @@ "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "from ribasim import Model, Node\n", - "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve" + "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve\n", + "from shapely.geometry import Point" ] }, { diff --git a/docs/tutorial/public-water-supply.ipynb b/docs/tutorial/public-water-supply.ipynb index 31727694a..977ef2e17 100644 --- a/docs/tutorial/public-water-supply.ipynb +++ b/docs/tutorial/public-water-supply.ipynb @@ -6,104 +6,13 @@ "metadata": {}, "outputs": [], "source": [ - "import subprocess # For running the model\n", "from pathlib import Path\n", "\n", - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", "from ribasim import Model, Node\n", - "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "title: \"Quick start guide\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align=\"left\"}\n", - "\n", - "# Introduction\n", - "Welcome to Ribasim!\n", - "This tutorial will help you get started with the basics of using Ribasim for river basin simulation.\n", - "In this tutorial, the schematization of models is done in Python using the Ribasim Python package.\n", - "The Ribasim Python package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", - "It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible.\n", - "\n", - "## Learning objectives\n", - "In this tutorial, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", - "The guide is divided into different modules, each covering various scenarios.\n", - "These include simulating natural flow, implementing reservoirs, and observing the impact of other structures.\n", - "While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations.\n", - "By the end of the tutorial, users will be able to:\n", - "\n", - "- **Set up a basic Ribasim model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", - "- **Evaluate the impact of demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", - "- **Modify and update models**: Learn how to update existing models with new data and changes.\n", - "- **Analyze simulation results**: Use built-in tools to analyze and interpret the results of your simulations.\n", - "\n", - "## Prerequisites\n", - "First install the latest release of Ribasim as documented in [the installation guide](/install.qmd).\n", - "\n", - "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:\n", - "\n", - "- `QuickStartGuide.pdf`\n", - "- `data`: Contains data inputs such as time series needed for running the case.\n", - "Additionally, your Python model (`.py`) and the results will also be saved in this folder." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Crystal River Basin\n", - "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 @fig-crystal-basin).\n", - "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", - "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", - "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", - "\n", - "![Crystal Basin based on natural flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-Basin-based-on-natural-flow.png){fig-align=\"left\" #fig-crystal-basin}\n", - "\n", - "After this module the user will be able to:\n", - "\n", - "- Build a river basin model from scratch\n", - "- Understand the functionality of the Demand and Basin nodes\n", - "- Generate overview of results\n", - "- Evaluate the simulation results\n", - "\n", - "## Natural flow\n", - "\n", - "### Import packages\n", - "Before building the model we need to import some modules.\n", - "Open your favorite Python editor (Visual Studio Code, Jupyter, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", - "Import the following modules in Python:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import plotly.express as px\n", - "from ribasim.nodes import user_demand" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup paths and model configuration\n", - "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.\n", - "The coordinate reference system (CRS) is also required, and set to [EPSG:4326](https://epsg.io/4326), which means all coordinates are interpreted as latitude and longitude values.\n", - "The CRS is important for correctly placing Ribasim models on the map, but since this is a fictional model, it is not important." + "from ribasim.nodes import (\n", + " user_demand,\n", + ")\n", + "from shapely.geometry import Point" ] }, { @@ -129,820 +38,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### FlowBoundary nodes\n", - "The Crystal basin consists of two inflow points, the tributary and the main Crystal river, we will call them Minor and Main respectively.\n", - "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", - "This is a monthly inflow timeseries from 2014 to 2023.\n", - "The used simulation period is defined by the `starttime` and `endtime` of the model, not by the input timeseries." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = pd.DataFrame({\n", - " \"time\": pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\"),\n", - " \"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],\n", - " \"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]\n", - "}) # fmt: skip\n", - "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", - "display(data)\n", - "\n", - "# Average and max inflow of the total inflow data timeseries\n", - "# From 2014 - 2023\n", - "print(\"Average inflow [m3/s]:\", data[\"total\"].mean())\n", - "print(\"Maximum inflow [m3/s]:\", data[\"total\"].max())\n", - "\n", - "main = model.flow_boundary.add(\n", - " Node(1, Point(0.0, 0.0), name=\"main\"),\n", - " [\n", - " flow_boundary.Time(\n", - " time=data.time,\n", - " flow_rate=data.main,\n", - " )\n", - " ],\n", - ")\n", - "\n", - "minor = model.flow_boundary.add(\n", - " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", - " [\n", - " flow_boundary.Time(\n", - " time=data.time,\n", - " flow_rate=data.minor,\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Basin node (confluence)\n", - "To schematize the confluence from the tributary we will use the Basin node.\n", - "The node by itself portrays as water storage with a certain volume of water and can be used for different purposes, such as a reservoir, river reach, lake or in this case a confluence.\n", - "@fig-confluence visualizes a cross section of the confluence point in our model.\n", - "\n", - "![Basin node concept for the confluence](https://s3.deltares.nl/ribasim/doc-image/quickstart/Basin-node-concept-for-the-confluence.png){fig-align=\"left\" #fig-confluence}\n", - "\n", - "@tbl-input1 shows the input data for the Basin node profile.\n", - "\n", - ": Profile data for the basin node {#tbl-input1}\n", - "\n", - "| Area [$\\text{m}^2$] | Level [$\\text{m}$] |\n", - "|---------------------|--------------------|\n", - "| $672000.0$ | $0.0$ |\n", - "| $5600000.0$ | $6.0$ |\n", - "\n", - "Whilst in this case the level starts at $0.0$ and therefore happens to be the same as the depth, it should never be interpreted as a depth.\n", - "All water levels in Ribasim are assumed to be with respect to a shared reference datum, like mean sea level (MSL).\n", - "The first water level in the profile is the height of the Basin bottom above this reference datum.\n", - "\n", - "To specify the Basin profile, the following code is used:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "confluence = model.basin.add(\n", - " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", - " [\n", - " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", - " basin.State(level=[4]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### TabulatedRatingCurve\n", - "In the previous step we implemented a Basin node that functions as a confluence.\n", - "Conceptually, the Basin acts as a store of water, accumulating inflows and then releasing them.\n", - "A Basin cannot directly connect to another Basin, because the rules for water exchange between them need to be defined.\n", - "Connector nodes take care of this.\n", - "The first such node we introduce is the TabulatedRatingCurve.\n", - "It defines a relation between the water level ($h$) in the Basin and the outflow ($Q$) from the Basin.\n", - "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", - "\n", - "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{m}^3/\\text{s}$.\n", - "It is therefore expected that the confluence Basin goes towards a level where the outflow is equal to the inflow via the rating curve.\n", - "Only then is the confluence Basin in equilibrium.\n", - "The maximum depth of the river is $6 \\text{m}$, and the maximum inflow is $221.5 \\text{m}^3/\\text{s}$\n", - "The $Q(h)$ relationship in @tbl-input2 allows such inflows with reasonable water levels.\n", - "\n", - ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", - "\n", - "| Water Level ($h$) [$\\text{m}$] | Outflow ($Q$) [$\\text{m}^3/\\text{s}$] |\n", - "| -------------------------------|---------------------------------------|\n", - "| $0.0$ | $0.0$ |\n", - "| $2.0$ | $50.0$ |\n", - "| $5.0$ | $200.0$ |\n", - "\n", - "In Ribasim, the $Q(h)$ relation is a piecewise linear function, so the points in between will be linearly interpolated.\n", - "@fig-discharge illustrates the visual process and shows a progressive increase in discharge with rising water levels.\n", - "In this case this means:\n", - "\n", - "- At level $0.0$: No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.\n", - "- At level $2.0$: Discharge is $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.\n", - "- 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.\n", - "\n", - "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", - "\n", - "Taking this into account, add the `TabulatedRatingCurve` as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "weir = model.tabulated_rating_curve.add(\n", - " Node(4, Point(-1.5, -1.5), name=\"weir\"),\n", - " [\n", - " tabulated_rating_curve.Static(\n", - " level=[0.0, 2, 5],\n", - " flow_rate=[0.0, 50, 200],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Terminal node\n", - "Finally all the water will discharge into the sea.\n", - "We schematize this with the Terminal node, as it portrays the end point of the model, that can receive but not give water.\n", - "Besides the node number/name and location, no further input is needed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sea = model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Defining edges\n", - "Implement the connections (edges) between the nodes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.edge.add(main, confluence)\n", - "model.edge.add(minor, confluence)\n", - "model.edge.add(confluence, weir)\n", - "model.edge.add(weir, sea)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Visualization and model execution\n", - "Plot the schematization." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.plot();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Write the model configuration to the `TOML` file.\n", - "Name the output file `Crystal_1.1/ribasim.toml`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "toml_path = model_dir / \"Crystal_1.1/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "The schematization should look like @fig-cs11.\n", - "\n", - "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", - "\n", - "After running `model.write` a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", - "\n", - "- ribasim.toml: The model configuration\n", - "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used.\n", - "\n", - "Now run the model:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result = subprocess.run([cli_path, toml_path], capture_output=True, encoding=\"utf-8\")\n", - "print(result.stderr)\n", - "result.check_returncode()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Post-processing results\n", - "Read the Arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", - "\n", - "# Create pivot tables and plot for basin data\n", - "df_basin_wide = df_basin.pivot_table(\n", - " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", - ")\n", - "\n", - "# Skip the first timestep as it is the initialization step\n", - "df_basin_wide = df_basin_wide.iloc[1:]\n", - "\n", - "# Plot level and storage on the same graph with dual y-axes\n", - "fig, ax1 = plt.subplots(figsize=(12, 6))\n", - "\n", - "# Plot level on the primary y-axis\n", - "color = \"b\"\n", - "ax1.set_xlabel(\"Time\")\n", - "ax1.set_ylabel(\"Level [m]\", color=color)\n", - "ax1.plot(df_basin_wide.index, df_basin_wide[\"level\"], color=color)\n", - "ax1.tick_params(axis=\"y\", labelcolor=color)\n", - "\n", - "# Create a secondary y-axis for storage\n", - "ax2 = ax1.twinx()\n", - "color = \"r\"\n", - "ax2.set_ylabel(\"Storage [m³]\", color=\"r\")\n", - "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"], linestyle=\"--\", color=color)\n", - "ax2.tick_params(axis=\"y\", labelcolor=color)\n", - "\n", - "fig.tight_layout() # Adjust layout to fit labels\n", - "plt.title(\"Basin Level and Storage Over Time\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot flow data\n", - "# Read the flow results\n", - "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", - "# Create 'edge' and 'flow_m3d' columns\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", - "\n", - "# Create a pivot table\n", - "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_rate\")\n", - "\n", - "# Skip the first timestep\n", - "pivot_flow = pivot_flow.iloc[1:]\n", - "\n", - "line_styles = [\"-\", \"--\", \"-\", \"-.\"]\n", - "num_styles = len(line_styles)\n", - "\n", - "fig, ax = plt.subplots(figsize=(12, 6))\n", - "for i, column in enumerate(pivot_flow.columns):\n", - " pivot_flow[column].plot(\n", - " ax=ax, linestyle=line_styles[i % num_styles], linewidth=1.5, alpha=0.8\n", - " )\n", - "\n", - "# Set labels and title\n", - "ax.set_xlabel(\"Time\")\n", - "ax.set_ylabel(\"Flow [m³/s]\")\n", - "ax.legend(bbox_to_anchor=(1.15, 1), title=\"Edge\")\n", - "plt.title(\"Flow Over Time\")\n", - "plt.grid(True)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "@fig-sim1 shows the storage and levels in the Basin node.\n", - "\n", - "To accurately represent the relationship between water levels and discharge rates at this confluence, a TabulatedRatingCurve is used.\n", - "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", - "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.\n", - "This is because there is no net change in storage; all incoming water is balanced by outgoing flow.\n", - "\n", - "![Simulated basin level and storage](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-level-and-storage.png){fig-align=\"left\" #fig-sim1}\n", - "\n", - "@fig-sim2 shows the discharges in $\\text{m}^3/\\text{s}$ on each edge.\n", - "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.\n", - "Both show the same discharge over time.\n", - "Which is expected in a natural flow environment, as what is coming into the confluence must come out.\n", - "\n", - "![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align=\"left\" #fig-sim2}\n", - "\n", - "## Irrigation demand\n", - "\n", - "Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation.\n", - "Water is diverted from the main river through an irrigation canal, with a portion of it eventually returning to the main river (see @fig-irrigation).\n", - "\n", - "![Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-irrigation}\n", - "\n", - "For this schematization update, we need to incorporate three additional nodes:\n", - "\n", - "- Basin: Represents a cross-sectional point where water is diverted.\n", - "- UserDemand: Represents the irrigation demand.\n", - "- TabulatedRatingCurve: Defines the remaining water flow from the main river at the diversion point." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add a second Basin node\n", - "Basin #3 will portray as the point in the river where the diversion takes place, getting the name `diversion`.\n", - "Its profile area at this intersection is slightly smaller than at the confluence." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name=\"diversion\"),\n", - " [\n", - " basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", - " basin.State(level=[3]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")\n", - "\n", - "model.basin.add(\n", - " Node(4, Point(-1.5, -1), name=\"confluence\"),\n", - " [\n", - " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", - " basin.State(level=[4]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add the irrigation demand\n", - "A big farm company needs to apply irrigation to its field starting from April to September.\n", - "The irrigated field is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", - "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.\n", - "Start of irrigation takes place on the 1st of April until the end of August.\n", - "The farmer taps water through a canal (demand).\n", - "\n", - "For now, let's assume the return flow remains $0.0$ (`return_factor`).\n", - "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", - "The user demand node interpolates the demand values. Thus the following code needs to be implemented:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.user_demand.add(\n", - " Node(6, Point(-1.5, 1.0), name=\"IrrA\"),\n", - " [\n", - " user_demand.Time(\n", - " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", - " return_factor=0,\n", - " min_level=0,\n", - " priority=1,\n", - " time=[\n", - " starttime,\n", - " \"2022-03-31\",\n", - " \"2022-04-01\",\n", - " \"2022-07-01\",\n", - " \"2022-09-30\",\n", - " \"2022-10-01\",\n", - " ],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add a TabulatedRatingCurve\n", - "The second TabulatedRatingCurve node will simulate the rest of the water that is left after diverting a part from the main river to the farm field.\n", - "The rest of the water will flow naturally towards the confluence:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.tabulated_rating_curve.add(\n", - " Node(7, Point(-1.125, -0.75), name=\"MainDiv\"),\n", - " [\n", - " tabulated_rating_curve.Static(\n", - " level=[0.0, 1.5, 5],\n", - " flow_rate=[0.0, 45, 200],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It is up to the user to renumber the IDs of the nodes.\n", - "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", - "\n", - "### Adjust the Terminal node ID and edges\n", - "Adjust the Terminal node ID.\n", - "Since we added more nodes we have more edges. Add and adjust the edges:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.terminal.add(Node(8, Point(-1.5, -3.0), name=\"Terminal\"))\n", - "\n", - "model.edge.add(model.flow_boundary[1], model.basin[3])\n", - "model.edge.add(model.flow_boundary[2], model.basin[4])\n", - "model.edge.add(model.basin[3], model.user_demand[6])\n", - "model.edge.add(model.user_demand[6], model.basin[4])\n", - "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", - "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", - "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", - "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Plot model and run\n", - "Plot the schematization and run the model.\n", - "This time the new outputs should be written in a new folder called `Crystal_1.2`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.plot()\n", - "\n", - "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", - "\n", - "subprocess.run([cli_path, toml_path], check=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The schematization should look like @fig-cs12.\n", - "\n", - "![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-cs12}\n", - "\n", - "### Name the edges and basins\n", - "The names of each nodes are defined and saved in the geopackage.\n", - "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Dictionary mapping node_ids to names\n", - "edge_names = {\n", - " (1, 3): \"Main\",\n", - " (2, 4): \"Minor\",\n", - " (3, 6): \"IrrA Demand\",\n", - " (6, 4): \"IrrA Drain\",\n", - " (3, 7): \"Div2Main\",\n", - " (7, 4): \"Main2Conf\",\n", - " (4, 5): \"Conf2TRC\",\n", - " (5, 8): \"TRC2Term\",\n", - "}\n", - "\n", - "# Dictionary mapping basins (node_ids) to names\n", - "node_names = {\n", - " 3: \"Div\",\n", - " 4: \"Conf\",\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Plot and compare the basin results\n", - "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_basin_div = df_basin_wide.xs(\"Div\", axis=1, level=1, drop_level=False)\n", - "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", - "\n", - "\n", - "def plot_basin_data(\n", - " ax, ax_twin, df_basin, level_color=\"b\", storage_color=\"r\", title=\"Basin\"\n", - "):\n", - " # Plot level data\n", - " for idx, column in enumerate(df_basin[\"level\"].columns):\n", - " ax.plot(\n", - " df_basin.index,\n", - " df_basin[\"level\"][column],\n", - " linestyle=\"-\",\n", - " color=level_color,\n", - " label=f\"Level - {column}\",\n", - " )\n", - "\n", - " # Plot storage data\n", - " for idx, column in enumerate(df_basin[\"storage\"].columns):\n", - " ax_twin.plot(\n", - " df_basin.index,\n", - " df_basin[\"storage\"][column],\n", - " linestyle=\"--\",\n", - " color=storage_color,\n", - " label=f\"Storage - {column}\",\n", - " )\n", - "\n", - " ax.set_ylabel(\"Level [m]\", color=level_color)\n", - " ax_twin.set_ylabel(\"Storage [m³]\", color=storage_color)\n", - "\n", - " ax.tick_params(axis=\"y\", labelcolor=level_color)\n", - " ax_twin.tick_params(axis=\"y\", labelcolor=storage_color)\n", - "\n", - " ax.set_title(title)\n", - "\n", - " # Combine legends from both axes\n", - " lines, labels = ax.get_legend_handles_labels()\n", - " lines_twin, labels_twin = ax_twin.get_legend_handles_labels()\n", - " ax.legend(lines + lines_twin, labels + labels_twin, loc=\"upper left\")\n", - "\n", - "\n", - "# Create subplots\n", - "fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True)\n", - "\n", - "# Plot Div basin data\n", - "ax2 = ax1.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax1, ax2, df_basin_div, title=\"Div Basin Level and Storage over Time\")\n", - "\n", - "# Plot Conf basin data\n", - "ax4 = ax3.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax3, ax4, df_basin_conf, title=\"Conf Basin Level and Storage over Time\")\n", - "\n", - "# Common X label\n", - "ax3.set_xlabel(\"Time\")\n", - "fig.tight_layout() # Adjust layout to fit labels\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", - "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.\n", - "\n", - "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.\n", - "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.\n", - "\n", - "![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align=\"left\" #fig-sim3}\n", - "\n", - "### Plot and compare the flow results\n", - "Plot the flow results in an interactive plotting tool." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_flow = pd.read_feather(model_dir / \"Crystal_1.2/results/flow.arrow\")\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", - "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", - "\n", - "# Plot the flow data, interactive plot with Plotly\n", - "pivot_flow = df_flow.pivot_table(\n", - " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", - ").reset_index()\n", - "fig = px.line(\n", - " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", - ")\n", - "\n", - "fig.update_layout(legend_title_text=\"Edge\")\n", - "fig.write_html(model_dir / \"Crystal_1.2/plot_edges.html\")\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", - "\n", - "![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align=\"left\" #fig-sim4}\n", - "\n", - "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 (@fig-sim5) and the return flow remains zero, as the assumption defined before was that there is no drain.\n", - "\n", - "![Supplied irrigation and return flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Supplied-irrigation-and-return-flow.png){fig-align=\"left\" #fig-sim5}\n", - "\n", - "@fig-sim6 shows the flow to the ocean (Terminal).\n", - "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", - "Indicating the impact of irrigation without any drain.\n", - "\n", - "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Reservoirs and Public Water Supply\n", - "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", - "The reservoir is to help ensure a reliable supply during dry periods.\n", - "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal Basin.\n", - "\n", - "![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align=\"left\" #fig-reservoir}\n", - "\n", - "## Reservoir\n", - "### Add a Basin\n", - "This time 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).\n", - "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", - "The profile of Basin #3 should change to:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name=\"Rsv\"),\n", - " [\n", - " basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", - " basin.State(level=[3.5]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjust the code\n", - "Adjust the naming of the Basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "toml_path = model_dir / \"Crystal_2.1/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", - "\n", - "# Dictionary mapping node_ids to names\n", - "edge_names = {\n", - " (1, 3): \"Main\",\n", - " (2, 4): \"Minor\",\n", - " (3, 6): \"IrrA Demand\",\n", - " (6, 4): \"IrrA Drain\",\n", - " (3, 7): \"Rsv2Main\",\n", - " (7, 4): \"Main2Conf\",\n", - " (4, 5): \"Conf2TRC\",\n", - " (5, 8): \"TRC2Term\",\n", - "}\n", - "\n", - "# Dictionary mapping basins (node_ids) to names\n", - "node_names = {\n", - " 3: \"Rsv\",\n", - " 4: \"Conf\",\n", - "}\n", - "\n", - "df_basin = pd.read_feather(model_dir / \"Crystal_2.1/results/basin.arrow\")\n", - "\n", - "# Create pivot tables and plot for basin data\n", - "df_basin_rsv = df_basin_wide.xs(\"Rsv\", axis=1, level=1, drop_level=False)\n", - "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", - "\n", - "# Plot Rsv basin data\n", - "ax2 = ax1.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax1, ax2, df_basin_rsv, title=\"Reservoir Level and Storage Over Time\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Sample data loading and preparation\n", - "df_flow = pd.read_feather(model_dir / \"Crystal_2.1/results/flow.arrow\")\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", - "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", - "\n", - "# Plot the flow data, interactive plot with Plotly\n", - "pivot_flow = df_flow.pivot_table(\n", - " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", - ").reset_index()\n", - "fig = px.line(\n", - " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", - ")\n", - "\n", - "fig.update_layout(legend_title_text=\"Edge\")\n", - "fig.write_html(model_dir / \"Crystal_2.1/plot_edges.html\")\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Plotting results\n", - "@fig-sim7 illustrates the new storage and water level at the reservoir.\n", - "As expected, after increasing the profile of basin 3 to mimic the reservoir, its storage capacity increased as well.\n", - "\n", - "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align=\"left\" #fig-sim7}\n", "\n", "## Public Water Supply\n", "\n", diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb index 31727694a..a722aa0e8 100644 --- a/docs/tutorial/reservoir.ipynb +++ b/docs/tutorial/reservoir.ipynb @@ -6,617 +6,14 @@ "metadata": {}, "outputs": [], "source": [ - "import subprocess # For running the model\n", "from pathlib import Path\n", "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", - "from ribasim import Model, Node\n", - "from ribasim.nodes import basin, flow_boundary, tabulated_rating_curve" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "title: \"Quick start guide\"\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align=\"left\"}\n", - "\n", - "# Introduction\n", - "Welcome to Ribasim!\n", - "This tutorial will help you get started with the basics of using Ribasim for river basin simulation.\n", - "In this tutorial, the schematization of models is done in Python using the Ribasim Python package.\n", - "The Ribasim Python package (named `ribasim`) simplifies the process of building, updating, and analyzing Ribasim model programmatically.\n", - "It also allows for the creation of entire models from base data, ensuring that your model setup is fully reproducible.\n", - "\n", - "## Learning objectives\n", - "In this tutorial, we will focus on a fictional river basin called Crystal, which will serve as our case study.\n", - "The guide is divided into different modules, each covering various scenarios.\n", - "These include simulating natural flow, implementing reservoirs, and observing the impact of other structures.\n", - "While not all node types and possibilities will be demonstrated, the focus will be on the most commonly used and significant situations.\n", - "By the end of the tutorial, users will be able to:\n", - "\n", - "- **Set up a basic Ribasim model**: Understand how to create a new model for a river basin using the Ribasim Python package.\n", - "- **Evaluate the impact of demands**: Introduce water demand (such as irrigation) and assess their effects on the river basin.\n", - "- **Modify and update models**: Learn how to update existing models with new data and changes.\n", - "- **Analyze simulation results**: Use built-in tools to analyze and interpret the results of your simulations.\n", - "\n", - "## Prerequisites\n", - "First install the latest release of Ribasim as documented in [the installation guide](/install.qmd).\n", - "\n", - "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:\n", - "\n", - "- `QuickStartGuide.pdf`\n", - "- `data`: Contains data inputs such as time series needed for running the case.\n", - "Additionally, your Python model (`.py`) and the results will also be saved in this folder." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Crystal River Basin\n", - "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 @fig-crystal-basin).\n", - "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", - "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", - "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", - "\n", - "![Crystal Basin based on natural flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-Basin-based-on-natural-flow.png){fig-align=\"left\" #fig-crystal-basin}\n", - "\n", - "After this module the user will be able to:\n", - "\n", - "- Build a river basin model from scratch\n", - "- Understand the functionality of the Demand and Basin nodes\n", - "- Generate overview of results\n", - "- Evaluate the simulation results\n", - "\n", - "## Natural flow\n", - "\n", - "### Import packages\n", - "Before building the model we need to import some modules.\n", - "Open your favorite Python editor (Visual Studio Code, Jupyter, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", - "Import the following modules in Python:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ "import plotly.express as px\n", - "from ribasim.nodes import user_demand" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup paths and model configuration\n", - "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.\n", - "The coordinate reference system (CRS) is also required, and set to [EPSG:4326](https://epsg.io/4326), which means all coordinates are interpreted as latitude and longitude values.\n", - "The CRS is important for correctly placing Ribasim models on the map, but since this is a fictional model, it is not important." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "base_dir = Path(\"c:/bin/ribasim\")\n", - "model_dir = base_dir / \"Crystal_Basin\"\n", - "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", - "\n", - "starttime = \"2022-01-01\"\n", - "endtime = \"2023-01-01\"\n", - "model = Model(\n", - " starttime=starttime,\n", - " endtime=endtime,\n", - " crs=\"EPSG:4326\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### FlowBoundary nodes\n", - "The Crystal basin consists of two inflow points, the tributary and the main Crystal river, we will call them Minor and Main respectively.\n", - "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", - "This is a monthly inflow timeseries from 2014 to 2023.\n", - "The used simulation period is defined by the `starttime` and `endtime` of the model, not by the input timeseries." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = pd.DataFrame({\n", - " \"time\": pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\"),\n", - " \"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],\n", - " \"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]\n", - "}) # fmt: skip\n", - "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", - "display(data)\n", - "\n", - "# Average and max inflow of the total inflow data timeseries\n", - "# From 2014 - 2023\n", - "print(\"Average inflow [m3/s]:\", data[\"total\"].mean())\n", - "print(\"Maximum inflow [m3/s]:\", data[\"total\"].max())\n", - "\n", - "main = model.flow_boundary.add(\n", - " Node(1, Point(0.0, 0.0), name=\"main\"),\n", - " [\n", - " flow_boundary.Time(\n", - " time=data.time,\n", - " flow_rate=data.main,\n", - " )\n", - " ],\n", - ")\n", - "\n", - "minor = model.flow_boundary.add(\n", - " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", - " [\n", - " flow_boundary.Time(\n", - " time=data.time,\n", - " flow_rate=data.minor,\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Basin node (confluence)\n", - "To schematize the confluence from the tributary we will use the Basin node.\n", - "The node by itself portrays as water storage with a certain volume of water and can be used for different purposes, such as a reservoir, river reach, lake or in this case a confluence.\n", - "@fig-confluence visualizes a cross section of the confluence point in our model.\n", - "\n", - "![Basin node concept for the confluence](https://s3.deltares.nl/ribasim/doc-image/quickstart/Basin-node-concept-for-the-confluence.png){fig-align=\"left\" #fig-confluence}\n", - "\n", - "@tbl-input1 shows the input data for the Basin node profile.\n", - "\n", - ": Profile data for the basin node {#tbl-input1}\n", - "\n", - "| Area [$\\text{m}^2$] | Level [$\\text{m}$] |\n", - "|---------------------|--------------------|\n", - "| $672000.0$ | $0.0$ |\n", - "| $5600000.0$ | $6.0$ |\n", - "\n", - "Whilst in this case the level starts at $0.0$ and therefore happens to be the same as the depth, it should never be interpreted as a depth.\n", - "All water levels in Ribasim are assumed to be with respect to a shared reference datum, like mean sea level (MSL).\n", - "The first water level in the profile is the height of the Basin bottom above this reference datum.\n", - "\n", - "To specify the Basin profile, the following code is used:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "confluence = model.basin.add(\n", - " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", - " [\n", - " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", - " basin.State(level=[4]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### TabulatedRatingCurve\n", - "In the previous step we implemented a Basin node that functions as a confluence.\n", - "Conceptually, the Basin acts as a store of water, accumulating inflows and then releasing them.\n", - "A Basin cannot directly connect to another Basin, because the rules for water exchange between them need to be defined.\n", - "Connector nodes take care of this.\n", - "The first such node we introduce is the TabulatedRatingCurve.\n", - "It defines a relation between the water level ($h$) in the Basin and the outflow ($Q$) from the Basin.\n", - "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", - "\n", - "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{m}^3/\\text{s}$.\n", - "It is therefore expected that the confluence Basin goes towards a level where the outflow is equal to the inflow via the rating curve.\n", - "Only then is the confluence Basin in equilibrium.\n", - "The maximum depth of the river is $6 \\text{m}$, and the maximum inflow is $221.5 \\text{m}^3/\\text{s}$\n", - "The $Q(h)$ relationship in @tbl-input2 allows such inflows with reasonable water levels.\n", - "\n", - ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", - "\n", - "| Water Level ($h$) [$\\text{m}$] | Outflow ($Q$) [$\\text{m}^3/\\text{s}$] |\n", - "| -------------------------------|---------------------------------------|\n", - "| $0.0$ | $0.0$ |\n", - "| $2.0$ | $50.0$ |\n", - "| $5.0$ | $200.0$ |\n", - "\n", - "In Ribasim, the $Q(h)$ relation is a piecewise linear function, so the points in between will be linearly interpolated.\n", - "@fig-discharge illustrates the visual process and shows a progressive increase in discharge with rising water levels.\n", - "In this case this means:\n", - "\n", - "- At level $0.0$: No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.\n", - "- At level $2.0$: Discharge is $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.\n", - "- 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.\n", - "\n", - "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", - "\n", - "Taking this into account, add the `TabulatedRatingCurve` as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "weir = model.tabulated_rating_curve.add(\n", - " Node(4, Point(-1.5, -1.5), name=\"weir\"),\n", - " [\n", - " tabulated_rating_curve.Static(\n", - " level=[0.0, 2, 5],\n", - " flow_rate=[0.0, 50, 200],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Terminal node\n", - "Finally all the water will discharge into the sea.\n", - "We schematize this with the Terminal node, as it portrays the end point of the model, that can receive but not give water.\n", - "Besides the node number/name and location, no further input is needed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sea = model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Defining edges\n", - "Implement the connections (edges) between the nodes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.edge.add(main, confluence)\n", - "model.edge.add(minor, confluence)\n", - "model.edge.add(confluence, weir)\n", - "model.edge.add(weir, sea)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Visualization and model execution\n", - "Plot the schematization." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.plot();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Write the model configuration to the `TOML` file.\n", - "Name the output file `Crystal_1.1/ribasim.toml`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "toml_path = model_dir / \"Crystal_1.1/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "The schematization should look like @fig-cs11.\n", - "\n", - "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", - "\n", - "After running `model.write` a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", - "\n", - "- ribasim.toml: The model configuration\n", - "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used.\n", - "\n", - "Now run the model:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result = subprocess.run([cli_path, toml_path], capture_output=True, encoding=\"utf-8\")\n", - "print(result.stderr)\n", - "result.check_returncode()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Post-processing results\n", - "Read the Arrow files and plot the simulated flows from different edges and the levels and storages at our confluence point:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", - "\n", - "# Create pivot tables and plot for basin data\n", - "df_basin_wide = df_basin.pivot_table(\n", - " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", - ")\n", - "\n", - "# Skip the first timestep as it is the initialization step\n", - "df_basin_wide = df_basin_wide.iloc[1:]\n", - "\n", - "# Plot level and storage on the same graph with dual y-axes\n", - "fig, ax1 = plt.subplots(figsize=(12, 6))\n", - "\n", - "# Plot level on the primary y-axis\n", - "color = \"b\"\n", - "ax1.set_xlabel(\"Time\")\n", - "ax1.set_ylabel(\"Level [m]\", color=color)\n", - "ax1.plot(df_basin_wide.index, df_basin_wide[\"level\"], color=color)\n", - "ax1.tick_params(axis=\"y\", labelcolor=color)\n", - "\n", - "# Create a secondary y-axis for storage\n", - "ax2 = ax1.twinx()\n", - "color = \"r\"\n", - "ax2.set_ylabel(\"Storage [m³]\", color=\"r\")\n", - "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"], linestyle=\"--\", color=color)\n", - "ax2.tick_params(axis=\"y\", labelcolor=color)\n", - "\n", - "fig.tight_layout() # Adjust layout to fit labels\n", - "plt.title(\"Basin Level and Storage Over Time\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot flow data\n", - "# Read the flow results\n", - "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", - "# Create 'edge' and 'flow_m3d' columns\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", - "\n", - "# Create a pivot table\n", - "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_rate\")\n", - "\n", - "# Skip the first timestep\n", - "pivot_flow = pivot_flow.iloc[1:]\n", - "\n", - "line_styles = [\"-\", \"--\", \"-\", \"-.\"]\n", - "num_styles = len(line_styles)\n", - "\n", - "fig, ax = plt.subplots(figsize=(12, 6))\n", - "for i, column in enumerate(pivot_flow.columns):\n", - " pivot_flow[column].plot(\n", - " ax=ax, linestyle=line_styles[i % num_styles], linewidth=1.5, alpha=0.8\n", - " )\n", - "\n", - "# Set labels and title\n", - "ax.set_xlabel(\"Time\")\n", - "ax.set_ylabel(\"Flow [m³/s]\")\n", - "ax.legend(bbox_to_anchor=(1.15, 1), title=\"Edge\")\n", - "plt.title(\"Flow Over Time\")\n", - "plt.grid(True)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "@fig-sim1 shows the storage and levels in the Basin node.\n", - "\n", - "To accurately represent the relationship between water levels and discharge rates at this confluence, a TabulatedRatingCurve is used.\n", - "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", - "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.\n", - "This is because there is no net change in storage; all incoming water is balanced by outgoing flow.\n", - "\n", - "![Simulated basin level and storage](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-level-and-storage.png){fig-align=\"left\" #fig-sim1}\n", - "\n", - "@fig-sim2 shows the discharges in $\\text{m}^3/\\text{s}$ on each edge.\n", - "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.\n", - "Both show the same discharge over time.\n", - "Which is expected in a natural flow environment, as what is coming into the confluence must come out.\n", - "\n", - "![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align=\"left\" #fig-sim2}\n", - "\n", - "## Irrigation demand\n", - "\n", - "Let us modify the environment to include agricultural activities within the basin, which necessitate irrigation.\n", - "Water is diverted from the main river through an irrigation canal, with a portion of it eventually returning to the main river (see @fig-irrigation).\n", - "\n", - "![Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-irrigation}\n", - "\n", - "For this schematization update, we need to incorporate three additional nodes:\n", - "\n", - "- Basin: Represents a cross-sectional point where water is diverted.\n", - "- UserDemand: Represents the irrigation demand.\n", - "- TabulatedRatingCurve: Defines the remaining water flow from the main river at the diversion point." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add a second Basin node\n", - "Basin #3 will portray as the point in the river where the diversion takes place, getting the name `diversion`.\n", - "Its profile area at this intersection is slightly smaller than at the confluence." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name=\"diversion\"),\n", - " [\n", - " basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", - " basin.State(level=[3]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")\n", - "\n", - "model.basin.add(\n", - " Node(4, Point(-1.5, -1), name=\"confluence\"),\n", - " [\n", - " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", - " basin.State(level=[4]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add the irrigation demand\n", - "A big farm company needs to apply irrigation to its field starting from April to September.\n", - "The irrigated field is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", - "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.\n", - "Start of irrigation takes place on the 1st of April until the end of August.\n", - "The farmer taps water through a canal (demand).\n", - "\n", - "For now, let's assume the return flow remains $0.0$ (`return_factor`).\n", - "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", - "The user demand node interpolates the demand values. Thus the following code needs to be implemented:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.user_demand.add(\n", - " Node(6, Point(-1.5, 1.0), name=\"IrrA\"),\n", - " [\n", - " user_demand.Time(\n", - " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", - " return_factor=0,\n", - " min_level=0,\n", - " priority=1,\n", - " time=[\n", - " starttime,\n", - " \"2022-03-31\",\n", - " \"2022-04-01\",\n", - " \"2022-07-01\",\n", - " \"2022-09-30\",\n", - " \"2022-10-01\",\n", - " ],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add a TabulatedRatingCurve\n", - "The second TabulatedRatingCurve node will simulate the rest of the water that is left after diverting a part from the main river to the farm field.\n", - "The rest of the water will flow naturally towards the confluence:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.tabulated_rating_curve.add(\n", - " Node(7, Point(-1.125, -0.75), name=\"MainDiv\"),\n", - " [\n", - " tabulated_rating_curve.Static(\n", - " level=[0.0, 1.5, 5],\n", - " flow_rate=[0.0, 45, 200],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It is up to the user to renumber the IDs of the nodes.\n", - "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", - "\n", - "### Adjust the Terminal node ID and edges\n", - "Adjust the Terminal node ID.\n", - "Since we added more nodes we have more edges. Add and adjust the edges:" + "from ribasim import Model, Node\n", + "from ribasim.nodes import basin\n", + "from shapely.geometry import Point" ] }, { @@ -625,25 +22,35 @@ "metadata": {}, "outputs": [], "source": [ - "model.terminal.add(Node(8, Point(-1.5, -3.0), name=\"Terminal\"))\n", + "base_dir = Path(\"c:/bin/ribasim\")\n", + "model_dir = base_dir / \"Crystal_Basin\"\n", + "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", "\n", - "model.edge.add(model.flow_boundary[1], model.basin[3])\n", - "model.edge.add(model.flow_boundary[2], model.basin[4])\n", - "model.edge.add(model.basin[3], model.user_demand[6])\n", - "model.edge.add(model.user_demand[6], model.basin[4])\n", - "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", - "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", - "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", - "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" + "starttime = \"2022-01-01\"\n", + "endtime = \"2023-01-01\"\n", + "model = Model(\n", + " starttime=starttime,\n", + " endtime=endtime,\n", + " crs=\"EPSG:4326\",\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Plot model and run\n", - "Plot the schematization and run the model.\n", - "This time the new outputs should be written in a new folder called `Crystal_1.2`:" + "# Reservoirs and Public Water Supply\n", + "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", + "The reservoir is to help ensure a reliable supply during dry periods.\n", + "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal Basin.\n", + "\n", + "![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align=\"left\" #fig-reservoir}\n", + "\n", + "## Reservoir\n", + "### Add a Basin\n", + "This time 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).\n", + "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", + "The profile of Basin #3 should change to:" ] }, { @@ -652,26 +59,22 @@ "metadata": {}, "outputs": [], "source": [ - "model.plot()\n", - "\n", - "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", - "\n", - "subprocess.run([cli_path, toml_path], check=True)" + "model.basin.add(\n", + " Node(3, Point(-0.75, -0.5), name=\"Rsv\"),\n", + " [\n", + " basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", + " basin.State(level=[3.5]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The schematization should look like @fig-cs12.\n", - "\n", - "![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-cs12}\n", - "\n", - "### Name the edges and basins\n", - "The names of each nodes are defined and saved in the geopackage.\n", - "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe." + "### Adjust the code\n", + "Adjust the naming of the Basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`." ] }, { @@ -680,13 +83,17 @@ "metadata": {}, "outputs": [], "source": [ + "toml_path = model_dir / \"Crystal_2.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "\n", "# Dictionary mapping node_ids to names\n", "edge_names = {\n", " (1, 3): \"Main\",\n", " (2, 4): \"Minor\",\n", " (3, 6): \"IrrA Demand\",\n", " (6, 4): \"IrrA Drain\",\n", - " (3, 7): \"Div2Main\",\n", + " (3, 7): \"Rsv2Main\",\n", " (7, 4): \"Main2Conf\",\n", " (4, 5): \"Conf2TRC\",\n", " (5, 8): \"TRC2Term\",\n", @@ -694,26 +101,23 @@ "\n", "# Dictionary mapping basins (node_ids) to names\n", "node_names = {\n", - " 3: \"Div\",\n", + " 3: \"Rsv\",\n", " 4: \"Conf\",\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Plot and compare the basin results\n", - "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_basin_div = df_basin_wide.xs(\"Div\", axis=1, level=1, drop_level=False)\n", + "}\n", + "\n", + "df_basin = pd.read_feather(model_dir / \"Crystal_2.1/results/basin.arrow\")\n", + "\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_wide = df_basin.pivot_table(\n", + " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", + ")\n", + "\n", + "# Skip the first timestep as it is the initialization step\n", + "df_basin_wide = df_basin_wide.iloc[1:]\n", + "\n", + "\n", + "# Create pivot tables and plot for basin data\n", + "df_basin_rsv = df_basin_wide.xs(\"Rsv\", axis=1, level=1, drop_level=False)\n", "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", "\n", "\n", @@ -757,154 +161,6 @@ "# Create subplots\n", "fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True)\n", "\n", - "# Plot Div basin data\n", - "ax2 = ax1.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax1, ax2, df_basin_div, title=\"Div Basin Level and Storage over Time\")\n", - "\n", - "# Plot Conf basin data\n", - "ax4 = ax3.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax3, ax4, df_basin_conf, title=\"Conf Basin Level and Storage over Time\")\n", - "\n", - "# Common X label\n", - "ax3.set_xlabel(\"Time\")\n", - "fig.tight_layout() # Adjust layout to fit labels\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", - "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.\n", - "\n", - "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.\n", - "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.\n", - "\n", - "![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align=\"left\" #fig-sim3}\n", - "\n", - "### Plot and compare the flow results\n", - "Plot the flow results in an interactive plotting tool." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_flow = pd.read_feather(model_dir / \"Crystal_1.2/results/flow.arrow\")\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", - "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", - "\n", - "# Plot the flow data, interactive plot with Plotly\n", - "pivot_flow = df_flow.pivot_table(\n", - " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", - ").reset_index()\n", - "fig = px.line(\n", - " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", - ")\n", - "\n", - "fig.update_layout(legend_title_text=\"Edge\")\n", - "fig.write_html(model_dir / \"Crystal_1.2/plot_edges.html\")\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", - "\n", - "![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align=\"left\" #fig-sim4}\n", - "\n", - "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 (@fig-sim5) and the return flow remains zero, as the assumption defined before was that there is no drain.\n", - "\n", - "![Supplied irrigation and return flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Supplied-irrigation-and-return-flow.png){fig-align=\"left\" #fig-sim5}\n", - "\n", - "@fig-sim6 shows the flow to the ocean (Terminal).\n", - "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", - "Indicating the impact of irrigation without any drain.\n", - "\n", - "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Reservoirs and Public Water Supply\n", - "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", - "The reservoir is to help ensure a reliable supply during dry periods.\n", - "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal Basin.\n", - "\n", - "![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align=\"left\" #fig-reservoir}\n", - "\n", - "## Reservoir\n", - "### Add a Basin\n", - "This time 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).\n", - "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", - "The profile of Basin #3 should change to:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name=\"Rsv\"),\n", - " [\n", - " basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", - " basin.State(level=[3.5]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjust the code\n", - "Adjust the naming of the Basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "toml_path = model_dir / \"Crystal_2.1/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", - "\n", - "# Dictionary mapping node_ids to names\n", - "edge_names = {\n", - " (1, 3): \"Main\",\n", - " (2, 4): \"Minor\",\n", - " (3, 6): \"IrrA Demand\",\n", - " (6, 4): \"IrrA Drain\",\n", - " (3, 7): \"Rsv2Main\",\n", - " (7, 4): \"Main2Conf\",\n", - " (4, 5): \"Conf2TRC\",\n", - " (5, 8): \"TRC2Term\",\n", - "}\n", - "\n", - "# Dictionary mapping basins (node_ids) to names\n", - "node_names = {\n", - " 3: \"Rsv\",\n", - " 4: \"Conf\",\n", - "}\n", - "\n", - "df_basin = pd.read_feather(model_dir / \"Crystal_2.1/results/basin.arrow\")\n", - "\n", - "# Create pivot tables and plot for basin data\n", - "df_basin_rsv = df_basin_wide.xs(\"Rsv\", axis=1, level=1, drop_level=False)\n", - "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", - "\n", "# Plot Rsv basin data\n", "ax2 = ax1.twinx() # Secondary y-axis for storage\n", "plot_basin_data(ax1, ax2, df_basin_rsv, title=\"Reservoir Level and Storage Over Time\")" @@ -942,132 +198,7 @@ "@fig-sim7 illustrates the new storage and water level at the reservoir.\n", "As expected, after increasing the profile of basin 3 to mimic the reservoir, its storage capacity increased as well.\n", "\n", - "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align=\"left\" #fig-sim7}\n", - "\n", - "## Public Water Supply\n", - "\n", - "### Rename the saving files\n", - "Rename the files to `Crystal_2.2`\n", - "\n", - "### Add a demand node\n", - "$50.000$ people live in Crystal City.\n", - "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%$." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.user_demand.add(\n", - " Node(9, Point(0.0, -0.25), name=\"PWS\"),\n", - " [\n", - " user_demand.Time(\n", - " # Total demand in m³/s\n", - " demand=[\n", - " 0.07,\n", - " 0.08,\n", - " 0.09,\n", - " 0.10,\n", - " 0.12,\n", - " 0.14,\n", - " 0.15,\n", - " 0.14,\n", - " 0.12,\n", - " 0.10,\n", - " 0.09,\n", - " 0.08,\n", - " ],\n", - " return_factor=0.6,\n", - " min_level=0,\n", - " priority=1,\n", - " time=[\n", - " starttime,\n", - " \"2022-02-01\",\n", - " \"2022-03-01\",\n", - " \"2022-04-01\",\n", - " \"2022-05-01\",\n", - " \"2022-06-01\",\n", - " \"2022-07-01\",\n", - " \"2022-08-01\",\n", - " \"2022-09-01\",\n", - " \"2022-10-01\",\n", - " \"2022-11-01\",\n", - " \"2022-12-01\",\n", - " ],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add the edges\n", - "The connection between the reservoir and the demand node needs to be defined:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.edge.add(model.flow_boundary[1], model.basin[3])\n", - "model.edge.add(model.flow_boundary[2], model.basin[4])\n", - "model.edge.add(model.basin[3], model.user_demand[6])\n", - "model.edge.add(model.basin[3], model.user_demand[9])\n", - "model.edge.add(model.user_demand[6], model.basin[4])\n", - "model.edge.add(model.user_demand[9], model.basin[4])\n", - "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", - "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", - "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", - "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjust the name dictionaries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Dictionary mapping node_ids to names\n", - "edge_names = {\n", - " (1, 3): \"Main\",\n", - " (2, 4): \"Minor\",\n", - " (3, 6): \"IrrA Demand\",\n", - " (6, 4): \"IrrA Drain\",\n", - " (3, 9): \"PWS Demand\",\n", - " (9, 4): \"PWS Return\",\n", - " (3, 7): \"Rsv2Main\",\n", - " (7, 4): \"Main2Conf\",\n", - " (4, 5): \"Conf2TRC\",\n", - " (5, 8): \"TRC2Term\",\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Check the simulated demands\n", - "@fig-sim8 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node.\n", - "@fig-sim9 shows the downstream flow to the ocean.\n", - "The impact is clear.\n", - "Due to the demands upstream (irrigation and public water supply) an expected decrease of discharge is shown downstream.\n", - "\n", - "![Simulated flows to and from the city](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-to-and-from-the-city.png){fig-align=\"left\" #fig-sim8}\n", - "\n", - "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels-reservoir.png){fig-align=\"left\" #fig-sim9}" + "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align=\"left\" #fig-sim7}" ] } ], From f4bd011b91e74e87a6ebe8990d219c90a43505a1 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 10 Sep 2024 20:53:51 +0200 Subject: [PATCH 13/19] Titles --- docs/tutorial/irrigation-demand.ipynb | 9 +++++++++ docs/tutorial/natural-flow.ipynb | 4 +--- docs/tutorial/public-water-supply.ipynb | 9 +++++++++ docs/tutorial/reservoir.ipynb | 9 +++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/irrigation-demand.ipynb b/docs/tutorial/irrigation-demand.ipynb index 27ce12c14..20d8ee850 100644 --- a/docs/tutorial/irrigation-demand.ipynb +++ b/docs/tutorial/irrigation-demand.ipynb @@ -1,5 +1,14 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: \"Irrigation demand\"\n", + "---" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb index efde4c53e..770e4b0e2 100644 --- a/docs/tutorial/natural-flow.ipynb +++ b/docs/tutorial/natural-flow.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "---\n", - "title: \"Quick start guide\"\n", + "title: \"Getting started\"\n", "---" ] }, @@ -13,8 +13,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "![](https://s3.deltares.nl/ribasim/doc-image/quickstart/cover.png){fig-align=\"left\"}\n", - "\n", "# Introduction\n", "Welcome to Ribasim!\n", "This tutorial will help you get started with the basics of using Ribasim for river basin simulation.\n", diff --git a/docs/tutorial/public-water-supply.ipynb b/docs/tutorial/public-water-supply.ipynb index 977ef2e17..0eac6cfa0 100644 --- a/docs/tutorial/public-water-supply.ipynb +++ b/docs/tutorial/public-water-supply.ipynb @@ -1,5 +1,14 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: \"Public water supply\"\n", + "---" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb index a722aa0e8..f9866e6fc 100644 --- a/docs/tutorial/reservoir.ipynb +++ b/docs/tutorial/reservoir.ipynb @@ -1,5 +1,14 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: \"Reservoir\"\n", + "---" + ] + }, { "cell_type": "code", "execution_count": null, From e019f625aa7434e984dd8242c589bc5bedf6cf1d Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 17 Sep 2024 16:25:12 +0200 Subject: [PATCH 14/19] progress --- docs/.gitignore | 1 + docs/guide/examples.ipynb | 13 -- docs/tutorial/irrigation-demand.ipynb | 201 +++++++++++++++--------- docs/tutorial/natural-flow.ipynb | 97 +++++++----- docs/tutorial/public-water-supply.ipynb | 24 ++- docs/tutorial/reservoir.ipynb | 43 +++-- 6 files changed, 237 insertions(+), 142 deletions(-) diff --git a/docs/.gitignore b/docs/.gitignore index cd2d14289..f9379ecd7 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -2,6 +2,7 @@ /_site/ /site_libs/ /reference/python/ +/tutorial/crystal-basin/ guide/data/ *.html objects.json diff --git a/docs/guide/examples.ipynb b/docs/guide/examples.ipynb index 0c967f7d0..4bdcfcbf1 100644 --- a/docs/guide/examples.ipynb +++ b/docs/guide/examples.ipynb @@ -405,19 +405,6 @@ "ribasim basic/ribasim.toml\n", "```\n", "\n", - "From Python you can run it with:\n", - "\n", - "```python\n", - "import subprocess\n", - "result = subprocess.run([cli_path, toml_path], capture_output=True, encoding=\"utf-8\")\n", - "print(result.stderr)\n", - "result.check_returncode()\n", - "```\n", - "\n", - "Where `cli_path` is a string with either the full path to the Ribasim executable, like `r\"c:\\ribasim_windows\\ribasim\"`, or just `\"ribasim\"` in case you added the `ribasim_windows` folder to your PATH.\n", - "\n", - "The `print(result.stderr)` ensures you see the same logging and error messages that you would see in the terminal. And `result.check_returncode()` will throw an error when the simulation was not successful.\n", - "\n", "After running the model, read back the results:" ] }, diff --git a/docs/tutorial/irrigation-demand.ipynb b/docs/tutorial/irrigation-demand.ipynb index 20d8ee850..141be640f 100644 --- a/docs/tutorial/irrigation-demand.ipynb +++ b/docs/tutorial/irrigation-demand.ipynb @@ -24,6 +24,7 @@ "from ribasim import Model, Node\n", "from ribasim.nodes import (\n", " basin,\n", + " flow_boundary,\n", " tabulated_rating_curve,\n", " user_demand,\n", ")\n", @@ -36,9 +37,7 @@ "metadata": {}, "outputs": [], "source": [ - "base_dir = Path(\"c:/bin/ribasim\")\n", - "model_dir = base_dir / \"Crystal_Basin\"\n", - "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "base_dir = Path(\"crystal-basin\")\n", "\n", "starttime = \"2022-01-01\"\n", "endtime = \"2023-01-01\"\n", @@ -49,6 +48,70 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These nodes are identical to the previous tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# FlowBoundary\n", + "data = pd.DataFrame({\n", + " \"time\": pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\"),\n", + " \"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],\n", + " \"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]\n", + "}) # fmt: skip\n", + "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", + "main = model.flow_boundary.add(\n", + " Node(1, Point(0.0, 0.0), name=\"main\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.main,\n", + " )\n", + " ],\n", + ")\n", + "minor = model.flow_boundary.add(\n", + " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.minor,\n", + " )\n", + " ],\n", + ")\n", + "\n", + "# Basin\n", + "confluence = model.basin.add(\n", + " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "\n", + "# TabulatedRatingCurve\n", + "weir = model.tabulated_rating_curve.add(\n", + " Node(4, Point(-1.5, -1.5), name=\"weir\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 2, 5],\n", + " flow_rate=[0.0, 50, 200],\n", + " )\n", + " ],\n", + ")\n", + "\n", + "# Terminal\n", + "sea = model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -72,7 +135,7 @@ "metadata": {}, "source": [ "### Add a second Basin node\n", - "Basin #3 will portray as the point in the river where the diversion takes place, getting the name `diversion`.\n", + "This Basin will portray as the point in the river where the diversion takes place, getting the name `diversion`.\n", "Its profile area at this intersection is slightly smaller than at the confluence." ] }, @@ -82,22 +145,13 @@ "metadata": {}, "outputs": [], "source": [ - "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name=\"diversion\"),\n", + "diversion_basin = model.basin.add(\n", + " Node(6, Point(-0.75, -0.5), name=\"diversion_basin\"),\n", " [\n", " basin.Profile(area=[500000, 5000000], level=[0, 6]),\n", " basin.State(level=[3]),\n", " basin.Time(time=[starttime, endtime]),\n", " ],\n", - ")\n", - "\n", - "model.basin.add(\n", - " Node(4, Point(-1.5, -1), name=\"confluence\"),\n", - " [\n", - " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", - " basin.State(level=[4]),\n", - " basin.Time(time=[starttime, endtime]),\n", - " ],\n", ")" ] }, @@ -106,11 +160,11 @@ "metadata": {}, "source": [ "### Add the irrigation demand\n", - "A big farm company needs to apply irrigation to its field starting from April to September.\n", - "The irrigated field is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", - "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.\n", + "An irrigation district needs to apply irrigation to its field starting from April to September.\n", + "The irrigated area is $> 17000 \\text{ ha}$ and requires around $5 \\text{ mm/day}$.\n", + "In this case the irrigation district 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.\n", "Start of irrigation takes place on the 1st of April until the end of August.\n", - "The farmer taps water through a canal (demand).\n", + "The water intake is through a canal (demand).\n", "\n", "For now, let's assume the return flow remains $0.0$ (`return_factor`).\n", "Meaning all the supplied water to fulfill the demand is consumed and does not return back to the river.\n", @@ -123,8 +177,8 @@ "metadata": {}, "outputs": [], "source": [ - "model.user_demand.add(\n", - " Node(6, Point(-1.5, 1.0), name=\"IrrA\"),\n", + "irrigation = model.user_demand.add(\n", + " Node(7, Point(-1.5, 1.0), name=\"irrigation\"),\n", " [\n", " user_demand.Time(\n", " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", @@ -149,7 +203,7 @@ "metadata": {}, "source": [ "### Add a TabulatedRatingCurve\n", - "The second TabulatedRatingCurve node will simulate the rest of the water that is left after diverting a part from the main river to the farm field.\n", + "The second TabulatedRatingCurve node will simulate the rest of the water that is left after diverting a part from the main river to the irrigation disctrict.\n", "The rest of the water will flow naturally towards the confluence:" ] }, @@ -159,8 +213,8 @@ "metadata": {}, "outputs": [], "source": [ - "model.tabulated_rating_curve.add(\n", - " Node(7, Point(-1.125, -0.75), name=\"MainDiv\"),\n", + "diversion_weir = model.tabulated_rating_curve.add(\n", + " Node(8, Point(-1.125, -0.75), name=\"diversion_weir\"),\n", " [\n", " tabulated_rating_curve.Static(\n", " level=[0.0, 1.5, 5],\n", @@ -174,12 +228,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It is up to the user to renumber the IDs of the nodes.\n", - "Applying the ID number based on the order of the nodes from up- to downstream keeps it more organized, but not necessary.\n", - "\n", - "### Adjust the Terminal node ID and edges\n", - "Adjust the Terminal node ID.\n", - "Since we added more nodes we have more edges. Add and adjust the edges:" + "### Add edges" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.edge.add(main, diversion_basin)\n", + "model.edge.add(minor, confluence)\n", + "model.edge.add(diversion_basin, irrigation)\n", + "model.edge.add(irrigation, confluence)\n", + "model.edge.add(diversion_basin, diversion_weir)\n", + "model.edge.add(diversion_weir, confluence)\n", + "model.edge.add(confluence, weir)\n", + "model.edge.add(weir, sea)" ] }, { @@ -188,16 +253,9 @@ "metadata": {}, "outputs": [], "source": [ - "model.terminal.add(Node(8, Point(-1.5, -3.0), name=\"Terminal\"))\n", - "\n", - "model.edge.add(model.flow_boundary[1], model.basin[3])\n", - "model.edge.add(model.flow_boundary[2], model.basin[4])\n", - "model.edge.add(model.basin[3], model.user_demand[6])\n", - "model.edge.add(model.user_demand[6], model.basin[4])\n", - "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", - "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", - "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", - "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" + "toml_path = base_dir / \"Crystal_1.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = \"ribasim\"" ] }, { @@ -215,26 +273,27 @@ "metadata": {}, "outputs": [], "source": [ - "model.plot()\n", - "\n", - "toml_path = model_dir / \"Crystal_1.2/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", - "\n", - "subprocess.run([cli_path, toml_path], check=True)" + "model.plot();" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "The schematization should look like @fig-cs12.\n", - "\n", - "![Schematization of the Crystal basin with irrigation](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-with-irrigation.png){fig-align=\"left\" #fig-cs12}\n", + "# | include: false\n", + "from subprocess import run\n", "\n", - "### Name the edges and basins\n", - "The names of each nodes are defined and saved in the geopackage.\n", - "However, in the dataframe this needs to be added by creating a dictionary and map it within the dataframe." + "run(\n", + " [\n", + " \"julia\",\n", + " \"--project=../../core\",\n", + " \"--eval\",\n", + " f'using Ribasim; Ribasim.main(\"{toml_path.as_posix()}\")',\n", + " ],\n", + " check=True,\n", + ")" ] }, { @@ -243,31 +302,21 @@ "metadata": {}, "outputs": [], "source": [ - "# Dictionary mapping node_ids to names\n", - "edge_names = {\n", - " (1, 3): \"Main\",\n", - " (2, 4): \"Minor\",\n", - " (3, 6): \"IrrA Demand\",\n", - " (6, 4): \"IrrA Drain\",\n", - " (3, 7): \"Div2Main\",\n", - " (7, 4): \"Main2Conf\",\n", - " (4, 5): \"Conf2TRC\",\n", - " (5, 8): \"TRC2Term\",\n", - "}\n", - "\n", - "# Dictionary mapping basins (node_ids) to names\n", - "node_names = {\n", - " 3: \"Div\",\n", - " 4: \"Conf\",\n", - "}" + "model.plot()\n", + "\n", + "toml_path = base_dir / \"Crystal_1.2/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = \"ribasim\"\n", + "\n", + "subprocess.run([cli_path, toml_path], check=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Plot and compare the basin results\n", - "Plot the simulated levels and storages at the diverted section (basin 3) and at the confluence (basin 4)." + "### Plot and compare the Basin results\n", + "Plot the simulated levels and storages at the diverted section and at the confluence." ] }, { @@ -276,7 +325,7 @@ "metadata": {}, "outputs": [], "source": [ - "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "df_basin = pd.read_feather(base_dir / \"Crystal_1.1/results/basin.arrow\")\n", "\n", "# Create pivot tables and plot for basin data\n", "df_basin_wide = df_basin.pivot_table(\n", @@ -366,7 +415,7 @@ "metadata": {}, "outputs": [], "source": [ - "df_flow = pd.read_feather(model_dir / \"Crystal_1.2/results/flow.arrow\")\n", + "df_flow = pd.read_feather(base_dir / \"Crystal_1.2/results/flow.arrow\")\n", "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", "\n", @@ -379,7 +428,7 @@ ")\n", "\n", "fig.update_layout(legend_title_text=\"Edge\")\n", - "fig.write_html(model_dir / \"Crystal_1.2/plot_edges.html\")\n", + "fig.write_html(base_dir / \"Crystal_1.2/plot_edges.html\")\n", "fig.show()" ] }, diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb index 770e4b0e2..d42330005 100644 --- a/docs/tutorial/natural-flow.ipynb +++ b/docs/tutorial/natural-flow.ipynb @@ -75,7 +75,6 @@ "metadata": {}, "outputs": [], "source": [ - "import subprocess # For running the model\n", "from pathlib import Path\n", "\n", "import matplotlib.pyplot as plt\n", @@ -101,9 +100,7 @@ "metadata": {}, "outputs": [], "source": [ - "base_dir = Path(\"c:/bin/ribasim\")\n", - "model_dir = base_dir / \"Crystal_Basin\"\n", - "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "base_dir = Path(\"crystal-basin\")\n", "\n", "starttime = \"2022-01-01\"\n", "endtime = \"2023-01-01\"\n", @@ -120,20 +117,10 @@ "source": [ "### FlowBoundary nodes\n", "The Crystal basin consists of two inflow points, the tributary and the main Crystal river, we will call them Minor and Main respectively.\n", - "In order to define the time series flow rate ($\\text{m}^3/\\text{s}$) we read the discharge data from `ACTINFLW.csv`.\n", "This is a monthly inflow timeseries from 2014 to 2023.\n", "The used simulation period is defined by the `starttime` and `endtime` of the model, not by the input timeseries." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\")" - ] - }, { "cell_type": "code", "execution_count": null, @@ -344,26 +331,19 @@ "metadata": {}, "outputs": [], "source": [ - "toml_path = model_dir / \"Crystal_1.1/ribasim.toml\"\n", + "toml_path = base_dir / \"Crystal_1.1/ribasim.toml\"\n", "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"" + "cli_path = \"ribasim\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "The schematization should look like @fig-cs11.\n", - "\n", - "![Schematization of the Crystal basin 1.1](https://s3.deltares.nl/ribasim/doc-image/quickstart/Schematization-of-the-Crystal-basin-1.1.png){fig-align=\"left\" #fig-cs11}\n", - "\n", "After running `model.write` a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", "\n", "- ribasim.toml: The model configuration\n", - "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used.\n", - "\n", - "Now run the model:" + "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used." ] }, { @@ -372,9 +352,42 @@ "metadata": {}, "outputs": [], "source": [ + "# | include: false\n", + "from subprocess import run\n", + "\n", + "run(\n", + " [\n", + " \"julia\",\n", + " \"--project=../../core\",\n", + " \"--eval\",\n", + " f'using Ribasim; Ribasim.main(\"{toml_path.as_posix()}\")',\n", + " ],\n", + " check=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now run the model. You can open a terminal and run it from there. For example:\n", + "\n", + "```bash\n", + "ribasim Crystal_1.1/ribasim.toml\n", + "```\n", + "\n", + "From Python you can run it with:\n", + "\n", + "```python\n", + "import subprocess\n", "result = subprocess.run([cli_path, toml_path], capture_output=True, encoding=\"utf-8\")\n", "print(result.stderr)\n", - "result.check_returncode()" + "result.check_returncode()\n", + "```\n", + "\n", + "Where `cli_path` is a string with either the full path to the Ribasim executable, like `r\"c:\\bin\\ribasim\\ribasim\"`, or just `\"ribasim\"` in case you added the `ribasim` folder to your PATH.\n", + "\n", + "The `print(result.stderr)` ensures you see the same logging and error messages that you would see in the terminal. And `result.check_returncode()` will throw an error when the simulation was not successful." ] }, { @@ -391,9 +404,9 @@ "metadata": {}, "outputs": [], "source": [ - "df_basin = pd.read_feather(model_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "df_basin = pd.read_feather(base_dir / \"Crystal_1.1/results/basin.arrow\")\n", "\n", - "# Create pivot tables and plot for basin data\n", + "# Create pivot tables and plot for Basin data\n", "df_basin_wide = df_basin.pivot_table(\n", " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", ")\n", @@ -423,6 +436,18 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The figure above shows the storage and levels in the Basin node.\n", + "\n", + "To accurately represent the relationship between water levels and discharge rates at this confluence, a TabulatedRatingCurve is used.\n", + "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", + "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.\n", + "This is because there is no net change in storage; all incoming water is balanced by outgoing flow." + ] + }, { "cell_type": "code", "execution_count": null, @@ -431,7 +456,7 @@ "source": [ "# Plot flow data\n", "# Read the flow results\n", - "df_flow = pd.read_feather(model_dir / \"Crystal_1.1/results/flow.arrow\")\n", + "df_flow = pd.read_feather(base_dir / \"Crystal_1.1/results/flow.arrow\")\n", "# Create 'edge' and 'flow_m3d' columns\n", "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", "\n", @@ -463,21 +488,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "@fig-sim1 shows the storage and levels in the Basin node.\n", - "\n", - "To accurately represent the relationship between water levels and discharge rates at this confluence, a TabulatedRatingCurve is used.\n", - "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", - "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.\n", - "This is because there is no net change in storage; all incoming water is balanced by outgoing flow.\n", + "The figure above shows the discharges in $\\text{m}^3/\\text{s}$ on each edge.\n", "\n", - "![Simulated basin level and storage](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-level-and-storage.png){fig-align=\"left\" #fig-sim1}\n", - "\n", - "@fig-sim2 shows the discharges in $\\text{m}^3/\\text{s}$ on each edge.\n", - "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.\n", + "Edge (3,4) represents the flow from the confluence to the TabulatedRatingCurve and edge (4,5) represents the flow from the TabulatedRatingCurve to the Terminal.\n", "Both show the same discharge over time.\n", - "Which is expected in a natural flow environment, as what is coming into the confluence must come out.\n", - "\n", - "![Simulated flows on each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-on-each-edge.jpg){fig-align=\"left\" #fig-sim2}" + "Which is expected in a natural flow environment, as what is coming into the confluence must come out." ] } ], diff --git a/docs/tutorial/public-water-supply.ipynb b/docs/tutorial/public-water-supply.ipynb index 0eac6cfa0..796737d48 100644 --- a/docs/tutorial/public-water-supply.ipynb +++ b/docs/tutorial/public-water-supply.ipynb @@ -30,9 +30,7 @@ "metadata": {}, "outputs": [], "source": [ - "base_dir = Path(\"c:/bin/ribasim\")\n", - "model_dir = base_dir / \"Crystal_Basin\"\n", - "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "base_dir = Path(\"crystal-basin\")\n", "\n", "starttime = \"2022-01-01\"\n", "endtime = \"2023-01-01\"\n", @@ -159,6 +157,26 @@ "}" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# | include: false\n", + "from subprocess import run\n", + "\n", + "run(\n", + " [\n", + " \"julia\",\n", + " \"--project=../../core\",\n", + " \"--eval\",\n", + " f'using Ribasim; Ribasim.main(\"{toml_path.as_posix()}\")',\n", + " ],\n", + " check=True,\n", + ")" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb index f9866e6fc..7b4ceb648 100644 --- a/docs/tutorial/reservoir.ipynb +++ b/docs/tutorial/reservoir.ipynb @@ -31,9 +31,7 @@ "metadata": {}, "outputs": [], "source": [ - "base_dir = Path(\"c:/bin/ribasim\")\n", - "model_dir = base_dir / \"Crystal_Basin\"\n", - "data_path = model_dir / \"data/input/ACTINFLW.csv\"\n", + "base_dir = Path(\"crystal-basin\")\n", "\n", "starttime = \"2022-01-01\"\n", "endtime = \"2023-01-01\"\n", @@ -92,10 +90,37 @@ "metadata": {}, "outputs": [], "source": [ - "toml_path = model_dir / \"Crystal_2.1/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = base_dir / \"ribasim_windows/ribasim.exe\"\n", + "# | include: false\n", + "from subprocess import run\n", "\n", + "run(\n", + " [\n", + " \"julia\",\n", + " \"--project=../../core\",\n", + " \"--eval\",\n", + " f'using Ribasim; Ribasim.main(\"{toml_path.as_posix()}\")',\n", + " ],\n", + " check=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "toml_path = base_dir / \"Crystal_2.1/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = \"ribasim\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Dictionary mapping node_ids to names\n", "edge_names = {\n", " (1, 3): \"Main\",\n", @@ -114,7 +139,7 @@ " 4: \"Conf\",\n", "}\n", "\n", - "df_basin = pd.read_feather(model_dir / \"Crystal_2.1/results/basin.arrow\")\n", + "df_basin = pd.read_feather(base_dir / \"Crystal_2.1/results/basin.arrow\")\n", "\n", "# Create pivot tables and plot for basin data\n", "df_basin_wide = df_basin.pivot_table(\n", @@ -182,7 +207,7 @@ "outputs": [], "source": [ "# Sample data loading and preparation\n", - "df_flow = pd.read_feather(model_dir / \"Crystal_2.1/results/flow.arrow\")\n", + "df_flow = pd.read_feather(base_dir / \"Crystal_2.1/results/flow.arrow\")\n", "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", "\n", @@ -195,7 +220,7 @@ ")\n", "\n", "fig.update_layout(legend_title_text=\"Edge\")\n", - "fig.write_html(model_dir / \"Crystal_2.1/plot_edges.html\")\n", + "fig.write_html(base_dir / \"Crystal_2.1/plot_edges.html\")\n", "fig.show()" ] }, From b15cfb5b63e2dfec1d2e79f25aae723aca6bab9d Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Thu, 19 Sep 2024 15:39:58 +0200 Subject: [PATCH 15/19] Finish up --- docs/_quarto.yml | 1 - docs/tutorial/irrigation-demand.ipynb | 82 ++----- docs/tutorial/natural-flow.ipynb | 35 ++- docs/tutorial/public-water-supply.ipynb | 217 ---------------- docs/tutorial/reservoir.ipynb | 313 ++++++++++++++++-------- 5 files changed, 249 insertions(+), 399 deletions(-) delete mode 100644 docs/tutorial/public-water-supply.ipynb diff --git a/docs/_quarto.yml b/docs/_quarto.yml index c0e911611..11645a750 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -38,7 +38,6 @@ website: - tutorial/natural-flow.ipynb - tutorial/irrigation-demand.ipynb - tutorial/reservoir.ipynb - - tutorial/public-water-supply.ipynb - title: "How-to guides" contents: diff --git a/docs/tutorial/irrigation-demand.ipynb b/docs/tutorial/irrigation-demand.ipynb index 141be640f..768608cf2 100644 --- a/docs/tutorial/irrigation-demand.ipynb +++ b/docs/tutorial/irrigation-demand.ipynb @@ -15,7 +15,6 @@ "metadata": {}, "outputs": [], "source": [ - "import subprocess # For running the model\n", "from pathlib import Path\n", "\n", "import matplotlib.pyplot as plt\n", @@ -178,7 +177,7 @@ "outputs": [], "source": [ "irrigation = model.user_demand.add(\n", - " Node(7, Point(-1.5, 1.0), name=\"irrigation\"),\n", + " Node(7, Point(-1.5, 0.5), name=\"irrigation\"),\n", " [\n", " user_demand.Time(\n", " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", @@ -237,14 +236,14 @@ "metadata": {}, "outputs": [], "source": [ - "model.edge.add(main, diversion_basin)\n", - "model.edge.add(minor, confluence)\n", - "model.edge.add(diversion_basin, irrigation)\n", + "model.edge.add(main, diversion_basin, name=\"main\")\n", + "model.edge.add(minor, confluence, name=\"minor\")\n", + "model.edge.add(diversion_basin, irrigation, name=\"irrigation\")\n", "model.edge.add(irrigation, confluence)\n", - "model.edge.add(diversion_basin, diversion_weir)\n", + "model.edge.add(diversion_basin, diversion_weir, name=\"not diverted\")\n", "model.edge.add(diversion_weir, confluence)\n", "model.edge.add(confluence, weir)\n", - "model.edge.add(weir, sea)" + "model.edge.add(weir, sea, name=\"sea\")" ] }, { @@ -253,7 +252,7 @@ "metadata": {}, "outputs": [], "source": [ - "toml_path = base_dir / \"Crystal_1.1/ribasim.toml\"\n", + "toml_path = base_dir / \"Crystal-2/ribasim.toml\"\n", "model.write(toml_path)\n", "cli_path = \"ribasim\"" ] @@ -264,7 +263,7 @@ "source": [ "### Plot model and run\n", "Plot the schematization and run the model.\n", - "This time the new outputs should be written in a new folder called `Crystal_1.2`:" + "This time the new outputs should be written in a new folder called `Crystal-2`:" ] }, { @@ -296,21 +295,6 @@ ")" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.plot()\n", - "\n", - "toml_path = base_dir / \"Crystal_1.2/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = \"ribasim\"\n", - "\n", - "subprocess.run([cli_path, toml_path], check=True)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -325,25 +309,22 @@ "metadata": {}, "outputs": [], "source": [ - "df_basin = pd.read_feather(base_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "df_basin = pd.read_feather(base_dir / \"Crystal-2/results/basin.arrow\")\n", "\n", "# Create pivot tables and plot for basin data\n", "df_basin_wide = df_basin.pivot_table(\n", " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", ")\n", "\n", - "# Skip the first timestep as it is the initialization step\n", - "df_basin_wide = df_basin_wide.iloc[1:]\n", - "\n", - "df_basin_div = df_basin_wide.xs(\"Div\", axis=1, level=1, drop_level=False)\n", - "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", + "df_basin_div = df_basin_wide.loc[:, pd.IndexSlice[:, diversion_basin.node_id]]\n", + "df_basin_conf = df_basin_wide.loc[:, pd.IndexSlice[:, confluence.node_id]]\n", "\n", "\n", "def plot_basin_data(\n", " ax, ax_twin, df_basin, level_color=\"b\", storage_color=\"r\", title=\"Basin\"\n", "):\n", " # Plot level data\n", - " for idx, column in enumerate(df_basin[\"level\"].columns):\n", + " for column in df_basin[\"level\"].columns:\n", " ax.plot(\n", " df_basin.index,\n", " df_basin[\"level\"][column],\n", @@ -353,7 +334,7 @@ " )\n", "\n", " # Plot storage data\n", - " for idx, column in enumerate(df_basin[\"storage\"].columns):\n", + " for column in df_basin[\"storage\"].columns:\n", " ax_twin.plot(\n", " df_basin.index,\n", " df_basin[\"storage\"][column],\n", @@ -381,11 +362,11 @@ "\n", "# Plot Div basin data\n", "ax2 = ax1.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax1, ax2, df_basin_div, title=\"Div Basin Level and Storage over Time\")\n", + "plot_basin_data(ax1, ax2, df_basin_div, title=\"Diversion Basin level and storage\")\n", "\n", "# Plot Conf basin data\n", "ax4 = ax3.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax3, ax4, df_basin_conf, title=\"Conf Basin Level and Storage over Time\")\n", + "plot_basin_data(ax3, ax4, df_basin_conf, title=\"Confluence Basin level and storage\")\n", "\n", "# Common X label\n", "ax3.set_xlabel(\"Time\")\n", @@ -397,14 +378,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "@fig-sim3 illustrates the water levels and storage capacities for each basin.\n", - "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.\n", + "The figure above illustrates the water levels and storage capacities for each Basin.\n", "\n", - "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.\n", + "When compared to the natural flow conditions, where no water is abstracted for irrigation (See Crystal 1), there is a noticeable decrease in both storage and water levels at the confluence downstream.\n", "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.\n", "\n", - "![Simulated basin levels and storages](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-levels-and-storages.png){fig-align=\"left\" #fig-sim3}\n", - "\n", "### Plot and compare the flow results\n", "Plot the flow results in an interactive plotting tool." ] @@ -415,20 +393,18 @@ "metadata": {}, "outputs": [], "source": [ - "df_flow = pd.read_feather(base_dir / \"Crystal_1.2/results/flow.arrow\")\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", - "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", + "df_flow = pd.read_feather(base_dir / \"Crystal-2/results/flow.arrow\")\n", + "# Add the edge names and then remove unnamed edges\n", + "df_flow[\"name\"] = model.edge.df[\"name\"].loc[df_flow[\"edge_id\"]].to_numpy()\n", + "df_flow = df_flow[df_flow[\"name\"].astype(bool)]\n", "\n", "# Plot the flow data, interactive plot with Plotly\n", "pivot_flow = df_flow.pivot_table(\n", " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", ").reset_index()\n", - "fig = px.line(\n", - " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", - ")\n", + "fig = px.line(pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow [m3/s]\")\n", "\n", "fig.update_layout(legend_title_text=\"Edge\")\n", - "fig.write_html(base_dir / \"Crystal_1.2/plot_edges.html\")\n", "fig.show()" ] }, @@ -436,19 +412,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The plot will be saved as an HTML file, which can be viewed by dragging the file into an internet browser (@fig-sim4).\n", - "\n", - "![Simulated flows of each edge](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-of-each-edge.png){fig-align=\"left\" #fig-sim4}\n", - "\n", - "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 (@fig-sim5) and the return flow remains zero, as the assumption defined before was that there is no drain.\n", - "\n", - "![Supplied irrigation and return flow](https://s3.deltares.nl/ribasim/doc-image/quickstart/Supplied-irrigation-and-return-flow.png){fig-align=\"left\" #fig-sim5}\n", - "\n", - "@fig-sim6 shows the flow to the ocean (Terminal).\n", - "Compared to Crystal 1.1 the flow has decreased during the irrigated period.\n", - "Indicating the impact of irrigation without any drain.\n", - "\n", - "![Simulated flow to Terminal](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flow-to-Terminal.png){fig-align=\"left\" #fig-sim6}" + "Try toggling the edges on and off by clicking on them in the edges." ] } ], diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb index d42330005..db990bb14 100644 --- a/docs/tutorial/natural-flow.ipynb +++ b/docs/tutorial/natural-flow.ipynb @@ -294,10 +294,10 @@ "metadata": {}, "outputs": [], "source": [ - "model.edge.add(main, confluence)\n", - "model.edge.add(minor, confluence)\n", + "model.edge.add(main, confluence, name=\"main\")\n", + "model.edge.add(minor, confluence, name=\"minor\")\n", "model.edge.add(confluence, weir)\n", - "model.edge.add(weir, sea)" + "model.edge.add(weir, sea, name=\"sea\")" ] }, { @@ -322,7 +322,7 @@ "metadata": {}, "source": [ "Write the model configuration to the `TOML` file.\n", - "Name the output file `Crystal_1.1/ribasim.toml`:" + "Name the output file `Crystal-1/ribasim.toml`:" ] }, { @@ -331,7 +331,7 @@ "metadata": {}, "outputs": [], "source": [ - "toml_path = base_dir / \"Crystal_1.1/ribasim.toml\"\n", + "toml_path = base_dir / \"Crystal-1/ribasim.toml\"\n", "model.write(toml_path)\n", "cli_path = \"ribasim\"" ] @@ -340,7 +340,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "After running `model.write` a subfolder `Crystal_1.1` is created, which contains the model input data and configuration:\n", + "After running `model.write` a subfolder `Crystal-1` is created, which contains the model input data and configuration:\n", "\n", "- ribasim.toml: The model configuration\n", "- database.gpkg: A GeoPackage containing the network geometry and input data of the nodes used." @@ -373,7 +373,7 @@ "Now run the model. You can open a terminal and run it from there. For example:\n", "\n", "```bash\n", - "ribasim Crystal_1.1/ribasim.toml\n", + "ribasim Crystal-1/ribasim.toml\n", "```\n", "\n", "From Python you can run it with:\n", @@ -404,16 +404,13 @@ "metadata": {}, "outputs": [], "source": [ - "df_basin = pd.read_feather(base_dir / \"Crystal_1.1/results/basin.arrow\")\n", + "df_basin = pd.read_feather(base_dir / \"Crystal-1/results/basin.arrow\")\n", "\n", "# Create pivot tables and plot for Basin data\n", "df_basin_wide = df_basin.pivot_table(\n", " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", ")\n", "\n", - "# Skip the first timestep as it is the initialization step\n", - "df_basin_wide = df_basin_wide.iloc[1:]\n", - "\n", "# Plot level and storage on the same graph with dual y-axes\n", "fig, ax1 = plt.subplots(figsize=(12, 6))\n", "\n", @@ -432,7 +429,7 @@ "ax2.tick_params(axis=\"y\", labelcolor=color)\n", "\n", "fig.tight_layout() # Adjust layout to fit labels\n", - "plt.title(\"Basin Level and Storage Over Time\")\n", + "plt.title(\"Basin level and storage\")\n", "plt.show()" ] }, @@ -456,15 +453,13 @@ "source": [ "# Plot flow data\n", "# Read the flow results\n", - "df_flow = pd.read_feather(base_dir / \"Crystal_1.1/results/flow.arrow\")\n", - "# Create 'edge' and 'flow_m3d' columns\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", + "df_flow = pd.read_feather(base_dir / \"Crystal-1/results/flow.arrow\")\n", + "# Add the edge names and then remove unnamed edges\n", + "df_flow[\"name\"] = model.edge.df[\"name\"].loc[df_flow[\"edge_id\"]].to_numpy()\n", + "df_flow = df_flow[df_flow[\"name\"].astype(bool)]\n", "\n", "# Create a pivot table\n", - "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_rate\")\n", - "\n", - "# Skip the first timestep\n", - "pivot_flow = pivot_flow.iloc[1:]\n", + "pivot_flow = df_flow.pivot_table(index=\"time\", columns=\"name\", values=\"flow_rate\")\n", "\n", "line_styles = [\"-\", \"--\", \"-\", \"-.\"]\n", "num_styles = len(line_styles)\n", @@ -479,7 +474,7 @@ "ax.set_xlabel(\"Time\")\n", "ax.set_ylabel(\"Flow [m³/s]\")\n", "ax.legend(bbox_to_anchor=(1.15, 1), title=\"Edge\")\n", - "plt.title(\"Flow Over Time\")\n", + "plt.title(\"Flow\")\n", "plt.grid(True)\n", "plt.show()" ] diff --git a/docs/tutorial/public-water-supply.ipynb b/docs/tutorial/public-water-supply.ipynb deleted file mode 100644 index 796737d48..000000000 --- a/docs/tutorial/public-water-supply.ipynb +++ /dev/null @@ -1,217 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "title: \"Public water supply\"\n", - "---" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "\n", - "from ribasim import Model, Node\n", - "from ribasim.nodes import (\n", - " user_demand,\n", - ")\n", - "from shapely.geometry import Point" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "base_dir = Path(\"crystal-basin\")\n", - "\n", - "starttime = \"2022-01-01\"\n", - "endtime = \"2023-01-01\"\n", - "model = Model(\n", - " starttime=starttime,\n", - " endtime=endtime,\n", - " crs=\"EPSG:4326\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Public Water Supply\n", - "\n", - "### Rename the saving files\n", - "Rename the files to `Crystal_2.2`\n", - "\n", - "### Add a demand node\n", - "$50.000$ people live in Crystal City.\n", - "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%$." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.user_demand.add(\n", - " Node(9, Point(0.0, -0.25), name=\"PWS\"),\n", - " [\n", - " user_demand.Time(\n", - " # Total demand in m³/s\n", - " demand=[\n", - " 0.07,\n", - " 0.08,\n", - " 0.09,\n", - " 0.10,\n", - " 0.12,\n", - " 0.14,\n", - " 0.15,\n", - " 0.14,\n", - " 0.12,\n", - " 0.10,\n", - " 0.09,\n", - " 0.08,\n", - " ],\n", - " return_factor=0.6,\n", - " min_level=0,\n", - " priority=1,\n", - " time=[\n", - " starttime,\n", - " \"2022-02-01\",\n", - " \"2022-03-01\",\n", - " \"2022-04-01\",\n", - " \"2022-05-01\",\n", - " \"2022-06-01\",\n", - " \"2022-07-01\",\n", - " \"2022-08-01\",\n", - " \"2022-09-01\",\n", - " \"2022-10-01\",\n", - " \"2022-11-01\",\n", - " \"2022-12-01\",\n", - " ],\n", - " )\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add the edges\n", - "The connection between the reservoir and the demand node needs to be defined:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.edge.add(model.flow_boundary[1], model.basin[3])\n", - "model.edge.add(model.flow_boundary[2], model.basin[4])\n", - "model.edge.add(model.basin[3], model.user_demand[6])\n", - "model.edge.add(model.basin[3], model.user_demand[9])\n", - "model.edge.add(model.user_demand[6], model.basin[4])\n", - "model.edge.add(model.user_demand[9], model.basin[4])\n", - "model.edge.add(model.basin[3], model.tabulated_rating_curve[7])\n", - "model.edge.add(model.tabulated_rating_curve[7], model.basin[4])\n", - "model.edge.add(model.basin[4], model.tabulated_rating_curve[5])\n", - "model.edge.add(model.tabulated_rating_curve[5], model.terminal[8])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjust the name dictionaries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Dictionary mapping node_ids to names\n", - "edge_names = {\n", - " (1, 3): \"Main\",\n", - " (2, 4): \"Minor\",\n", - " (3, 6): \"IrrA Demand\",\n", - " (6, 4): \"IrrA Drain\",\n", - " (3, 9): \"PWS Demand\",\n", - " (9, 4): \"PWS Return\",\n", - " (3, 7): \"Rsv2Main\",\n", - " (7, 4): \"Main2Conf\",\n", - " (4, 5): \"Conf2TRC\",\n", - " (5, 8): \"TRC2Term\",\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# | include: false\n", - "from subprocess import run\n", - "\n", - "run(\n", - " [\n", - " \"julia\",\n", - " \"--project=../../core\",\n", - " \"--eval\",\n", - " f'using Ribasim; Ribasim.main(\"{toml_path.as_posix()}\")',\n", - " ],\n", - " check=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Check the simulated demands\n", - "@fig-sim8 shows the flow to (PWS Demand) and out (PWS Return) of the PWS node.\n", - "@fig-sim9 shows the downstream flow to the ocean.\n", - "The impact is clear.\n", - "Due to the demands upstream (irrigation and public water supply) an expected decrease of discharge is shown downstream.\n", - "\n", - "![Simulated flows to and from the city](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-flows-to-and-from-the-city.png){fig-align=\"left\" #fig-sim8}\n", - "\n", - "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels-reservoir.png){fig-align=\"left\" #fig-sim9}" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb index 7b4ceb648..58fb6f63d 100644 --- a/docs/tutorial/reservoir.ipynb +++ b/docs/tutorial/reservoir.ipynb @@ -21,7 +21,12 @@ "import pandas as pd\n", "import plotly.express as px\n", "from ribasim import Model, Node\n", - "from ribasim.nodes import basin\n", + "from ribasim.nodes import (\n", + " basin,\n", + " flow_boundary,\n", + " tabulated_rating_curve,\n", + " user_demand,\n", + ")\n", "from shapely.geometry import Point" ] }, @@ -46,18 +51,111 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Reservoirs and Public Water Supply\n", + "These nodes are identical to the previous tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# FlowBoundary\n", + "data = pd.DataFrame({\n", + " \"time\": pd.date_range(start=\"2022-01-01\", end=\"2023-01-01\", freq=\"MS\"),\n", + " \"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],\n", + " \"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]\n", + "}) # fmt: skip\n", + "data[\"total\"] = data[\"minor\"] + data[\"main\"]\n", + "main = model.flow_boundary.add(\n", + " Node(1, Point(0.0, 0.0), name=\"main\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.main,\n", + " )\n", + " ],\n", + ")\n", + "minor = model.flow_boundary.add(\n", + " Node(2, Point(-3.0, 0.0), name=\"minor\"),\n", + " [\n", + " flow_boundary.Time(\n", + " time=data.time,\n", + " flow_rate=data.minor,\n", + " )\n", + " ],\n", + ")\n", + "\n", + "# Basin\n", + "confluence = model.basin.add(\n", + " Node(3, Point(-1.5, -1), name=\"confluence\"),\n", + " [\n", + " basin.Profile(area=[672000, 5600000], level=[0, 6]),\n", + " basin.State(level=[4]),\n", + " basin.Time(time=[starttime, endtime]),\n", + " ],\n", + ")\n", + "\n", + "# TabulatedRatingCurve\n", + "weir = model.tabulated_rating_curve.add(\n", + " Node(4, Point(-1.5, -1.5), name=\"weir\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 2, 5],\n", + " flow_rate=[0.0, 50, 200],\n", + " )\n", + " ],\n", + ")\n", + "diversion_weir = model.tabulated_rating_curve.add(\n", + " Node(8, Point(-1.125, -0.75), name=\"diversion_weir\"),\n", + " [\n", + " tabulated_rating_curve.Static(\n", + " level=[0.0, 1.5, 5],\n", + " flow_rate=[0.0, 45, 200],\n", + " )\n", + " ],\n", + ")\n", + "\n", + "# UserDemand\n", + "irrigation = model.user_demand.add(\n", + " Node(7, Point(-1.5, 0.5), name=\"irrigation\"),\n", + " [\n", + " user_demand.Time(\n", + " demand=[0.0, 0.0, 10, 12, 12, 0.0],\n", + " return_factor=0,\n", + " min_level=0,\n", + " priority=1,\n", + " time=[\n", + " starttime,\n", + " \"2022-03-31\",\n", + " \"2022-04-01\",\n", + " \"2022-07-01\",\n", + " \"2022-09-30\",\n", + " \"2022-10-01\",\n", + " ],\n", + " )\n", + " ],\n", + ")\n", + "\n", + "# Terminal\n", + "sea = model.terminal.add(Node(5, Point(-1.5, -3.0), name=\"sea\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Due to the increase of population and climate change Crystal city has implemented a reservoir upstream to store water for domestic use (See @fig-reservoir).\n", "The reservoir is to help ensure a reliable supply during dry periods.\n", - "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal Basin.\n", + "In this module, the user will update the model to incorporate the reservoir's impact on the whole Crystal basin.\n", "\n", "![Crystal basin with demands and a reservoir](https://s3.deltares.nl/ribasim/doc-image/quickstart/Crystal-basin-with-demands-and-a-reservoir.png){fig-align=\"left\" #fig-reservoir}\n", "\n", "## Reservoir\n", "### Add a Basin\n", - "This time 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).\n", - "The reservoir has a max. area of $32.3 \\text{ km}^2$ and a max. depth of $7 \\text{ m}$.\n", - "The profile of Basin #3 should change to:" + "The `diversion_basin` from the previous tutorial is not used, but replaced by a larger `reservoir` Basin.\n", + "Its water will play an important role for the users (the city and the irrigation district).\n", + "The reservoir has a maximum area of $32.3 \\text{km}^2$ and a maximum depth of $7 \\text{m}$." ] }, { @@ -66,8 +164,8 @@ "metadata": {}, "outputs": [], "source": [ - "model.basin.add(\n", - " Node(3, Point(-0.75, -0.5), name=\"Rsv\"),\n", + "reservoir = model.basin.add(\n", + " Node(6, Point(-0.75, -0.5), name=\"reservoir\"),\n", " [\n", " basin.Profile(area=[20000000, 32300000], level=[0, 7]),\n", " basin.State(level=[3.5]),\n", @@ -76,12 +174,80 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a demand node\n", + "$50.000$ people live in Crystal City.\n", + "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%$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "city = model.user_demand.add(\n", + " Node(9, Point(0, -1), name=\"city\"),\n", + " [\n", + " user_demand.Time(\n", + " # Total demand in m³/s\n", + " demand=[0.07, 0.08, 0.09, 0.10, 0.12, 0.14, 0.15, 0.14, 0.12, 0.10, 0.09, 0.08],\n", + " return_factor=0.6,\n", + " min_level=0,\n", + " priority=1,\n", + " time=pd.date_range(start=\"2022-01-01\", periods=12, freq=\"MS\"),\n", + " )\n", + " ],\n", + ") # fmt: skip" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.edge.add(main, reservoir, name=\"main\")\n", + "model.edge.add(minor, confluence, name=\"minor\")\n", + "model.edge.add(reservoir, irrigation, name=\"irrigation\")\n", + "model.edge.add(irrigation, confluence)\n", + "model.edge.add(reservoir, city, name=\"city\")\n", + "model.edge.add(city, confluence, name=\"city returnflow\")\n", + "model.edge.add(reservoir, diversion_weir, name=\"not diverted\")\n", + "model.edge.add(diversion_weir, confluence)\n", + "model.edge.add(confluence, weir)\n", + "model.edge.add(weir, sea, name=\"sea\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.plot();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "toml_path = base_dir / \"Crystal-3/ribasim.toml\"\n", + "model.write(toml_path)\n", + "cli_path = \"ribasim\"" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adjust the code\n", - "Adjust the naming of the Basin in the dictionary mapping and the saving file should be `Crystal_2.1` instead of `*_1.2`." + "Adjust the naming of the Basin in the dictionary mapping and the saving file should be `Crystal-3`." ] }, { @@ -105,14 +271,10 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "toml_path = base_dir / \"Crystal_2.1/ribasim.toml\"\n", - "model.write(toml_path)\n", - "cli_path = \"ribasim\"" + "## Plot reservoir storage and level" ] }, { @@ -121,83 +283,44 @@ "metadata": {}, "outputs": [], "source": [ - "# Dictionary mapping node_ids to names\n", - "edge_names = {\n", - " (1, 3): \"Main\",\n", - " (2, 4): \"Minor\",\n", - " (3, 6): \"IrrA Demand\",\n", - " (6, 4): \"IrrA Drain\",\n", - " (3, 7): \"Rsv2Main\",\n", - " (7, 4): \"Main2Conf\",\n", - " (4, 5): \"Conf2TRC\",\n", - " (5, 8): \"TRC2Term\",\n", - "}\n", - "\n", - "# Dictionary mapping basins (node_ids) to names\n", - "node_names = {\n", - " 3: \"Rsv\",\n", - " 4: \"Conf\",\n", - "}\n", + "df_basin = pd.read_feather(base_dir / \"Crystal-3/results/basin.arrow\")\n", "\n", - "df_basin = pd.read_feather(base_dir / \"Crystal_2.1/results/basin.arrow\")\n", - "\n", - "# Create pivot tables and plot for basin data\n", + "# Create pivot tables and plot for Basin data\n", "df_basin_wide = df_basin.pivot_table(\n", " index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n", ")\n", + "df_basin_wide = df_basin_wide.loc[:, pd.IndexSlice[:, reservoir.node_id]]\n", "\n", - "# Skip the first timestep as it is the initialization step\n", - "df_basin_wide = df_basin_wide.iloc[1:]\n", - "\n", - "\n", - "# Create pivot tables and plot for basin data\n", - "df_basin_rsv = df_basin_wide.xs(\"Rsv\", axis=1, level=1, drop_level=False)\n", - "df_basin_conf = df_basin_wide.xs(\"Conf\", axis=1, level=1, drop_level=False)\n", - "\n", - "\n", - "def plot_basin_data(\n", - " ax, ax_twin, df_basin, level_color=\"b\", storage_color=\"r\", title=\"Basin\"\n", - "):\n", - " # Plot level data\n", - " for idx, column in enumerate(df_basin[\"level\"].columns):\n", - " ax.plot(\n", - " df_basin.index,\n", - " df_basin[\"level\"][column],\n", - " linestyle=\"-\",\n", - " color=level_color,\n", - " label=f\"Level - {column}\",\n", - " )\n", - "\n", - " # Plot storage data\n", - " for idx, column in enumerate(df_basin[\"storage\"].columns):\n", - " ax_twin.plot(\n", - " df_basin.index,\n", - " df_basin[\"storage\"][column],\n", - " linestyle=\"--\",\n", - " color=storage_color,\n", - " label=f\"Storage - {column}\",\n", - " )\n", - "\n", - " ax.set_ylabel(\"Level [m]\", color=level_color)\n", - " ax_twin.set_ylabel(\"Storage [m³]\", color=storage_color)\n", - "\n", - " ax.tick_params(axis=\"y\", labelcolor=level_color)\n", - " ax_twin.tick_params(axis=\"y\", labelcolor=storage_color)\n", - "\n", - " ax.set_title(title)\n", + "# Plot level and storage on the same graph with dual y-axes\n", + "fig, ax1 = plt.subplots(figsize=(12, 6))\n", "\n", - " # Combine legends from both axes\n", - " lines, labels = ax.get_legend_handles_labels()\n", - " lines_twin, labels_twin = ax_twin.get_legend_handles_labels()\n", - " ax.legend(lines + lines_twin, labels + labels_twin, loc=\"upper left\")\n", + "# Plot level on the primary y-axis\n", + "color = \"b\"\n", + "ax1.set_xlabel(\"Time\")\n", + "ax1.set_ylabel(\"Level [m]\", color=color)\n", + "ax1.plot(df_basin_wide.index, df_basin_wide[\"level\"], color=color)\n", + "ax1.tick_params(axis=\"y\", labelcolor=color)\n", "\n", + "# Create a secondary y-axis for storage\n", + "ax2 = ax1.twinx()\n", + "color = \"r\"\n", + "ax2.set_ylabel(\"Storage [m³]\", color=\"r\")\n", + "ax2.plot(df_basin_wide.index, df_basin_wide[\"storage\"], linestyle=\"--\", color=color)\n", + "ax2.tick_params(axis=\"y\", labelcolor=color)\n", "\n", - "# Create subplots\n", - "fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(12, 12), sharex=True)\n", + "fig.tight_layout() # Adjust layout to fit labels\n", + "plt.title(\"Basin level and storage\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The figure above illustrates the storage and water level at the reservoir.\n", + "As expected, after increasing the profile of the Basin, its storage capacity increased as well.\n", "\n", - "# Plot Rsv basin data\n", - "ax2 = ax1.twinx() # Secondary y-axis for storage\n", - "plot_basin_data(ax1, ax2, df_basin_rsv, title=\"Reservoir Level and Storage Over Time\")" + "## Plot flows" ] }, { @@ -206,34 +329,20 @@ "metadata": {}, "outputs": [], "source": [ - "# Sample data loading and preparation\n", - "df_flow = pd.read_feather(base_dir / \"Crystal_2.1/results/flow.arrow\")\n", - "df_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\n", - "df_flow[\"name\"] = df_flow[\"edge\"].map(edge_names)\n", + "df_flow = pd.read_feather(base_dir / \"Crystal-3/results/flow.arrow\")\n", + "# Add the edge names and then remove unnamed edges\n", + "df_flow[\"name\"] = model.edge.df[\"name\"].loc[df_flow[\"edge_id\"]].to_numpy()\n", + "df_flow = df_flow[df_flow[\"name\"].astype(bool)]\n", "\n", "# Plot the flow data, interactive plot with Plotly\n", "pivot_flow = df_flow.pivot_table(\n", " index=\"time\", columns=\"name\", values=\"flow_rate\"\n", ").reset_index()\n", - "fig = px.line(\n", - " pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow Over Time [m3/s]\"\n", - ")\n", + "fig = px.line(pivot_flow, x=\"time\", y=pivot_flow.columns[1:], title=\"Flow [m3/s]\")\n", "\n", "fig.update_layout(legend_title_text=\"Edge\")\n", - "fig.write_html(base_dir / \"Crystal_2.1/plot_edges.html\")\n", "fig.show()" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Plotting results\n", - "@fig-sim7 illustrates the new storage and water level at the reservoir.\n", - "As expected, after increasing the profile of basin 3 to mimic the reservoir, its storage capacity increased as well.\n", - "\n", - "![Simulated basin storages and levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Simulated-basin-storages-and-levels.png){fig-align=\"left\" #fig-sim7}" - ] } ], "metadata": { From 445185f83768a532475529377d859bfee199fe40 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Thu, 19 Sep 2024 15:44:53 +0200 Subject: [PATCH 16/19] Clean up unused pixi docs task --- pixi.toml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pixi.toml b/pixi.toml index de17158a5..347ec664a 100644 --- a/pixi.toml +++ b/pixi.toml @@ -38,14 +38,6 @@ initialize-julia = { depends_on = [ "instantiate-julia", ] } # Docs -build-julia-docs = { cmd = "julia --project docs/make.jl", depends_on = [ - "initialize-julia", -], inputs = [ - "core", - "docs/make.jl", -], outputs = [ - "docs/build", -] } quartodoc-build = { cmd = "quartodoc build && rm objects.json", cwd = "docs", inputs = [ "docs/_quarto.yml", "python/ribasim", @@ -61,7 +53,7 @@ quarto-render = { cmd = "julia --project --eval 'using Pkg; Pkg.build(\"IJulia\" "quartodoc-build", "generate-testmodels", ] } -docs = { depends_on = ["build-julia-docs", "quarto-preview"] } +docs = { depends_on = ["quarto-preview"] } # Lint mypy-ribasim-python = "mypy python/ribasim/ribasim" mypy-ribasim-testmodels = "mypy python/ribasim_testmodels/ribasim_testmodels" From 394df61734849a43aecaa8f4093bc1f5b6a448a3 Mon Sep 17 00:00:00 2001 From: Bart de Koning Date: Thu, 19 Sep 2024 17:15:12 +0200 Subject: [PATCH 17/19] unit formatting nit --- docs/guide/delwaq.ipynb | 2 +- docs/reference/node/basin.qmd | 28 +++++++++---------- docs/reference/node/flow-boundary.qmd | 6 ++-- docs/reference/node/flow-demand.qmd | 4 +-- docs/reference/node/level-boundary.qmd | 6 ++-- docs/reference/node/level-demand.qmd | 8 +++--- docs/reference/node/linear-resistance.qmd | 4 +-- docs/reference/node/manning-resistance.qmd | 6 ++-- docs/reference/node/outlet.qmd | 10 +++---- docs/reference/node/pid-control.qmd | 4 +-- docs/reference/node/pump.qmd | 10 +++---- .../reference/node/tabulated-rating-curve.qmd | 12 ++++---- docs/reference/node/user-demand.qmd | 8 +++--- docs/reference/usage.qmd | 24 ++++++++-------- docs/tutorial/natural-flow.ipynb | 4 +-- docs/tutorial/reservoir.ipynb | 2 +- 16 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docs/guide/delwaq.ipynb b/docs/guide/delwaq.ipynb index 1082aba42..e7c17cc71 100644 --- a/docs/guide/delwaq.ipynb +++ b/docs/guide/delwaq.ipynb @@ -200,7 +200,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.12.5" } }, "nbformat": 4, diff --git a/docs/reference/node/basin.qmd b/docs/reference/node/basin.qmd index 3aee60f78..f65bb1fea 100644 --- a/docs/reference/node/basin.qmd +++ b/docs/reference/node/basin.qmd @@ -18,10 +18,10 @@ time table, it is empty, or all timestamps of that variable are missing. column | type | unit | restriction --------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted -precipitation | Float64 | $\text{m}/\text{s}$ | non-negative -potential_evaporation | Float64 | $\text{m}/\text{s}$ | non-negative -drainage | Float64 | $\text{m}^3/\text{s}$ | non-negative -infiltration | Float64 | $\text{m}^3/\text{s}$ | non-negative +precipitation | Float64 | $\text m}/\text{s}$ | non-negative +potential_evaporation | Float64 | $\text m}/\text{s}$ | non-negative +drainage | Float64 | $\text m}^3/\text{s}$ | non-negative +infiltration | Float64 | $\text m}^3/\text{s}$ | non-negative Note that if variables are not set in the static table, default values are used when possible. These are generally zero, e.g. no precipitation, no inflow. If it is not possible @@ -93,7 +93,7 @@ The state table gives the initial water levels of all Basins. column | type | unit | restriction --------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted -level | Float64 | $\text{m}$ | $\ge$ basin bottom +level | Float64 | $\text m}$ | $\ge$ basin bottom Each Basin ID needs to be in the table. To use the final state of an earlier simulation as an initial condition, copy [`results/basin_state.arrow`](/reference/usage.qmd#sec-state) over to the `input_dir`, and point the TOML to it: @@ -114,8 +114,8 @@ The profile table defines the physical dimensions of the storage reservoir of ea column | type | unit | restriction --------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted -area | Float64 | $\text{m}^2$ | non-negative, per node_id: start positive and not decreasing -level | Float64 | $\text{m}$ | per node_id: increasing +area | Float64 | $\text m}^2$ | non-negative, per node_id: start positive and not decreasing +level | Float64 | $\text m}$ | per node_id: increasing The level is the level at the basin outlet. All levels are defined in meters above a datum that is the same for the entire model. An example of the first 4 rows of such a table is @@ -369,8 +369,8 @@ column | type | unit | restriction ------------- | ------- | ------------ | ------------------------ subgrid_id | Int32 | - | sorted node_id | Int32 | - | constant per subgrid_id -basin_level | Float64 | $\text{m}$ | sorted per subgrid_id -subgrid_level | Float64 | $\text{m}$ | sorted per subgrid_id +basin_level | Float64 | $\text m}$ | sorted per subgrid_id +subgrid_level | Float64 | $\text m}$ | sorted per subgrid_id The table below shows example input for two subgrid elements: @@ -399,8 +399,8 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | | can correspond to known Delwaq substances -drainage | Float64 | $\text{g}/\text{m}^3$ | (optional) -precipitation | Float64 | $\text{g}/\text{m}^3$ | (optional) +drainage | Float64 | $\text{g}/\text m}^3$ | (optional) +precipitation | Float64 | $\text{g}/\text m}^3$ | (optional) ## ConcentrationState {#sec-basin-conc-state} This table defines the concentration(s) of (a) substance(s) in the basin at the start of the simulation. @@ -410,7 +410,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text{m}^3$ | +concentration | Float64 | $\text{g}/\text m}^3$ | ## ConcentrationExternal This table is used for (external) concentrations, that can be used for Control lookups. @@ -420,7 +420,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text{m}^3$ | +concentration | Float64 | $\text{g}/\text m}^3$ | # Equations @@ -520,7 +520,7 @@ $$ Q_\text{inf} = \sum_{i=1}^{n} \sum_{j=1}^{m} \max(Q_{\mathrm{mf6}_{i,j}}, 0.0) $$ {#eq-inf} -Where $i$ is the index of the boundary condition, $j$ the MODFLOW 6 cell index, $n$ the number of boundary conditions, and $\text{m}$ the number of MODFLOW 6 cells in the Basin. +Where $i$ is the index of the boundary condition, $j$ the MODFLOW 6 cell index, $n$ the number of boundary conditions, and $\text m}$ the number of MODFLOW 6 cells in the Basin. $Q_{\mathrm{mf6}_{i,j}}$ is the flow computed by MODFLOW 6 for cell $j$ for boundary condition $i$. Drainage is a lump sum for the Basin, and consists of the sum of the absolute value of all **negative** flows of the MODFLOW 6 boundary conditions in the Basin. diff --git a/docs/reference/node/flow-boundary.qmd b/docs/reference/node/flow-boundary.qmd index 3600ead68..e62f81b8c 100644 --- a/docs/reference/node/flow-boundary.qmd +++ b/docs/reference/node/flow-boundary.qmd @@ -19,7 +19,7 @@ column | type | unit | restriction ------------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted active | Bool | - | (optional, default true) -flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative +flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative ## Time @@ -35,7 +35,7 @@ column | type | unit | restriction --------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id -flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative +flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative ## Concentration {#sec-flow-boundary-conc} This table defines the concentration(s) of (a) substance(s) for the flow from the FlowBoundary. @@ -45,7 +45,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text{m}^3$ | +concentration | Float64 | $\text{g}/\text m}^3$ | # Equations diff --git a/docs/reference/node/flow-demand.qmd b/docs/reference/node/flow-demand.qmd index dbd139fcf..e36d8d48b 100644 --- a/docs/reference/node/flow-demand.qmd +++ b/docs/reference/node/flow-demand.qmd @@ -13,7 +13,7 @@ column | type | unit | restriction ------------- | -------- | --------------------- | ----------- node_id | Int32 | - | sorted priority | Int32 | - | positive -demand | Float64 | $\text{m}^3/\text{s}$ | non-negative +demand | Float64 | $\text m}^3/\text{s}$ | non-negative ## Time @@ -25,4 +25,4 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id priority | Int32 | - | positive -demand | Float64 | $\text{m}^3/\text{s}$ | non-negative +demand | Float64 | $\text m}^3/\text{s}$ | non-negative diff --git a/docs/reference/node/level-boundary.qmd b/docs/reference/node/level-boundary.qmd index 3cb322f56..ef3a81bba 100644 --- a/docs/reference/node/level-boundary.qmd +++ b/docs/reference/node/level-boundary.qmd @@ -15,7 +15,7 @@ column | type | unit | restriction ------------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted active | Bool | - | (optional, default true) -level | Float64 | $\text{m}$ | - +level | Float64 | $\text m}$ | - ## Time @@ -31,7 +31,7 @@ column | type | unit | restriction --------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id -level | Float64 | $\text{m}$ | - +level | Float64 | $\text m}$ | - ## Concentration {#sec-level-boundary-conc} This table defines the concentration(s) of (a) substance(s) for the flow from the LevelBoundary. @@ -41,7 +41,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text{m}^3$ | +concentration | Float64 | $\text{g}/\text m}^3$ | # Equations diff --git a/docs/reference/node/level-demand.qmd b/docs/reference/node/level-demand.qmd index 23aaff5ef..94368661c 100644 --- a/docs/reference/node/level-demand.qmd +++ b/docs/reference/node/level-demand.qmd @@ -20,8 +20,8 @@ If both are missing, `LevelDemand` won't have any effects on allocation. column | type | unit | restriction ------------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted -min_level | Float64 | $\text{m}$ | (optional, default -Inf) -max_level | Float64 | $\text{m}$ | (optional, default Inf) +min_level | Float64 | $\text m}$ | (optional, default -Inf) +max_level | Float64 | $\text m}$ | (optional, default Inf) priority | Int32 | - | positive ## Time @@ -33,6 +33,6 @@ column | type | unit | restriction ------------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node id -min_level | Float64 | $\text{m}$ | - -max_level | Float64 | $\text{m}$ | - +min_level | Float64 | $\text m}$ | - +max_level | Float64 | $\text m}$ | - priority | Int32 | - | positive diff --git a/docs/reference/node/linear-resistance.qmd b/docs/reference/node/linear-resistance.qmd index 1103a04d4..f071968e3 100644 --- a/docs/reference/node/linear-resistance.qmd +++ b/docs/reference/node/linear-resistance.qmd @@ -13,8 +13,8 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -resistance | Float64 | $\text{s}/\text{m}^2$ | - -max_flow_rate | Float64 | $\text{m}^3/s$ | non-negative +resistance | Float64 | $\text{s}/\text m}^2$ | - +max_flow_rate | Float64 | $\text m}^3/s$ | non-negative # Equations diff --git a/docs/reference/node/manning-resistance.qmd b/docs/reference/node/manning-resistance.qmd index 63e66d979..a8cb8cf5d 100644 --- a/docs/reference/node/manning-resistance.qmd +++ b/docs/reference/node/manning-resistance.qmd @@ -14,9 +14,9 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -length | Float64 | $\text{m}$ | positive -manning_n | Float64 | $\text{s} \text{m}^{-\frac{1}{3}}$ | positive -profile_width | Float64 | $\text{m}$ | positive +length | Float64 | $\text m}$ | positive +manning_n | Float64 | $\text{s} \text m}^{-\frac{1}{3}}$ | positive +profile_width | Float64 | $\text m}$ | positive profile_slope | Float64 | - | - # Equations diff --git a/docs/reference/node/outlet.qmd b/docs/reference/node/outlet.qmd index 673f7b279..fbb20e456 100644 --- a/docs/reference/node/outlet.qmd +++ b/docs/reference/node/outlet.qmd @@ -16,11 +16,11 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -flow_rate | Float64 | $\text{m}^3/\text{s}\$ | non-negative -min_flow_rate | Float64 | $\text{m}^3/\text{s}\$ | (optional, default 0.0) -max_flow_rate | Float64 | $\text{m}^3/\text{s}\$ | (optional) -min_upstream_level | Float64 | $\text{m}$ | (optional) -max_downstream_level | Float64 | $\text{m}$ | (optional) +flow_rate | Float64 | $\text m}^3/\text{s}\$ | non-negative +min_flow_rate | Float64 | $\text m}^3/\text{s}\$ | (optional, default 0.0) +max_flow_rate | Float64 | $\text m}^3/\text{s}\$ | (optional) +min_upstream_level | Float64 | $\text m}$ | (optional) +max_downstream_level | Float64 | $\text m}$ | (optional) # Equations diff --git a/docs/reference/node/pid-control.qmd b/docs/reference/node/pid-control.qmd index cae2d0a9b..5a85f245e 100644 --- a/docs/reference/node/pid-control.qmd +++ b/docs/reference/node/pid-control.qmd @@ -20,7 +20,7 @@ control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) listen_node_type | String | - | known node type listen_node_id | Int32 | - | - -target | Float64 | $\text{m}$ | - +target | Float64 | $\text m}$ | - proportional | Float64 | $\text{s}^{-1}$ | - integral | Float64 | $\text{s}^{-2}$ | - derivative | Float64 | - | - @@ -41,7 +41,7 @@ node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id listen_node_type | Int32 | - | known node type listen_node_id | Int32 | - | - -target | Float64 | $\text{m}$ | - +target | Float64 | $\text m}$ | - proportional | Float64 | $\text{s}^{-1}$ | - integral | Float64 | $\text{s}^{-2}$ | - derivative | Float64 | - | - diff --git a/docs/reference/node/pump.qmd b/docs/reference/node/pump.qmd index 67712ad36..681e604a7 100644 --- a/docs/reference/node/pump.qmd +++ b/docs/reference/node/pump.qmd @@ -17,11 +17,11 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative -min_flow_rate | Float64 | $\text{m}^3/\text{s}$ | (optional, default 0.0) -max_flow_rate | Float64 | $\text{m}^3/\text{s}$ | (optional) -min_upstream_level | Float64 | $\text{m}$ | (optional) -max_downstream_level | Float64 | $\text{m}$ | (optional) +flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative +min_flow_rate | Float64 | $\text m}^3/\text{s}$ | (optional, default 0.0) +max_flow_rate | Float64 | $\text m}^3/\text{s}$ | (optional) +min_upstream_level | Float64 | $\text m}$ | (optional) +max_downstream_level | Float64 | $\text m}$ | (optional) # Equations diff --git a/docs/reference/node/tabulated-rating-curve.qmd b/docs/reference/node/tabulated-rating-curve.qmd index 7e45c6b4f..aded71414 100644 --- a/docs/reference/node/tabulated-rating-curve.qmd +++ b/docs/reference/node/tabulated-rating-curve.qmd @@ -15,9 +15,9 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -max_downstream_level | Float64 | $\text{m}$ | (optional) -level | Float64 | $\text{m}$ | sorted per control_state, unique -flow_rate | Float64 | $\text{m}^3/\text{s}$ | start at 0, increasing +max_downstream_level | Float64 | $\text m}$ | (optional) +level | Float64 | $\text m}$ | sorted per control_state, unique +flow_rate | Float64 | $\text m}^3/\text{s}$ | start at 0, increasing Thus a single rating curve can be given by the following table: @@ -89,9 +89,9 @@ column | type | unit | restriction -------------------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id -level | Float64 | $\text{m}$ | sorted per node_id per time -flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative -max_downstream_level | Float64 | $\text{m}$ | (optional) +level | Float64 | $\text m}$ | sorted per node_id per time +flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative +max_downstream_level | Float64 | $\text m}$ | (optional) # Equations diff --git a/docs/reference/node/user-demand.qmd b/docs/reference/node/user-demand.qmd index f53f5fbfc..1482b2cd7 100644 --- a/docs/reference/node/user-demand.qmd +++ b/docs/reference/node/user-demand.qmd @@ -26,9 +26,9 @@ column | type | unit | restriction ------------- | ------- | ----------------------| ----------- node_id | Int32 | - | sorted active | Bool | - | (optional, default true) -demand | Float64 | $\text{m}^3/\text{s}$ | non-negative +demand | Float64 | $\text m}^3/\text{s}$ | non-negative return_factor | Float64 | - | between [0 - 1] -min_level | Float64 | $\text{m}$ | - +min_level | Float64 | $\text m}$ | - priority | Int32 | - | positive, sorted per node id ## Time @@ -47,9 +47,9 @@ column | type | unit | restriction node_id | Int32 | - | sorted priority | Int32 | - | positive, sorted per node id time | DateTime | - | sorted per priority per node id -demand | Float64 | $\text{m}^3/\text{s}$ | non-negative +demand | Float64 | $\text m}^3/\text{s}$ | non-negative return_factor | Float64 | - | between [0 - 1] -min_level | Float64 | $\text{m}$ | - +min_level | Float64 | $\text m}$ | - # Equations diff --git a/docs/reference/usage.qmd b/docs/reference/usage.qmd index 180d07271..b7b92ea1b 100644 --- a/docs/reference/usage.qmd +++ b/docs/reference/usage.qmd @@ -205,16 +205,16 @@ column | type | unit -------------- | ---------| ---- time | DateTime | - node_id | Int32 | - -storage | Float64 | $\text{m}^3$ -level | Float64 | $\text{m}$ -inflow_rate | Float64 | $\text{m}^3/\text{s}$ -outflow_rate | Float64 | $\text{m}^3/\text{s}$ -storage_rate | Float64 | $\text{m}^3/\text{s}$ -precipitation | Float64 | $\text{m}^3/\text{s}$ -evaporation | Float64 | $\text{m}^3/\text{s}$ -drainage | Float64 | $\text{m}^3/\text{s}$ -infiltration | Float64 | $\text{m}^3/\text{s}$ -balance_error | Float64 | $\text{m}^3/\text{s}$ +storage | Float64 | $\text m}^3$ +level | Float64 | $\text m}$ +inflow_rate | Float64 | $\text m}^3/\text{s}$ +outflow_rate | Float64 | $\text m}^3/\text{s}$ +storage_rate | Float64 | $\text m}^3/\text{s}$ +precipitation | Float64 | $\text m}^3/\text{s}$ +evaporation | Float64 | $\text m}^3/\text{s}$ +drainage | Float64 | $\text m}^3/\text{s}$ +infiltration | Float64 | $\text m}^3/\text{s}$ +balance_error | Float64 | $\text m}^3/\text{s}$ relative_error | Float64 | - The table is sorted by time, and per time it is sorted by `node_id`. @@ -232,7 +232,7 @@ from_node_type | String | - from_node_id | Int32 | - to_node_type | String | - to_node_id | Int32 | - -flow_rate | Float64 | $\text{m}^3/\text{s}$ +flow_rate | Float64 | $\text m}^3/\text{s}$ The table is sorted by time, and per time the same `edge_id` order is used, though not sorted. The `edge_id` value is the same as the `fid` written to the Edge table, and can be used to directly look up the Edge geometry. @@ -245,7 +245,7 @@ The Basin state table contains the water levels in each Basin at the end of the column | type | unit --------- | ------- | ------------ node_id | Int32 | - -level | Float64 | $\text{m}$ +level | Float64 | $\text m}$ To use this result as the initial condition of another simulation, see the [Basin / state](/reference/node/basin.qmd#sec-state) table reference. diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb index db990bb14..62dc034e2 100644 --- a/docs/tutorial/natural-flow.ipynb +++ b/docs/tutorial/natural-flow.ipynb @@ -48,7 +48,7 @@ "source": [ "# Crystal River Basin\n", "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 @fig-crystal-basin).\n", - "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", + "An average discharge of $44.45 \\text{ m}^3/\\text{s}$ is measured at the confluence.\n", "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", "\n", @@ -65,7 +65,7 @@ "\n", "### Import packages\n", "Before building the model we need to import some modules.\n", - "Open your favorite Python editor (Visual Studio Code, Jupyter, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", + "Open your favorite Python editor (Visual Studio Code, Jupyter, Spyder, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", "Import the following modules in Python:" ] }, diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb index 58fb6f63d..e06c58a66 100644 --- a/docs/tutorial/reservoir.ipynb +++ b/docs/tutorial/reservoir.ipynb @@ -155,7 +155,7 @@ "### Add a Basin\n", "The `diversion_basin` from the previous tutorial is not used, but replaced by a larger `reservoir` Basin.\n", "Its water will play an important role for the users (the city and the irrigation district).\n", - "The reservoir has a maximum area of $32.3 \\text{km}^2$ and a maximum depth of $7 \\text{m}$." + "The reservoir has a maximum area of $32.3 \\text{km}^2$ and a maximum depth of $7 \\text m}$." ] }, { From 11e64cfe0b43cc1faeb6165abb402bb49595441e Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Thu, 19 Sep 2024 22:30:37 +0200 Subject: [PATCH 18/19] Revert "unit formatting nit" This reverts commit 394df61734849a43aecaa8f4093bc1f5b6a448a3. --- docs/guide/delwaq.ipynb | 2 +- docs/reference/node/basin.qmd | 28 +++++++++---------- docs/reference/node/flow-boundary.qmd | 6 ++-- docs/reference/node/flow-demand.qmd | 4 +-- docs/reference/node/level-boundary.qmd | 6 ++-- docs/reference/node/level-demand.qmd | 8 +++--- docs/reference/node/linear-resistance.qmd | 4 +-- docs/reference/node/manning-resistance.qmd | 6 ++-- docs/reference/node/outlet.qmd | 10 +++---- docs/reference/node/pid-control.qmd | 4 +-- docs/reference/node/pump.qmd | 10 +++---- .../reference/node/tabulated-rating-curve.qmd | 12 ++++---- docs/reference/node/user-demand.qmd | 8 +++--- docs/reference/usage.qmd | 24 ++++++++-------- docs/tutorial/natural-flow.ipynb | 4 +-- docs/tutorial/reservoir.ipynb | 2 +- 16 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docs/guide/delwaq.ipynb b/docs/guide/delwaq.ipynb index e7c17cc71..1082aba42 100644 --- a/docs/guide/delwaq.ipynb +++ b/docs/guide/delwaq.ipynb @@ -200,7 +200,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.5" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/docs/reference/node/basin.qmd b/docs/reference/node/basin.qmd index f65bb1fea..3aee60f78 100644 --- a/docs/reference/node/basin.qmd +++ b/docs/reference/node/basin.qmd @@ -18,10 +18,10 @@ time table, it is empty, or all timestamps of that variable are missing. column | type | unit | restriction --------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted -precipitation | Float64 | $\text m}/\text{s}$ | non-negative -potential_evaporation | Float64 | $\text m}/\text{s}$ | non-negative -drainage | Float64 | $\text m}^3/\text{s}$ | non-negative -infiltration | Float64 | $\text m}^3/\text{s}$ | non-negative +precipitation | Float64 | $\text{m}/\text{s}$ | non-negative +potential_evaporation | Float64 | $\text{m}/\text{s}$ | non-negative +drainage | Float64 | $\text{m}^3/\text{s}$ | non-negative +infiltration | Float64 | $\text{m}^3/\text{s}$ | non-negative Note that if variables are not set in the static table, default values are used when possible. These are generally zero, e.g. no precipitation, no inflow. If it is not possible @@ -93,7 +93,7 @@ The state table gives the initial water levels of all Basins. column | type | unit | restriction --------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted -level | Float64 | $\text m}$ | $\ge$ basin bottom +level | Float64 | $\text{m}$ | $\ge$ basin bottom Each Basin ID needs to be in the table. To use the final state of an earlier simulation as an initial condition, copy [`results/basin_state.arrow`](/reference/usage.qmd#sec-state) over to the `input_dir`, and point the TOML to it: @@ -114,8 +114,8 @@ The profile table defines the physical dimensions of the storage reservoir of ea column | type | unit | restriction --------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted -area | Float64 | $\text m}^2$ | non-negative, per node_id: start positive and not decreasing -level | Float64 | $\text m}$ | per node_id: increasing +area | Float64 | $\text{m}^2$ | non-negative, per node_id: start positive and not decreasing +level | Float64 | $\text{m}$ | per node_id: increasing The level is the level at the basin outlet. All levels are defined in meters above a datum that is the same for the entire model. An example of the first 4 rows of such a table is @@ -369,8 +369,8 @@ column | type | unit | restriction ------------- | ------- | ------------ | ------------------------ subgrid_id | Int32 | - | sorted node_id | Int32 | - | constant per subgrid_id -basin_level | Float64 | $\text m}$ | sorted per subgrid_id -subgrid_level | Float64 | $\text m}$ | sorted per subgrid_id +basin_level | Float64 | $\text{m}$ | sorted per subgrid_id +subgrid_level | Float64 | $\text{m}$ | sorted per subgrid_id The table below shows example input for two subgrid elements: @@ -399,8 +399,8 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | | can correspond to known Delwaq substances -drainage | Float64 | $\text{g}/\text m}^3$ | (optional) -precipitation | Float64 | $\text{g}/\text m}^3$ | (optional) +drainage | Float64 | $\text{g}/\text{m}^3$ | (optional) +precipitation | Float64 | $\text{g}/\text{m}^3$ | (optional) ## ConcentrationState {#sec-basin-conc-state} This table defines the concentration(s) of (a) substance(s) in the basin at the start of the simulation. @@ -410,7 +410,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text m}^3$ | +concentration | Float64 | $\text{g}/\text{m}^3$ | ## ConcentrationExternal This table is used for (external) concentrations, that can be used for Control lookups. @@ -420,7 +420,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text m}^3$ | +concentration | Float64 | $\text{g}/\text{m}^3$ | # Equations @@ -520,7 +520,7 @@ $$ Q_\text{inf} = \sum_{i=1}^{n} \sum_{j=1}^{m} \max(Q_{\mathrm{mf6}_{i,j}}, 0.0) $$ {#eq-inf} -Where $i$ is the index of the boundary condition, $j$ the MODFLOW 6 cell index, $n$ the number of boundary conditions, and $\text m}$ the number of MODFLOW 6 cells in the Basin. +Where $i$ is the index of the boundary condition, $j$ the MODFLOW 6 cell index, $n$ the number of boundary conditions, and $\text{m}$ the number of MODFLOW 6 cells in the Basin. $Q_{\mathrm{mf6}_{i,j}}$ is the flow computed by MODFLOW 6 for cell $j$ for boundary condition $i$. Drainage is a lump sum for the Basin, and consists of the sum of the absolute value of all **negative** flows of the MODFLOW 6 boundary conditions in the Basin. diff --git a/docs/reference/node/flow-boundary.qmd b/docs/reference/node/flow-boundary.qmd index e62f81b8c..3600ead68 100644 --- a/docs/reference/node/flow-boundary.qmd +++ b/docs/reference/node/flow-boundary.qmd @@ -19,7 +19,7 @@ column | type | unit | restriction ------------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted active | Bool | - | (optional, default true) -flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative +flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative ## Time @@ -35,7 +35,7 @@ column | type | unit | restriction --------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id -flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative +flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative ## Concentration {#sec-flow-boundary-conc} This table defines the concentration(s) of (a) substance(s) for the flow from the FlowBoundary. @@ -45,7 +45,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text m}^3$ | +concentration | Float64 | $\text{g}/\text{m}^3$ | # Equations diff --git a/docs/reference/node/flow-demand.qmd b/docs/reference/node/flow-demand.qmd index e36d8d48b..dbd139fcf 100644 --- a/docs/reference/node/flow-demand.qmd +++ b/docs/reference/node/flow-demand.qmd @@ -13,7 +13,7 @@ column | type | unit | restriction ------------- | -------- | --------------------- | ----------- node_id | Int32 | - | sorted priority | Int32 | - | positive -demand | Float64 | $\text m}^3/\text{s}$ | non-negative +demand | Float64 | $\text{m}^3/\text{s}$ | non-negative ## Time @@ -25,4 +25,4 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id priority | Int32 | - | positive -demand | Float64 | $\text m}^3/\text{s}$ | non-negative +demand | Float64 | $\text{m}^3/\text{s}$ | non-negative diff --git a/docs/reference/node/level-boundary.qmd b/docs/reference/node/level-boundary.qmd index ef3a81bba..3cb322f56 100644 --- a/docs/reference/node/level-boundary.qmd +++ b/docs/reference/node/level-boundary.qmd @@ -15,7 +15,7 @@ column | type | unit | restriction ------------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted active | Bool | - | (optional, default true) -level | Float64 | $\text m}$ | - +level | Float64 | $\text{m}$ | - ## Time @@ -31,7 +31,7 @@ column | type | unit | restriction --------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id -level | Float64 | $\text m}$ | - +level | Float64 | $\text{m}$ | - ## Concentration {#sec-level-boundary-conc} This table defines the concentration(s) of (a) substance(s) for the flow from the LevelBoundary. @@ -41,7 +41,7 @@ column | type | unit | restriction node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id substance | String | - | can correspond to known Delwaq substances -concentration | Float64 | $\text{g}/\text m}^3$ | +concentration | Float64 | $\text{g}/\text{m}^3$ | # Equations diff --git a/docs/reference/node/level-demand.qmd b/docs/reference/node/level-demand.qmd index 94368661c..23aaff5ef 100644 --- a/docs/reference/node/level-demand.qmd +++ b/docs/reference/node/level-demand.qmd @@ -20,8 +20,8 @@ If both are missing, `LevelDemand` won't have any effects on allocation. column | type | unit | restriction ------------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted -min_level | Float64 | $\text m}$ | (optional, default -Inf) -max_level | Float64 | $\text m}$ | (optional, default Inf) +min_level | Float64 | $\text{m}$ | (optional, default -Inf) +max_level | Float64 | $\text{m}$ | (optional, default Inf) priority | Int32 | - | positive ## Time @@ -33,6 +33,6 @@ column | type | unit | restriction ------------- | ------- | ------------ | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node id -min_level | Float64 | $\text m}$ | - -max_level | Float64 | $\text m}$ | - +min_level | Float64 | $\text{m}$ | - +max_level | Float64 | $\text{m}$ | - priority | Int32 | - | positive diff --git a/docs/reference/node/linear-resistance.qmd b/docs/reference/node/linear-resistance.qmd index f071968e3..1103a04d4 100644 --- a/docs/reference/node/linear-resistance.qmd +++ b/docs/reference/node/linear-resistance.qmd @@ -13,8 +13,8 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -resistance | Float64 | $\text{s}/\text m}^2$ | - -max_flow_rate | Float64 | $\text m}^3/s$ | non-negative +resistance | Float64 | $\text{s}/\text{m}^2$ | - +max_flow_rate | Float64 | $\text{m}^3/s$ | non-negative # Equations diff --git a/docs/reference/node/manning-resistance.qmd b/docs/reference/node/manning-resistance.qmd index a8cb8cf5d..63e66d979 100644 --- a/docs/reference/node/manning-resistance.qmd +++ b/docs/reference/node/manning-resistance.qmd @@ -14,9 +14,9 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -length | Float64 | $\text m}$ | positive -manning_n | Float64 | $\text{s} \text m}^{-\frac{1}{3}}$ | positive -profile_width | Float64 | $\text m}$ | positive +length | Float64 | $\text{m}$ | positive +manning_n | Float64 | $\text{s} \text{m}^{-\frac{1}{3}}$ | positive +profile_width | Float64 | $\text{m}$ | positive profile_slope | Float64 | - | - # Equations diff --git a/docs/reference/node/outlet.qmd b/docs/reference/node/outlet.qmd index fbb20e456..673f7b279 100644 --- a/docs/reference/node/outlet.qmd +++ b/docs/reference/node/outlet.qmd @@ -16,11 +16,11 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -flow_rate | Float64 | $\text m}^3/\text{s}\$ | non-negative -min_flow_rate | Float64 | $\text m}^3/\text{s}\$ | (optional, default 0.0) -max_flow_rate | Float64 | $\text m}^3/\text{s}\$ | (optional) -min_upstream_level | Float64 | $\text m}$ | (optional) -max_downstream_level | Float64 | $\text m}$ | (optional) +flow_rate | Float64 | $\text{m}^3/\text{s}\$ | non-negative +min_flow_rate | Float64 | $\text{m}^3/\text{s}\$ | (optional, default 0.0) +max_flow_rate | Float64 | $\text{m}^3/\text{s}\$ | (optional) +min_upstream_level | Float64 | $\text{m}$ | (optional) +max_downstream_level | Float64 | $\text{m}$ | (optional) # Equations diff --git a/docs/reference/node/pid-control.qmd b/docs/reference/node/pid-control.qmd index 5a85f245e..cae2d0a9b 100644 --- a/docs/reference/node/pid-control.qmd +++ b/docs/reference/node/pid-control.qmd @@ -20,7 +20,7 @@ control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) listen_node_type | String | - | known node type listen_node_id | Int32 | - | - -target | Float64 | $\text m}$ | - +target | Float64 | $\text{m}$ | - proportional | Float64 | $\text{s}^{-1}$ | - integral | Float64 | $\text{s}^{-2}$ | - derivative | Float64 | - | - @@ -41,7 +41,7 @@ node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id listen_node_type | Int32 | - | known node type listen_node_id | Int32 | - | - -target | Float64 | $\text m}$ | - +target | Float64 | $\text{m}$ | - proportional | Float64 | $\text{s}^{-1}$ | - integral | Float64 | $\text{s}^{-2}$ | - derivative | Float64 | - | - diff --git a/docs/reference/node/pump.qmd b/docs/reference/node/pump.qmd index 681e604a7..67712ad36 100644 --- a/docs/reference/node/pump.qmd +++ b/docs/reference/node/pump.qmd @@ -17,11 +17,11 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative -min_flow_rate | Float64 | $\text m}^3/\text{s}$ | (optional, default 0.0) -max_flow_rate | Float64 | $\text m}^3/\text{s}$ | (optional) -min_upstream_level | Float64 | $\text m}$ | (optional) -max_downstream_level | Float64 | $\text m}$ | (optional) +flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative +min_flow_rate | Float64 | $\text{m}^3/\text{s}$ | (optional, default 0.0) +max_flow_rate | Float64 | $\text{m}^3/\text{s}$ | (optional) +min_upstream_level | Float64 | $\text{m}$ | (optional) +max_downstream_level | Float64 | $\text{m}$ | (optional) # Equations diff --git a/docs/reference/node/tabulated-rating-curve.qmd b/docs/reference/node/tabulated-rating-curve.qmd index aded71414..7e45c6b4f 100644 --- a/docs/reference/node/tabulated-rating-curve.qmd +++ b/docs/reference/node/tabulated-rating-curve.qmd @@ -15,9 +15,9 @@ column | type | unit | restriction node_id | Int32 | - | sorted control_state | String | - | (optional) sorted per node_id active | Bool | - | (optional, default true) -max_downstream_level | Float64 | $\text m}$ | (optional) -level | Float64 | $\text m}$ | sorted per control_state, unique -flow_rate | Float64 | $\text m}^3/\text{s}$ | start at 0, increasing +max_downstream_level | Float64 | $\text{m}$ | (optional) +level | Float64 | $\text{m}$ | sorted per control_state, unique +flow_rate | Float64 | $\text{m}^3/\text{s}$ | start at 0, increasing Thus a single rating curve can be given by the following table: @@ -89,9 +89,9 @@ column | type | unit | restriction -------------------- | ------- | --------------------- | ----------- node_id | Int32 | - | sorted time | DateTime | - | sorted per node_id -level | Float64 | $\text m}$ | sorted per node_id per time -flow_rate | Float64 | $\text m}^3/\text{s}$ | non-negative -max_downstream_level | Float64 | $\text m}$ | (optional) +level | Float64 | $\text{m}$ | sorted per node_id per time +flow_rate | Float64 | $\text{m}^3/\text{s}$ | non-negative +max_downstream_level | Float64 | $\text{m}$ | (optional) # Equations diff --git a/docs/reference/node/user-demand.qmd b/docs/reference/node/user-demand.qmd index 1482b2cd7..f53f5fbfc 100644 --- a/docs/reference/node/user-demand.qmd +++ b/docs/reference/node/user-demand.qmd @@ -26,9 +26,9 @@ column | type | unit | restriction ------------- | ------- | ----------------------| ----------- node_id | Int32 | - | sorted active | Bool | - | (optional, default true) -demand | Float64 | $\text m}^3/\text{s}$ | non-negative +demand | Float64 | $\text{m}^3/\text{s}$ | non-negative return_factor | Float64 | - | between [0 - 1] -min_level | Float64 | $\text m}$ | - +min_level | Float64 | $\text{m}$ | - priority | Int32 | - | positive, sorted per node id ## Time @@ -47,9 +47,9 @@ column | type | unit | restriction node_id | Int32 | - | sorted priority | Int32 | - | positive, sorted per node id time | DateTime | - | sorted per priority per node id -demand | Float64 | $\text m}^3/\text{s}$ | non-negative +demand | Float64 | $\text{m}^3/\text{s}$ | non-negative return_factor | Float64 | - | between [0 - 1] -min_level | Float64 | $\text m}$ | - +min_level | Float64 | $\text{m}$ | - # Equations diff --git a/docs/reference/usage.qmd b/docs/reference/usage.qmd index b7b92ea1b..180d07271 100644 --- a/docs/reference/usage.qmd +++ b/docs/reference/usage.qmd @@ -205,16 +205,16 @@ column | type | unit -------------- | ---------| ---- time | DateTime | - node_id | Int32 | - -storage | Float64 | $\text m}^3$ -level | Float64 | $\text m}$ -inflow_rate | Float64 | $\text m}^3/\text{s}$ -outflow_rate | Float64 | $\text m}^3/\text{s}$ -storage_rate | Float64 | $\text m}^3/\text{s}$ -precipitation | Float64 | $\text m}^3/\text{s}$ -evaporation | Float64 | $\text m}^3/\text{s}$ -drainage | Float64 | $\text m}^3/\text{s}$ -infiltration | Float64 | $\text m}^3/\text{s}$ -balance_error | Float64 | $\text m}^3/\text{s}$ +storage | Float64 | $\text{m}^3$ +level | Float64 | $\text{m}$ +inflow_rate | Float64 | $\text{m}^3/\text{s}$ +outflow_rate | Float64 | $\text{m}^3/\text{s}$ +storage_rate | Float64 | $\text{m}^3/\text{s}$ +precipitation | Float64 | $\text{m}^3/\text{s}$ +evaporation | Float64 | $\text{m}^3/\text{s}$ +drainage | Float64 | $\text{m}^3/\text{s}$ +infiltration | Float64 | $\text{m}^3/\text{s}$ +balance_error | Float64 | $\text{m}^3/\text{s}$ relative_error | Float64 | - The table is sorted by time, and per time it is sorted by `node_id`. @@ -232,7 +232,7 @@ from_node_type | String | - from_node_id | Int32 | - to_node_type | String | - to_node_id | Int32 | - -flow_rate | Float64 | $\text m}^3/\text{s}$ +flow_rate | Float64 | $\text{m}^3/\text{s}$ The table is sorted by time, and per time the same `edge_id` order is used, though not sorted. The `edge_id` value is the same as the `fid` written to the Edge table, and can be used to directly look up the Edge geometry. @@ -245,7 +245,7 @@ The Basin state table contains the water levels in each Basin at the end of the column | type | unit --------- | ------- | ------------ node_id | Int32 | - -level | Float64 | $\text m}$ +level | Float64 | $\text{m}$ To use this result as the initial condition of another simulation, see the [Basin / state](/reference/node/basin.qmd#sec-state) table reference. diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb index 62dc034e2..db990bb14 100644 --- a/docs/tutorial/natural-flow.ipynb +++ b/docs/tutorial/natural-flow.ipynb @@ -48,7 +48,7 @@ "source": [ "# Crystal River Basin\n", "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 @fig-crystal-basin).\n", - "An average discharge of $44.45 \\text{ m}^3/\\text{s}$ is measured at the confluence.\n", + "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", "\n", @@ -65,7 +65,7 @@ "\n", "### Import packages\n", "Before building the model we need to import some modules.\n", - "Open your favorite Python editor (Visual Studio Code, Jupyter, Spyder, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", + "Open your favorite Python editor (Visual Studio Code, Jupyter, ...) and create a new script or notebook and name it `Crystal_1.1` and save it into your model folder `Crystal_Basin`.\n", "Import the following modules in Python:" ] }, diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb index e06c58a66..58fb6f63d 100644 --- a/docs/tutorial/reservoir.ipynb +++ b/docs/tutorial/reservoir.ipynb @@ -155,7 +155,7 @@ "### Add a Basin\n", "The `diversion_basin` from the previous tutorial is not used, but replaced by a larger `reservoir` Basin.\n", "Its water will play an important role for the users (the city and the irrigation district).\n", - "The reservoir has a maximum area of $32.3 \\text{km}^2$ and a maximum depth of $7 \\text m}$." + "The reservoir has a maximum area of $32.3 \\text{km}^2$ and a maximum depth of $7 \\text{m}$." ] }, { From 3c8633462a30bd5ad9bdbac64ef21902c21ebf6b Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Thu, 19 Sep 2024 22:42:09 +0200 Subject: [PATCH 19/19] Add spaces before units following numbers --- docs/tutorial/natural-flow.ipynb | 10 +++++----- docs/tutorial/reservoir.ipynb | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/natural-flow.ipynb b/docs/tutorial/natural-flow.ipynb index db990bb14..a4e817a0a 100644 --- a/docs/tutorial/natural-flow.ipynb +++ b/docs/tutorial/natural-flow.ipynb @@ -48,7 +48,7 @@ "source": [ "# Crystal River Basin\n", "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 @fig-crystal-basin).\n", - "An average discharge of $44.45 \\text{m}^3/\\text{s}$ is measured at the confluence.\n", + "An average discharge of $44.45 \\text{ m}^3/\\text{s}$ is measured at the confluence.\n", "In this module, the basin is free of any activities, allowing the model to simulate the natural flow.\n", "The next step is to include a demand (irrigation) that taps from a canal out of the main river.\n", "\n", @@ -217,10 +217,10 @@ "It defines a relation between the water level ($h$) in the Basin and the outflow ($Q$) from the Basin.\n", "This setup mimics the behavior of a gate or spillway, allowing us to model how varying water levels influence flow rates at the confluence.\n", "\n", - "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{m}^3/\\text{s}$.\n", + "As the two inflows come together at the confluence, we expect, as mentioned above, a discharge average of $44.45 \\text{ m}^3/\\text{s}$.\n", "It is therefore expected that the confluence Basin goes towards a level where the outflow is equal to the inflow via the rating curve.\n", "Only then is the confluence Basin in equilibrium.\n", - "The maximum depth of the river is $6 \\text{m}$, and the maximum inflow is $221.5 \\text{m}^3/\\text{s}$\n", + "The maximum depth of the river is $6 \\text{ m}$, and the maximum inflow is $221.5 \\text{ m}^3/\\text{s}$\n", "The $Q(h)$ relationship in @tbl-input2 allows such inflows with reasonable water levels.\n", "\n", ": Input data for the Tabulated Rating Curve {#tbl-input2}\n", @@ -236,8 +236,8 @@ "In this case this means:\n", "\n", "- At level $0.0$: No discharge occurs. This represents a condition where the water level is too low for any flow to be discharged.\n", - "- At level $2.0$: Discharge is $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.\n", - "- 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.\n", + "- At level $2.0$: Discharge is $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.\n", + "- 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.\n", "\n", "![Discharge at corresponding water levels](https://s3.deltares.nl/ribasim/doc-image/quickstart/Discharge-at-corresponding-water-levels.png){fig-align=\"left\" #fig-discharge}\n", "\n", diff --git a/docs/tutorial/reservoir.ipynb b/docs/tutorial/reservoir.ipynb index 58fb6f63d..bcd262745 100644 --- a/docs/tutorial/reservoir.ipynb +++ b/docs/tutorial/reservoir.ipynb @@ -155,7 +155,7 @@ "### Add a Basin\n", "The `diversion_basin` from the previous tutorial is not used, but replaced by a larger `reservoir` Basin.\n", "Its water will play an important role for the users (the city and the irrigation district).\n", - "The reservoir has a maximum area of $32.3 \\text{km}^2$ and a maximum depth of $7 \\text{m}$." + "The reservoir has a maximum area of $32.3 \\text{ km}^2$ and a maximum depth of $7 \\text{ m}$." ] }, {