diff --git a/search.json b/search.json
index c6014e045..bd4007088 100644
--- a/search.json
+++ b/search.json
@@ -1,203 +1,287 @@
[
{
- "objectID": "src/index.html",
- "href": "src/index.html",
- "title": "1 API Reference",
+ "objectID": "qgis/index.html",
+ "href": "qgis/index.html",
+ "title": "QGIS plugin",
"section": "",
- "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
+ "text": "Install QGIS version 3.28 or higher.\n\n\nDownload ribasim_qgis.zip, see the download section.\nPlugins menu > Manage and Install Plugins…\n\n\n\n\n\nSelect “Install from ZIP”:\n\nBrowse to the ribasim_qgis.zip file containing the plugin that was downloaded earlier\nClick “Install Plugin”\n\n\n\n\n\n\nStart the Ribasim plugin.\n\n\n\n\n\n\n\n\nIn QGIS, navigate to “Plugins > Manage and Install Plugins > All”. In the search bar, type: “iMOD”. Select the iMOD plugin, and click “Install”.\nAt least version 0.4.0 of the iMOD plugin is required.\nThe Time Series widget from the iMOD plugin is used for visualizing Ribasim results, which is described in Section 1.5. Documentation on the Time Series widget can be found in the iMOD documentation.\n\n\n\nOpen an existing model or create a new model. As an example of an existing model, you can use the “basic” model from generated_testmodels.zip, see the download section.\n\n\n\n\n\nCheck if your coordinate reference system (CRS) is set correctly.\n\n\n\n\n\nIf you are working with an unknown CRS, right click the model database group in Layers, and click “Set Group CRS…”.\n\n\n\n\n\nIf you are modeling the Netherlands, select “Amersfoort / RD New” (EPSG:28992).\n\n\n\n\n\n\n\n\n\n\n\nSelect the Node layer.\n\n\n\n\n\nTurn on the edit mode to be able to add nodes on the map.\n\n\n\n\n\nAdd nodes to the map with a left click and select the node type.\n\n\n\n\n\nTurn the edit mode off and save the edits to the Nodes layer.\n\n\n\n\n\n\n\n\nRight click a layer and select “Open Attribute Table”.\n\n\n\n\n\nClick the yellow pencil icon on the top left to enable editing, and copy and paste a record. A record can be selected by clicking on the row number.\n\n\n\n\n\nAdjust the content. If you prefer, it also works to copy data with the same columns from Excel. Turn off edit mode and save changes to the layer.\n\n\n\n\n\n\n\n\n\n\n\nMake sure the Snapping Toolbar is visible, by going to the View > Toolbars menu. Turn on snapping mode by clicking the magnet and set the snapping distance to 25 pixels.\n\n\n\n\n\n\n\n\nSelect the Edge layer and turn on the edit mode.\n\n\n\n\n\nSelect “Add line feature”.\n\n\n\n\n\nCreate a connection by left clicking a source node and right clicking the destination node.\n\n\n\n\n\nNow leave the edit mode and save the results to the layer.\n\n\n\n\n\nUnzip the Ribasim command line interface, ribasim_cli.zip\nOpen your terminal and go to the directory where your TOML is stored. Now run path/to/ribasim_cli/ribasim ribasim.toml. Adjust the path to the ribasim_cli folder to where you placed it. This runs the model.\nIn your model directory there is now a results/ folder with basin.arrow and flow.arrow output files.\n\n\n\n\nBefore trying to inspect the results, verify that the run was successful and the output files are there.\nClick the “Time Series” button of the iMOD plugin.\n\n\n\n\n\nSelect the layer that you wish to plot. From the “Node” layer you can plot level or storage on Basin nodes. From the “Edge” layer you can plot flow over flow edges. Note that before switching between these, you typically have to click “Clear” to clear the selection. If you run a simulation with the model open in QGIS, you have to close and re-open the “iMOD Time Series Plot” panel for the new results to be loaded.\nSelect the variables that you want to plot.\n\n\n\n\n\nClick “Select points” and select a node by dragging a rectangle around it on the map. Hold the Ctrl key to select multiple nodes.\n\n\n\n\n\nThe associated time series are shown the the graph.\n\n\n\n\n\nOnly the “basin.arrow” and “flow.arrow” can be inspected with the “iMOD Time Series Plot” panel. All Arrow files can be loaded as a layer by dragging the files onto QGIS. Right click the layer and select “Open Attribute Table” to view the contents."
},
{
- "objectID": "src/index.html#modules",
- "href": "src/index.html#modules",
- "title": "1 API Reference",
+ "objectID": "qgis/index.html#start",
+ "href": "qgis/index.html#start",
+ "title": "QGIS plugin",
"section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]"
+ "text": "Install QGIS version 3.28 or higher.\n\n\nDownload ribasim_qgis.zip, see the download section.\nPlugins menu > Manage and Install Plugins…\n\n\n\n\n\nSelect “Install from ZIP”:\n\nBrowse to the ribasim_qgis.zip file containing the plugin that was downloaded earlier\nClick “Install Plugin”\n\n\n\n\n\n\nStart the Ribasim plugin.\n\n\n\n\n\n\n\n\nIn QGIS, navigate to “Plugins > Manage and Install Plugins > All”. In the search bar, type: “iMOD”. Select the iMOD plugin, and click “Install”.\nAt least version 0.4.0 of the iMOD plugin is required.\nThe Time Series widget from the iMOD plugin is used for visualizing Ribasim results, which is described in Section 1.5. Documentation on the Time Series widget can be found in the iMOD documentation.\n\n\n\nOpen an existing model or create a new model. As an example of an existing model, you can use the “basic” model from generated_testmodels.zip, see the download section.\n\n\n\n\n\nCheck if your coordinate reference system (CRS) is set correctly.\n\n\n\n\n\nIf you are working with an unknown CRS, right click the model database group in Layers, and click “Set Group CRS…”.\n\n\n\n\n\nIf you are modeling the Netherlands, select “Amersfoort / RD New” (EPSG:28992)."
},
{
- "objectID": "src/index.html#types",
- "href": "src/index.html#types",
- "title": "1 API Reference",
+ "objectID": "qgis/index.html#edit-nodes",
+ "href": "qgis/index.html#edit-nodes",
+ "title": "QGIS plugin",
"section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]"
+ "text": "Select the Node layer.\n\n\n\n\n\nTurn on the edit mode to be able to add nodes on the map.\n\n\n\n\n\nAdd nodes to the map with a left click and select the node type.\n\n\n\n\n\nTurn the edit mode off and save the edits to the Nodes layer.\n\n\n\n\n\n\n\n\nRight click a layer and select “Open Attribute Table”.\n\n\n\n\n\nClick the yellow pencil icon on the top left to enable editing, and copy and paste a record. A record can be selected by clicking on the row number.\n\n\n\n\n\nAdjust the content. If you prefer, it also works to copy data with the same columns from Excel. Turn off edit mode and save changes to the layer."
},
{
- "objectID": "src/index.html#functions",
- "href": "src/index.html#functions",
- "title": "1 API Reference",
+ "objectID": "qgis/index.html#connect-nodes",
+ "href": "qgis/index.html#connect-nodes",
+ "title": "QGIS plugin",
"section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]"
+ "text": "Make sure the Snapping Toolbar is visible, by going to the View > Toolbars menu. Turn on snapping mode by clicking the magnet and set the snapping distance to 25 pixels.\n\n\n\n\n\n\n\n\nSelect the Edge layer and turn on the edit mode.\n\n\n\n\n\nSelect “Add line feature”.\n\n\n\n\n\nCreate a connection by left clicking a source node and right clicking the destination node.\n\n\n\n\n\nNow leave the edit mode and save the results to the layer."
},
{
- "objectID": "src/index.html#constants",
- "href": "src/index.html#constants",
- "title": "1 API Reference",
+ "objectID": "qgis/index.html#run-a-model",
+ "href": "qgis/index.html#run-a-model",
+ "title": "QGIS plugin",
"section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]"
+ "text": "Unzip the Ribasim command line interface, ribasim_cli.zip\nOpen your terminal and go to the directory where your TOML is stored. Now run path/to/ribasim_cli/ribasim ribasim.toml. Adjust the path to the ribasim_cli folder to where you placed it. This runs the model.\nIn your model directory there is now a results/ folder with basin.arrow and flow.arrow output files."
},
{
- "objectID": "src/index.html#macros",
- "href": "src/index.html#macros",
- "title": "1 API Reference",
+ "objectID": "qgis/index.html#sec-results",
+ "href": "qgis/index.html#sec-results",
+ "title": "QGIS plugin",
"section": "",
- "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
+ "text": "Before trying to inspect the results, verify that the run was successful and the output files are there.\nClick the “Time Series” button of the iMOD plugin.\n\n\n\n\n\nSelect the layer that you wish to plot. From the “Node” layer you can plot level or storage on Basin nodes. From the “Edge” layer you can plot flow over flow edges. Note that before switching between these, you typically have to click “Clear” to clear the selection. If you run a simulation with the model open in QGIS, you have to close and re-open the “iMOD Time Series Plot” panel for the new results to be loaded.\nSelect the variables that you want to plot.\n\n\n\n\n\nClick “Select points” and select a node by dragging a rectangle around it on the map. Hold the Ctrl key to select multiple nodes.\n\n\n\n\n\nThe associated time series are shown the the graph.\n\n\n\n\n\nOnly the “basin.arrow” and “flow.arrow” can be inspected with the “iMOD Time Series Plot” panel. All Arrow files can be loaded as a layer by dragging the files onto QGIS. Right click the layer and select “Open Attribute Table” to view the contents."
},
{
- "objectID": "core/usage.html",
- "href": "core/usage.html",
- "title": "Usage",
+ "objectID": "couple/index.html",
+ "href": "couple/index.html",
+ "title": "Coupled models",
"section": "",
- "text": "Ribasim is typically used as a command-line interface (CLI). It is distributed as a .zip archive, that must be downloaded and unpacked. It can be placed anywhere, however it is important that the contents of the zip file are kept together in a directory. The Ribasim CLI executable is in the bin directory.\nTo download ribasim_cli.zip, see the download section.\nTo check whether the installation was performed successfully, run ribasim with no arguments in the command line. This will give the following message:"
- },
- {
- "objectID": "core/usage.html#sec-solver-settings",
- "href": "core/usage.html#sec-solver-settings",
- "title": "Usage",
- "section": "1.1 Solver settings",
- "text": "1.1 Solver settings\nThe solver section in the configuration file is entirely optional, since we aim to use defaults that will generally work well. Common reasons to modify the solver settings are to adjust the calculation or result stepsizes: adaptive, dt, and saveat. If your model does not converge, or your performance is lower than expected, it can help to adjust other solver settings as well.\nThe default solver algorithm = \"QNDF\", which is a multistep method similar to Matlab’s ode15s (Shampine and Reichelt 1997). It is an implicit method that supports the default adaptive = true timestepping. The full list of available solvers is: QNDF, Rosenbrock23, TRBDF2, Rodas5, KenCarp4, Tsit5, RK4, ImplicitEuler, Euler. Information on the solver algorithms can be found on the ODE solvers page.\nThe dt controls the stepsize. When adaptive = true, dt only applies to the initial stepsize, and by default it is automatically determined. When adaptive = false a suitable dt must always be provided. The value is in seconds, so dt = 3600.0 corresponds to an hourly timestep. When adaptive = true, dtmin and dtmax control the minimum and maximum allowed dt. By default these depend on the problem and algorithm. If a smaller dt than dtmin is needed to meet the set error tolerances, the simulation stops, unless force_dtmin is set to true. force_dtmin is off by default to ensure an accurate solution.\nBy default the calculation and result stepsize are the same, with saveat = [], which will save every timestep. saveat can be a number, which is the saving interval in seconds, or it can be a list of numbers, which are the times in seconds since start that are saved. For instance, saveat = 86400.0 will save results after every day that passed.\nThe Jacobian matrix provides information about the local sensitivity of the model with respect to changes in the states. For implicit solvers it must be calculated often, which can be expensive to do. There are several methods to do this. By default Ribasim uses a Jacobian derived automatically using ForwardDiff.jl with memory management provided by PreallocationTools.jl. If this is not used by setting autodiff = false, the Jacobian is calculated with a finite difference method, which can be less accurate and more expensive.\nBy default the Jacobian matrix is a sparse matrix (sparse = true). Since each state typically only depends on a small number of other states, this is generally more efficient, especially for larger models. The sparsity structure is calculated from the network and provided as a Jacobian prototype to the solver. For small or highly connected models it could be faster to use a dense Jacobian matrix instead by setting sparse = false.\nThe total maximum number of iterations maxiters = 1e9, can normally stay as-is unless doing extremely long simulations.\nThe absolute and relative tolerance for adaptive timestepping can be set with abstol and reltol. For more information on these and other solver options, see the DifferentialEquations.jl docs."
+ "text": "Ribasim can be coupled to other software, for instance to model another process or domain, or to control a simulation from another process.\nTo enable this, Ribasim can be compiled as a shared library (libribasim) instead of a command line interface (ribasim). This shared library exposes a C API in the form of the Basic Model Interface (BMI). Other software can then load libribasim and load a Ribasim model, exchange data, and control the time stepping.\nAn initial coupling to MODFLOW 6 was done in 2022 inside the Julia core as a proof of concept. Read about the coupling setup and see the demonstration. Going forward this and other coupling codes will be implemented outside of the core, by making use of iMOD Coupler and libribasim. iMOD Coupler is a generic coupling tool, and can be used to couple to other models as well."
},
{
- "objectID": "core/usage.html#allocation-settings",
- "href": "core/usage.html#allocation-settings",
- "title": "Usage",
- "section": "1.2 Allocation settings",
- "text": "1.2 Allocation settings\nCurrently there are the following allocation settings: - use_allocation: A boolean which says whether allocation should be used or not; - timestep: a float value in seconds which dictates the update interval for allocations."
+ "objectID": "python/examples.html",
+ "href": "python/examples.html",
+ "title": "Examples",
+ "section": "",
+ "text": "1 Basic model with static forcing\n\nfrom pathlib import Path\n\nimport geopandas as gpd\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport ribasim\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1, 3, 3, 6, 6, 9, 9],\n \"area\": [0.01, 1000.0] * 4,\n \"level\": [0.0, 1.0] * 4,\n }\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\nseconds_in_day = 24 * 3600\nprecipitation = 0.002 / seconds_in_day\nevaporation = 0.001 / seconds_in_day\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [0],\n \"drainage\": [0.0],\n \"potential_evaporation\": [evaporation],\n \"infiltration\": [0.0],\n \"precipitation\": [precipitation],\n \"urban_runoff\": [0.0],\n }\n)\nstatic = static.iloc[[0, 0, 0, 0]]\nstatic[\"node_id\"] = [1, 3, 6, 9]\n\nbasin = ribasim.Basin(profile=profile, static=static)\n\nSetup linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\"node_id\": [10, 12], \"resistance\": [5e3, (3600.0 * 24) / 100.0]}\n )\n)\n\nSetup Manning resistance:\n\nmanning_resistance = ribasim.ManningResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [2],\n \"length\": [900.0],\n \"manning_n\": [0.04],\n \"profile_width\": [6.0],\n \"profile_slope\": [3.0],\n }\n )\n)\n\nSet up a rating curve node:\n\n# Discharge: lose 1% of storage volume per day at storage = 1000.0.\nq1000 = 1000.0 * 0.01 / seconds_in_day\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": [4, 4],\n \"level\": [0.0, 1.0],\n \"discharge\": [0.0, q1000],\n }\n )\n)\n\nSetup fractional flows:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [5, 8, 13],\n \"fraction\": [0.3, 0.6, 0.1],\n }\n )\n)\n\nSetup pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [7],\n \"flow_rate\": [0.5 / 3600],\n }\n )\n)\n\nSetup level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [11, 17],\n \"level\": [0.5, 1.5],\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [15, 16],\n \"flow_rate\": [1e-4, 1e-4],\n }\n )\n)\n\nSetup terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [14],\n }\n )\n)\n\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin,\n (1.0, 0.0), # 2: ManningResistance\n (2.0, 0.0), # 3: Basin\n (3.0, 0.0), # 4: TabulatedRatingCurve\n (3.0, 1.0), # 5: FractionalFlow\n (3.0, 2.0), # 6: Basin\n (4.0, 1.0), # 7: Pump\n (4.0, 0.0), # 8: FractionalFlow\n (5.0, 0.0), # 9: Basin\n (6.0, 0.0), # 10: LinearResistance\n (2.0, 2.0), # 11: LevelBoundary\n (2.0, 1.0), # 12: LinearResistance\n (3.0, -1.0), # 13: FractionalFlow\n (3.0, -2.0), # 14: Terminal\n (3.0, 3.0), # 15: FlowBoundary\n (0.0, 1.0), # 16: FlowBoundary\n (6.0, 1.0), # 17: LevelBoundary\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_id, node_type = ribasim.Node.node_ids_and_types(\n basin,\n manning_resistance,\n rating_curve,\n pump,\n fractional_flow,\n linear_resistance,\n level_boundary,\n flow_boundary,\n terminal,\n)\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(node_id, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64\n)\nto_id = np.array(\n [2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64\n)\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": len(from_id) * [\"flow\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n level_boundary=level_boundary,\n flow_boundary=flow_boundary,\n pump=pump,\n linear_resistance=linear_resistance,\n manning_resistance=manning_resistance,\n tabulated_rating_curve=rating_curve,\n fractional_flow=fractional_flow,\n terminal=terminal,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"basic/ribasim.toml\")\n\nPosixPath('data/basic/ribasim.toml')\n\n\n\n\n2 Update the basic model with transient forcing\nThis assumes you have already created the basic model with static forcing.\n\nimport numpy as np\nimport pandas as pd\nimport ribasim\nimport xarray as xr\n\n\nmodel = ribasim.Model(filepath=datadir / \"basic/ribasim.toml\")\n\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\nWe’ll use xarray to easily broadcast the values.\n\ntimeseries = (\n pd.DataFrame(\n data={\n \"node_id\": 1,\n \"time\": pd.date_range(model.starttime, model.endtime),\n \"drainage\": 0.0,\n \"potential_evaporation\": evaporation,\n \"infiltration\": 0.0,\n \"precipitation\": precipitation,\n \"urban_runoff\": 0.0,\n }\n )\n .set_index(\"time\")\n .to_xarray()\n)\n\nbasin_ids = model.basin.static.df[\"node_id\"].to_numpy()\nbasin_nodes = xr.DataArray(\n np.ones(len(basin_ids)), coords={\"node_id\": basin_ids}, dims=[\"node_id\"]\n)\nforcing = (timeseries * basin_nodes).to_dataframe().reset_index()\n\n\nstate = pd.DataFrame(\n data={\n \"node_id\": basin_ids,\n \"level\": 1.4,\n }\n)\n\n\nmodel.basin.time.df = forcing\nmodel.basin.state.df = state\n\n\nmodel.write(datadir / \"basic_transient/ribasim.toml\")\n\nPosixPath('data/basic_transient/ribasim.toml')\n\n\nNow run the model with ribasim basic_transient/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"basic_transient/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n<Axes: xlabel='time'>\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic_transient/results/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n<matplotlib.legend.Legend at 0x7f606dcb8490>\n\n\n\n\n\n\ntype(df_flow)\n\npandas.core.frame.DataFrame\n\n\n\n\n3 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin\n (1.0, 1.0), # 2: Pump\n (1.0, -1.0), # 3: Pump\n (2.0, 0.0), # 4: LevelBoundary\n (-1.0, 0.0), # 5: TabulatedRatingCurve\n (-2.0, 0.0), # 6: Terminal\n (1.0, 0.0), # 7: DiscreteControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"Basin\",\n \"Pump\",\n \"Pump\",\n \"LevelBoundary\",\n \"TabulatedRatingCurve\",\n \"Terminal\",\n \"DiscreteControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)\nto_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)\n\nedge_type = 6 * [\"flow\"] + 2 * [\"control\"]\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\"from_node_id\": from_id, \"to_node_id\": to_id, \"edge_type\": edge_type},\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1],\n \"area\": [1000.0, 1000.0],\n \"level\": [0.0, 1.0],\n }\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [1],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [1], \"level\": [20.0]})\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": 3 * [7],\n \"listen_feature_id\": 3 * [1],\n \"variable\": 3 * [\"level\"],\n \"greater_than\": [5.0, 10.0, 15.0], # min, setpoint, max\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 5 * [7],\n \"truth_state\": [\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n \"control_state\": [\"in\", \"in\", \"none\", \"out\", \"out\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nThe above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": 3 * [2] + 3 * [3],\n \"control_state\": 2 * [\"none\", \"in\", \"out\"],\n \"flow_rate\": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],\n }\n )\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(data={\"node_id\": [4], \"level\": [10.0]})\n)\n\nSetup the rating curve:\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\"node_id\": 2 * [5], \"level\": [2.0, 15.0], \"discharge\": [0.0, 1e-3]}\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(static=pd.DataFrame(data={\"node_id\": [6]}))\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n pump=pump,\n level_boundary=level_boundary,\n tabulated_rating_curve=rating_curve,\n terminal=terminal,\n discrete_control=discrete_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax/ribasim.toml\")\n\nPosixPath('data/level_setpoint_with_minmax/ribasim.toml')\n\n\nNow run the model with level_setpoint_with_minmax/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.df.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\nThe highlighted regions show where a pump is active.\nLet’s print an overview of what happened with control:\n\nmodel.print_discrete_control_record(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\n0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level > 15.0\n\n This yielded control state \"out\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.002\n\n1. At 2020-02-08 19:02:21.861000 the control node with ID 7 reached truth state TFF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n2. At 2020-07-05 08:56:10.319000 the control node with ID 7 reached truth state FFF:\n For node ID 1 (Basin): level < 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"in\":\n For node ID 2 (Pump): flow_rate = 0.002\n For node ID 3 (Pump): flow_rate = 0.0\n\n3. At 2020-08-11 06:05:15.592000 the control node with ID 7 reached truth state TTF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n\n\nNote that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.\n\n\n4 Model with PID control\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.5), # 3: Pump\n (3.0, 0.0), # 4: LevelBoundary\n (1.5, 1.0), # 5: PidControl\n (2.0, -0.5), # 6: outlet\n (1.5, -1.0), # 7: PidControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"Pump\",\n \"LevelBoundary\",\n \"PidControl\",\n \"Outlet\",\n \"PidControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)\nto_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": 5 * [\"flow\"] + 2 * [\"control\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2], \"level\": [0.0, 1.0], \"area\": [1000.0, 1000.0]}\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"level\": [6.0],\n }\n)\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup the outlet:\n\noutlet = ribasim.Outlet(\n static=pd.DataFrame(\n data={\n \"node_id\": [6],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": [1e-3]})\n)\n\nSetup flow boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"level\": [1.0], # Not relevant\n }\n )\n)\n\nSetup PID control:\n\npid_control = ribasim.PidControl(\n time=pd.DataFrame(\n data={\n \"node_id\": 4 * [5, 7],\n \"time\": [\n \"2020-01-01 00:00:00\",\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n \"listen_node_id\": 4 * [2, 2],\n \"target\": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],\n \"proportional\": 4 * [-1e-3, 1e-3],\n \"integral\": 4 * [-1e-7, 1e-7],\n \"derivative\": 4 * [0.0, 0.0],\n }\n )\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n flow_boundary=flow_boundary,\n level_boundary=level_boundary,\n pump=pump,\n outlet=outlet,\n pid_control=pid_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control/ribasim.toml\")\n\nPosixPath('data/pid_control/ribasim.toml')\n\n\nNow run the model with ribasim pid_control/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\ntarget_levels = model.pid_control.time.df.target.to_numpy()[:4]\ntimes = date2num(model.pid_control.time.df.time)[:4]\nax.plot(times, target_levels, color=\"k\", ls=\":\", label=\"target level\")\npass"
},
{
- "objectID": "core/usage.html#basin-time",
- "href": "core/usage.html#basin-time",
- "title": "Usage",
- "section": "5.1 Basin / time",
- "text": "5.1 Basin / time\nThis table is the transient form of the Basin table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. A linear interpolation between the given timesteps is currently done if the solver takes timesteps between the given data points. More options will be available later."
+ "objectID": "python/reference/PidControl.html",
+ "href": "python/reference/PidControl.html",
+ "title": "1 PidControl",
+ "section": "",
+ "text": "1 PidControl\nPidControl()"
},
{
- "objectID": "core/usage.html#basin-state",
- "href": "core/usage.html#basin-state",
- "title": "Usage",
- "section": "5.2 Basin / state",
- "text": "5.2 Basin / state\nThe state table aims to capture the full state of the Basin, such that it can be used as an initial condition, potentially the outcome of an earlier simulation. Currently only the Basin node types have state.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlevel\nFloat64\n\\(m\\)\n\\(\\ge\\) basin bottom\n\n\n\nEach Basin ID needs to be in the table."
+ "objectID": "python/reference/Node.html",
+ "href": "python/reference/Node.html",
+ "title": "1 Node",
+ "section": "",
+ "text": "Node()\nThe Ribasim nodes as Point geometries.\n\n\n\n\n\nName\nDescription\n\n\n\n\nconnectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings\n\n\ngeometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\nNode.connectivity_from_geometry(lines)\nDerive from_node_id and to_node_id for every edge in lines. LineStrings may be used to connect multiple nodes in a sequence, but every linestring vertex must also a node.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int\n\n\n\n\n\n\n\n\nNode.geometry_from_connectivity(from_id, to_id)\nCreate edge shapely geometries from connectivities.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings.\n\n\n\n\n\n\n\nNode.plot(ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone"
},
{
- "objectID": "core/usage.html#basin-profile",
- "href": "core/usage.html#basin-profile",
- "title": "Usage",
- "section": "5.3 Basin / profile",
- "text": "5.3 Basin / profile\nThe profile table defines the physical dimensions of the storage reservoir of each basin.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\narea\nFloat64\n\\(m^2\\)\nnon-negative, per node_id: start positive and increasing\n\n\nlevel\nFloat64\n\\(m\\)\nper node_id: increasing\n\n\n\nThe 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 5 rows of such a table is given below. The first 4 rows define the profile of ID 2. The number of rows can vary per ID. Using a very large number of rows may impact performance.\n\n\n\nnode_id\narea\nlevel\n\n\n\n\n2\n1.0\n6.0\n\n\n2\n1000.0\n7.0\n\n\n2\n1000.0\n9.0\n\n\n3\n1.0\n2.2\n\n\n\nWe use the symbol \\(A\\) for area, \\(h\\) for level and \\(S\\) for storage. The profile provides a function \\(A(h)\\) for each basin. Internally this get converted to two functions, \\(A(S)\\) and \\(h(S)\\), by integrating over the function, setting the storage to zero for the bottom of the profile. The minimum area cannot be zero to avoid numerical issues. The maximum area is used to convert the precipitation flux into an inflow."
+ "objectID": "python/reference/Node.html#methods",
+ "href": "python/reference/Node.html#methods",
+ "title": "1 Node",
+ "section": "",
+ "text": "Name\nDescription\n\n\n\n\nconnectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings\n\n\ngeometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\nNode.connectivity_from_geometry(lines)\nDerive from_node_id and to_node_id for every edge in lines. LineStrings may be used to connect multiple nodes in a sequence, but every linestring vertex must also a node.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int\n\n\n\n\n\n\n\n\nNode.geometry_from_connectivity(from_id, to_id)\nCreate edge shapely geometries from connectivities.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings.\n\n\n\n\n\n\n\nNode.plot(ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone"
},
{
- "objectID": "core/usage.html#basin-subgrid",
- "href": "core/usage.html#basin-subgrid",
- "title": "Usage",
- "section": "5.4 Basin / subgrid",
- "text": "5.4 Basin / subgrid\nThe subgrid_level table defines a piecewise linear interpolation from a basin water level to a subgrid element water level. Many subgrid elements may be associated with a single basin, each with distinct interpolation functions. This functionality can be used to translate a single lumped basin level to a more spatially detailed representation (e.g comparable to the output of a hydrodynamic simulation).\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nsubgrid_id\nInt\n-\nsorted\n\n\nnode_id\nInt\n-\nconstant per subgrid_id\n\n\nbasin_level\nFloat64\n\\(m\\)\nsorted per subgrid_id\n\n\nsubgrid_level\nFloat64\n\\(m\\)\nsorted per subgrid_id\n\n\n\nThe table below shows example input for two subgrid elements:\n\n\n\nsubgrid_id\nnode_id\nbasin_level\nsubgrid_level\n\n\n\n\n1\n9\n0.0\n0.0\n\n\n1\n9\n1.0\n1.0\n\n\n1\n9\n2.0\n2.0\n\n\n2\n9\n0.0\n0.5\n\n\n2\n9\n1.0\n1.5\n\n\n2\n9\n2.0\n2.5\n\n\n\nBoth subgrid elements use the water level of the basin with node_id 9 to interpolate to their respective water levels. The first element has a one to one connection with the water level; the second also has a one to one connection, but is offset by half a meter. A basin water level of 0.3 would be translated to a water level of 0.3 for the first subgrid element, and 0.8 for the second. Water levels beyond the last basin_level are linearly extrapolated.\nNote that the interpolation to subgrid water level is not constrained by any water balance within Ribasim. Generally, to create physically meaningful subgrid water levels, the subgrid table must be parametrized properly such that the spatially integrated water volume of the subgrid elements agrees with the total storage volume of the basin."
+ "objectID": "python/reference/Edge.html",
+ "href": "python/reference/Edge.html",
+ "title": "1 Edge",
+ "section": "",
+ "text": "Edge()\nDefines the connections between nodes.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired"
},
{
- "objectID": "core/usage.html#basin-results",
- "href": "core/usage.html#basin-results",
- "title": "Usage",
- "section": "5.5 Basin results",
- "text": "5.5 Basin results\nThe basin table contains results of the storage and level of each basin at every solver timestep. The initial condition is also written to the file.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nnode_id\nInt\n-\n\n\nstorage\nFloat64\n\\(m^3\\)\n\n\nlevel\nFloat64\n\\(m\\)\n\n\n\nThe table is sorted by time, and per time it is sorted by node_id."
+ "objectID": "python/reference/Edge.html#parameters",
+ "href": "python/reference/Edge.html#parameters",
+ "title": "1 Edge",
+ "section": "",
+ "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired"
},
{
- "objectID": "core/usage.html#flow-results",
- "href": "core/usage.html#flow-results",
- "title": "Usage",
- "section": "5.6 Flow results",
- "text": "5.6 Flow results\nThe flow table contains results of the flow on every edge in the model, for each solver timestep.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nedge_id\nUnion{Int, Missing}\n-\n\n\nfrom_node_id\nInt\n-\n\n\nto_node_id\nInt\n-\n\n\nflow\nFloat64\n\\(m^3 s^{-1}\\)\n\n\n\nThe table is sorted by time, and per time the same edge_id order is used, though not sorted. Flows that are added to the model at a node, have a missing edge_id, and identical from_node_id and to_node_id. Flows out of the model always have a negative sign, and additions a positive sign."
+ "objectID": "python/reference/Basin.html",
+ "href": "python/reference/Basin.html",
+ "title": "1 Basin",
+ "section": "",
+ "text": "1 Basin\nBasin()"
},
{
- "objectID": "core/usage.html#tabulatedratingcurve-time",
- "href": "core/usage.html#tabulatedratingcurve-time",
- "title": "Usage",
- "section": "7.1 TabulatedRatingCurve / time",
- "text": "7.1 TabulatedRatingCurve / time\nThis table is the transient form of the TabulatedRatingCurve table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. With this the rating curves can be updated over time. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-\n\n\ndischarge\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative"
+ "objectID": "python/reference/FlowBoundary.html",
+ "href": "python/reference/FlowBoundary.html",
+ "title": "1 FlowBoundary",
+ "section": "",
+ "text": "1 FlowBoundary\nFlowBoundary()"
},
{
- "objectID": "core/usage.html#user-time",
- "href": "core/usage.html#user-time",
- "title": "Usage",
- "section": "10.1 User / time",
- "text": "10.1 User / time\nThis table is the transient form of the User table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the demand can be updated over time. In between the given times the demand is interpolated linearly, and outside the demand is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntime\nDateTime\n-\nsorted per priority per node id\n\n\ndemand\nFloat64\n\\(m^3 s^{-1}\\)\n-\n\n\nreturn_factor\nFloat64\n-\nbetween [0 - 1]\n\n\nmin_level\nFloat64\n\\(m\\)\n(optional)\n\n\npriority\nInt\n-\nsorted per node id"
+ "objectID": "python/reference/Outlet.html",
+ "href": "python/reference/Outlet.html",
+ "title": "1 Outlet",
+ "section": "",
+ "text": "1 Outlet\nOutlet()"
},
{
- "objectID": "core/usage.html#allocation-results",
- "href": "core/usage.html#allocation-results",
- "title": "Usage",
- "section": "10.2 Allocation results",
- "text": "10.2 Allocation results\nThe allocation table contains a record of allocation results: when it happened, for which user node, in which allocation network, and what the demand, allocated flow and abstracted flow were.\n\n\n\ncolumn\ntype\n\n\n\n\ntime\nDateTime\n\n\nallocation_network_id\nInt\n\n\nuser_node_id\nInt\n\n\npriority\nInt\n\n\ndemand\nFloat64\n\n\nallocated\nFloat64\n\n\nabstracted\nFloat64\n\n\n\n\n\n\n\n\n\nNote\n\n\n\nCurrently the stored demand and abstraction rate are those at the allocation timepoint (and the abstraction rate is based on the previous allocation optimization). In the future these will be an average over the previous allocation timestep."
+ "objectID": "python/reference/FractionalFlow.html",
+ "href": "python/reference/FractionalFlow.html",
+ "title": "1 FractionalFlow",
+ "section": "",
+ "text": "1 FractionalFlow\nFractionalFlow()"
},
{
- "objectID": "core/usage.html#levelboundary-time",
- "href": "core/usage.html#levelboundary-time",
- "title": "Usage",
- "section": "11.1 LevelBoundary / time",
- "text": "11.1 LevelBoundary / time\nThis table is the transient form of the LevelBoundary table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the levels can be updated over time. In between the given times the level is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-"
+ "objectID": "python/reference/index.html",
+ "href": "python/reference/index.html",
+ "title": "1 API Reference",
+ "section": "",
+ "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.\n\n\n\n\n\n\nThe Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUser\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl"
},
{
- "objectID": "core/usage.html#flowboundary-time",
- "href": "core/usage.html#flowboundary-time",
- "title": "Usage",
- "section": "12.1 FlowBoundary / time",
- "text": "12.1 FlowBoundary / time\nThis table is the transient form of the FlowBoundary table. The only differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the flow rates can be updated over time. In between the given times the flow rate is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nflow_rate\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative"
+ "objectID": "python/reference/index.html#model",
+ "href": "python/reference/index.html#model",
+ "title": "1 API Reference",
+ "section": "",
+ "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input."
},
{
- "objectID": "core/usage.html#sec-condition",
- "href": "core/usage.html#sec-condition",
- "title": "Usage",
- "section": "16.1 DiscreteControl / condition",
- "text": "16.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_feature_id\nInt\n-\n-\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”\n\n\ngreater_than\nFloat64\nvarious\n-\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)"
+ "objectID": "python/reference/index.html#network",
+ "href": "python/reference/index.html#network",
+ "title": "1 API Reference",
+ "section": "",
+ "text": "The Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes."
},
{
- "objectID": "core/usage.html#discretecontrol-logic",
- "href": "core/usage.html#discretecontrol-logic",
- "title": "Usage",
- "section": "16.2 DiscreteControl / logic",
- "text": "16.2 DiscreteControl / logic\nThe logic schema defines which control states are triggered based on the truth of the conditions a discrete_control node listens to. DiscreteControl is applied in the Julia core as follows:\n\nDuring the simulation it is checked whether the truth of any of the conditions changes.\nWhen a condition changes, the corresponding discrrete_control node id is retrieved (node_id in the condition schema above).\nThe truth value of all the conditions this discrete_control node lisens to are retrieved, in the order as they are specified in the condition schema. This is then converted into a string of “T” for true and “F” for false. This string we call the truth state.*\nThe table below determines for the given discrete_control node ID and truth state what the corresponding control state is.\nFor all the nodes this discrete_control node affects (as given by the “control” edges in Edges / static), their parameters are set to those parameters in NodeType / static corresponding to the determined control state.\n\n*. There is also a second truth state created in which for the last condition that changed it is specified whether it was an upcrossing (“U”) or downcrossing (“D”) of the threshold (greater than) value. If a control state is specified for a truth state that is crossing-specific, this takes precedence over the control state for the truth state that contains only “T” and “F”.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntruth_state\nString\n-\nConsists of the characters “T” (true), “F” (false), “U” (upcrossing), “D” (downcrossing) and “*” (any)\n\n\ncontrol_state\nString\n-"
+ "objectID": "python/reference/index.html#node-types",
+ "href": "python/reference/index.html#node-types",
+ "title": "1 API Reference",
+ "section": "",
+ "text": "Available node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUser\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl"
},
{
- "objectID": "core/usage.html#discretecontrol-results",
- "href": "core/usage.html#discretecontrol-results",
- "title": "Usage",
- "section": "16.3 DiscreteControl results",
- "text": "16.3 DiscreteControl results\nThe control table contains a record of each change of control state: when it happened, which control node was involved, to which control state it changed and based on which truth state.\n\n\n\ncolumn\ntype\n\n\n\n\ntime\nDateTime\n\n\ncontrol_node_id\nInt\n\n\ntruth_state\nString\n\n\ncontrol_state\nString"
+ "objectID": "python/index.html",
+ "href": "python/index.html",
+ "title": "Python tooling",
+ "section": "",
+ "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip:\npip install ribasim\nFor wheel (.whl) downloads, including nightly builds, see the download section. After downloading wheels can be installed by referring to the correct path:\npip install path/to/ribasim-*.whl\nFor documentation please see the examples and API reference."
},
{
- "objectID": "core/usage.html#pidcontrol-time",
- "href": "core/usage.html#pidcontrol-time",
- "title": "Usage",
- "section": "17.1 PidControl / time",
- "text": "17.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted per time\n\n\ntime\nDateTime\n-\nsorted\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-"
+ "objectID": "src/index.html",
+ "href": "src/index.html",
+ "title": "1 API Reference",
+ "section": "",
+ "text": "This is the private internal documentation of the Ribasim API.\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:module]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:type]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:function]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:constant]\n\n\n\nModules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
},
{
- "objectID": "core/index.html",
- "href": "core/index.html",
- "title": "Julia core",
+ "objectID": "src/index.html#modules",
+ "href": "src/index.html#modules",
+ "title": "1 API Reference",
"section": "",
- "text": "With the term “core”, we mean the computational engine of Ribasim. As detailed in the usage documentation, it is generally used as a command line tool.\nThe theory is described on the equations page, and more in-depth numerical considerations are described on the numerical considerations page. As allocation is a large and self-contained part of the Ribasim core, it is described on the separate allocation page.\nThe core is implemented in the Julia programming language, and can be found in the Ribasim repository under the core/ folder. For developers we also advise to read the developer documentation.\n\n\n\n\nflowchart TB\nmodeler([Modeler]):::user\n\napi[\"Ribasim Python\\n[python]\"]:::system\nmodeler-->|prepare model|api\n\nsubgraph ribasimBoundary[Ribasim]\n ribasim[\"Ribasim.jl\\n[julia]\"]:::system\n libribasim[\"libribasim\\n[julia + python + BMI]\"]:::system\n cli[\"Ribasim CLI\\n[julia]\"]:::system\n cli-->ribasim\n libribasim-->ribasim\nend\nmodeler-->|start|cli\nmodeler-->|coupled simulation|libribasim\n\nsubgraph qgisBoundary[QGIS]\n QGIS[QGIS Application]:::system_ext\n qgisPlugin[\"Ribasim QGIS plugin\\n[python]\"]:::system\n QGIS-->qgisPlugin\nend\nmodeler-->|prepare model|qgisBoundary\n\nmodel[(\"input model data\\n[toml + geopackage + arrow]\")]:::system\nqgisPlugin-->|read/write|model\napi-->|read/write|model\nribasim-->|simulate|model\n\noutput[(\"simulation output\\n[arrow]\")]:::system\nribasim-->|write|output\n\nclass qgisBoundary,ribasimBoundary boundary\n\n%% class definitions for C4 model\nclassDef default stroke-width:1px,stroke:white,color:white\nclassDef system fill:#1168bd\nclassDef user fill:#08427b\nclassDef system_ext fill:#999999\nclassDef boundary fill:transparent,stroke-dasharray:5 5,stroke:black,color:black\n\n\nComponent overview of Ribasim\n\n\n\n\n1 The simulation loop\nThe figure below shows a simple flowchart of the simulation in Ribasim.jl.\n\n\n\n\nflowchart LR\nStart((Start))\nInit[Initialize model]\nCon[Conditional: allocation, control]\nSim[Simulate flows over timestep]\nFinished{End of simulation period?}\nDone((Done))\n\nStart --> Init\nInit --> Con\nCon --> Sim\nSim --> Finished\nFinished -->|no| Con\nFinished -->|yes| Done"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:module]"
},
{
- "objectID": "contribute/python.html",
- "href": "contribute/python.html",
- "title": "Python tooling development",
+ "objectID": "src/index.html#types",
+ "href": "src/index.html#types",
+ "title": "1 API Reference",
"section": "",
- "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi run install\n\n\n\nIn order to run tests on Ribasim Python execute\npixi run test-ribasim-python\n\n\n\nMake sure to run Clear All Outputs on the notebook before committing.\n\n\n\nBefore running the Julia tests or building binaries, example model input needs to created. This is done by running the following:\npixi run generate-testmodels\nThis places example model input files under ./generated_testmodels/. If the example models change, re-run this script.\n\n\n\nInstall the Python, ruff and autoDocstring extensions.\n\n\n\nTo run our linting suite locally, execute:\npixi run lint"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:type]"
},
{
- "objectID": "contribute/python.html#setting-up-pixi",
- "href": "contribute/python.html#setting-up-pixi",
- "title": "Python tooling development",
+ "objectID": "src/index.html#functions",
+ "href": "src/index.html#functions",
+ "title": "1 API Reference",
"section": "",
- "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi run install"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:function]"
},
{
- "objectID": "contribute/python.html#sec-test",
- "href": "contribute/python.html#sec-test",
- "title": "Python tooling development",
+ "objectID": "src/index.html#constants",
+ "href": "src/index.html#constants",
+ "title": "1 API Reference",
"section": "",
- "text": "In order to run tests on Ribasim Python execute\npixi run test-ribasim-python"
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:constant]"
},
{
- "objectID": "contribute/python.html#updating-example-notebooks",
- "href": "contribute/python.html#updating-example-notebooks",
+ "objectID": "src/index.html#macros",
+ "href": "src/index.html#macros",
+ "title": "1 API Reference",
+ "section": "",
+ "text": "Modules = [Ribasim, Ribasim.config]\nOrder = [:macro]"
+ },
+ {
+ "objectID": "contribute/core.html",
+ "href": "contribute/core.html",
+ "title": "Julia core development",
+ "section": "",
+ "text": "This section is about the Julia core in Ribasim.jl. See the component overview here for the context of this computational core.\nRibasim.jl can be divided into 3 parts:\n\nModel initialization\nRunning the simulation loop\nWriting the output files\n\nThe figure below gives a more detailed description of the simulation loop in the form of a sequence diagram. From top to bottom, it contains the following blocks:\n\nAllocation optimization; activated when the allocation timestep has been passed;\nControl actions; activated when some discrete control callback is triggered;\nWater balance; computing the flows over flow edges happens each timestep;\nTime integration step; done by the integrator from OrdinaryDiffEq.jl.\n\n\n\n\n\nsequenceDiagram\n autonumber\n participant Int as Process: Integrator\n participant Optim as Process: Allocation optimization\n participant Param as Data: Parameters\n participant State as Data: State\n participant Sim as Process: Water balance\n loop Simulation loop (OrdinaryDiffEq.jl)\n activate Int\n %% Allocation\n rect rgb(200, 200, 200)\n opt Allocation optimization, per allocation network (JuMP.jl, HiGHS)\n activate Optim\n Int->>Optim: Callback: allocation timestep has passed\n Param-->>Optim: Input\n State-->>Optim: Input\n Optim->>Optim: Optimize Basin allocations if below target level\n Optim->>Optim: Optimize User allocation, per priority\n Optim-->>Param: Set allocated flow rates\n deactivate Optim\n end\n end\n %% Control\n rect rgb(200, 200, 200)\n opt Control actions\n Int->>Int: DiscreteControl callback\n Int-->>Param: Parameter updates by control\n end\n end\n %% water_balance!\n rect rgb(200, 200, 200)\n activate Sim\n State-->>Sim: Input\n Param-->>Sim: Input\n Sim->>Sim: Compute flows over edges per node type\n Sim-->>Param: Set flows\n deactivate Sim\n end\n %% Time integration\n rect rgb(200, 200, 200)\n State-->>Int: Input\n Param-->>Int: Input\n Int->>Int: Time integration step\n Int-->>State: Update state\n end\n deactivate Int\n end"
+ },
+ {
+ "objectID": "contribute/core.html#install-julia-via-juliaup",
+ "href": "contribute/core.html#install-julia-via-juliaup",
+ "title": "Julia core development",
+ "section": "2.1 Install Julia via Juliaup",
+ "text": "2.1 Install Julia via Juliaup\nInstall Julia via Juliaup as described in its README."
+ },
+ {
+ "objectID": "contribute/core.html#install-julia-libraries",
+ "href": "contribute/core.html#install-julia-libraries",
+ "title": "Julia core development",
+ "section": "2.2 Install Julia libraries",
+ "text": "2.2 Install Julia libraries\nStart the Julia REPL by executing julia in your terminal. Within the REPL type ] to enter the Pkg REPL. For more information on how to use Pkg, see the Getting Started page in its documentation. There you can add Revise and TestEnv to your global environment.\npkg> add Revise TestEnv"
+ },
+ {
+ "objectID": "contribute/core.html#setup-revise.jl",
+ "href": "contribute/core.html#setup-revise.jl",
+ "title": "Julia core development",
+ "section": "2.3 Setup Revise.jl",
+ "text": "2.3 Setup Revise.jl\nRevise.jl is a library that allows you to modify code and use the changes without restarting Julia. You can let it start automatically by following these instructions."
+ },
+ {
+ "objectID": "contribute/core.html#clone-ribasim",
+ "href": "contribute/core.html#clone-ribasim",
+ "title": "Julia core development",
+ "section": "2.4 Clone Ribasim",
+ "text": "2.4 Clone Ribasim\nIn order to have the Ribasim repository locally available, you can clone it with Git. Git can be installed from git-scm.com. Once installed, run the following command at a directory of your choice:\nIn order to have the Ribasim repository locally available, run the following command at a directory of your choice:\ngit clone https://github.com/Deltares/Ribasim.git\nTo continue with the following steps, make the root of the repository your working directory by running\ncd Ribasim"
+ },
+ {
+ "objectID": "contribute/core.html#install-visual-studio-code-optional",
+ "href": "contribute/core.html#install-visual-studio-code-optional",
+ "title": "Julia core development",
+ "section": "2.5 Install Visual Studio Code (optional)",
+ "text": "2.5 Install Visual Studio Code (optional)\nThere is a section on editors and IDEs for Julia on https://julialang.org/, scroll down to see it. We use and recommend Microsoft’s free editor Visual Studio Code. When combined with the Julia extension it provides a powerful and interactive development experience."
+ },
+ {
+ "objectID": "contribute/core.html#sec-test",
+ "href": "contribute/core.html#sec-test",
+ "title": "Julia core development",
+ "section": "3.1 Running tests",
+ "text": "3.1 Running tests\nYou will want to run the testsuite on a regular basis to check if your changes had unexpected side effects. It is also a good way to find out if your development environment is set up correctly.\nBefore the tests can run, you need to prepare model input.\nWith the root of the repository as your working directory you can start the REPL with activated Ribasim environment by running the following:\njulia --project\nWhile not technically required, it is advised to import Ribasim first to catch installation issues early on.\njulia> using Ribasim\nThen open the Pkg REPL by typing ] and execute:\npkg> test Ribasim\nIn order to debug tests, it is very useful to run them in a REPL. However, here, you don’t have the dependencies available in the [extras] section of your Project.toml. TestEnv.jl that we installed earlier solves that problem.\nWhen you then debug your tests inside the REPL, you can include the [extras] dependencies as follows:\nusing TestEnv,\nTestEnv.activate(\"Ribasim\")"
+ },
+ {
+ "objectID": "contribute/core.html#render-documentation",
+ "href": "contribute/core.html#render-documentation",
+ "title": "Julia core development",
+ "section": "3.2 Render documentation",
+ "text": "3.2 Render documentation\nExample models are created and simulated as part of the rendering of the documentation. The Julia API reference is created using Documenter.jl by running this command:\npixi run build-julia-docs\nIn order to preview documentation you can run the following command from the docs/ folder. Afterwards, a browser tab will open with the rendered documentation, updating it as you make changes.\npixi run quarto-preview\nThe documentation also includes Jupyter notebooks. Note that they are stored in the repository without any output, and this should stay this way to keep the repository small. The documentation rendering process adds the output by running the notebooks.\n\n\n\n\n\n\nTip\n\n\n\nThe Jupyter VS Code extension allows you to run Jupyter notebooks directly in VS Code."
+ },
+ {
+ "objectID": "contribute/core.html#run-ribasim-simulations",
+ "href": "contribute/core.html#run-ribasim-simulations",
+ "title": "Julia core development",
+ "section": "3.3 Run Ribasim simulations",
+ "text": "3.3 Run Ribasim simulations\nAssuming your working directory is the root of the repository, you can activate this project by entering the Pkg mode of the REPL with ] and execute:\npkg> activate .\npkg> instantiate\nPress backspace to go back to the Julia REPL. There you can run a model with:\njulia> Ribasim.run(\"path/to/model/ribasim.toml\")\n\n\n\n\n\n\nTip\n\n\n\nThe Julia VS Code extension allows you to execute code cells in REPL. This is a very convenient way of executing only parts of your source file."
+ },
+ {
+ "objectID": "contribute/python.html",
+ "href": "contribute/python.html",
+ "title": "Python tooling development",
+ "section": "",
+ "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi run install\n\n\n\nIn order to run tests on Ribasim Python execute\npixi run test-ribasim-python\n\n\n\nMake sure to run Clear All Outputs on the notebook before committing.\n\n\n\nBefore running the Julia tests or building binaries, example model input needs to created. This is done by running the following:\npixi run generate-testmodels\nThis places example model input files under ./generated_testmodels/. If the example models change, re-run this script.\n\n\n\nInstall the Python, ruff and autoDocstring extensions.\n\n\n\nTo run our linting suite locally, execute:\npixi run lint"
+ },
+ {
+ "objectID": "contribute/python.html#setting-up-pixi",
+ "href": "contribute/python.html#setting-up-pixi",
+ "title": "Python tooling development",
+ "section": "",
+ "text": "First, set up pixi as described on their getting started page.\nThen set up the environment by running the following commands:\npixi run install"
+ },
+ {
+ "objectID": "contribute/python.html#sec-test",
+ "href": "contribute/python.html#sec-test",
+ "title": "Python tooling development",
+ "section": "",
+ "text": "In order to run tests on Ribasim Python execute\npixi run test-ribasim-python"
+ },
+ {
+ "objectID": "contribute/python.html#updating-example-notebooks",
+ "href": "contribute/python.html#updating-example-notebooks",
"title": "Python tooling development",
"section": "",
"text": "Make sure to run Clear All Outputs on the notebook before committing."
@@ -223,13 +307,6 @@
"section": "",
"text": "To run our linting suite locally, execute:\npixi run lint"
},
- {
- "objectID": "contribute/qgis.html",
- "href": "contribute/qgis.html",
- "title": "QGIS plugin development",
- "section": "",
- "text": "1 Set up the developer environment\nFirst set up pixi as described in the Python tooling page.\nQGIS is installed with Pixi when running pixi run install. To further set up the development environment, you must add the plugins to QGIS. The simplest way to do this is by running pixi run install-qgis-plugins. It grabs the latest version of the iMOD QGIS plugin and it makes a symlink to the ribasim_qgis folder so that QGIS can find it.\n\n\n\n\n\n\nNote\n\n\n\nOn Windows you need to have Developer mode enabled. Otherwise you will not have enough access rights to create symlinks. For more info, see this Windows blog.\n\n\n\n\n2 Running QGIS\nIn order to run QGIS with the plugins, simply call pixi run qgis. You will find the Ribasim and iMOD plugins in the tool bars.\n\n\n\n\n\n\nNote\n\n\n\nOn Windows, running QGIS from the start menu will disable Python, and thus the plugins. QGIS needs some more paths during the startup and the Pixi environment provides those.\n\n\n\n\n3 Running tests\nTo run the QGIS plugin tests in the application environment of QGIS, it is best to make use of the Docker environment provided in this repository. Make sure that docker is installed and available in your path.\nThen simply call pixi run test-ribasim-qgis.\n\n\n4 Debugging\nAfter installing the plugins via pixi run install-qgis-plugins. Extra debugging tools are also installed in QGIS that is installed within your pixi environment.\nAfter you have started pixi run qgis, you can make alterations to the python code and use the Plugin Reloader to reload the plugin without restarting QGIS. The shortcut in QGIS is CTRL+F5.\nIt is also possible to connect the debugger of Visual Studio Code. For this the debugvs plugin is installed in QGIS. In QGIS press the button to Enable Debug for Visual Studio. Then go to Visual Studio Code and start the launch task Ribasim QGIS: Attach to QGIS. Now you can place breakpoints.\n\n\n\n\n\n\nNote\n\n\n\nWe are currently using debugvs 0.7 with ptvsd as service, since there is an open issue that breaks debugvs 0.8 with debugpy."
- },
{
"objectID": "contribute/addnode.html",
"href": "contribute/addnode.html",
@@ -273,389 +350,277 @@
"text": "2.1 Python class\nCreate a new file python/ribasim/ribasim/node_types/new_node_type.py which is structured as follows:\nfrom typing import Optional\n\nimport pandera as pa\nfrom pandera.engines.pandas_engine import PydanticModel\nfrom pandera.typing import DataFrame\nfrom pydantic import ConfigDict\n\nfrom ribasim import models\nfrom ribasim.input_base import TableModel\n\n__all__ = (\"NewNodeType\",)\n\nclass StaticSchema(pa.SchemaModel):\n class Config:\n \"\"\"Config with dataframe-level data type.\"\"\"\n\n dtype = PydanticModel(models.NewNodeTypeStatic)\n\n# Possible other schemas\n\n\nclass NewNodeType(TableModel):\n \"\"\"\n Description of this node type.\n\n Parameters\n ----------\n static: pandas.DataFrame\n table with data for this node type.\n\n possible other schemas\n \"\"\"\n\n static: DataFrame[StaticSchema] | None\n # possible other schemas\n\n model_config = ConfigDict(validate_assignment=True)\n\n def sort(self):\n self.static.sort_values(\"node_id\", ignore_index=True, inplace=True)\nThe sort method should implement the same sorting as in validation.jl.\nNow in both python/ribasim/ribasim/__init__.py and python/ribasim/ribasim/node_types/__init__.py add\n\nfrom ribasim.node_types.new_node_type import NewNodeType;\n\"NewNodeType\" to __all__.\n\nIn python/ribasim/ribasim/model.py, add\n\nfrom ribasim.new_node_type import NewNodeType;\nnew_node_type as a parameter and in the docstring of the Model class.\n\nIn python/ribasim/ribasim/geometry/node.py add a color and shape description in the MARKERS and COLORS dictionaries."
},
{
- "objectID": "index.html",
- "href": "index.html",
- "title": "Ribasim",
+ "objectID": "core/equations.html",
+ "href": "core/equations.html",
+ "title": "Equations",
"section": "",
- "text": "Ribasim is a water resources model, designed to be the replacement of the regional surface water modules Mozart and SIMRES in the Netherlands Hydrological Instrument (NHI). Ribasim is a work in progress, it is a prototype that demonstrates all essential functionalities. Further development of the prototype in a software release is planned in 2022 and 2023.\nRibasim is written in the Julia programming language and is built on top of the SciML: Open Source Software for Scientific Machine Learning libraries, notably DifferentialEquations.jl."
+ "text": "Ribasim currently simulates the following “natural” water balance terms:\nAdditionally, Ribasim simulates the following “allocated” water balance terms:\nDepending on the type of boundary conditions, Ribasim requires relation between storage volume and wetted area \\(A\\), and between the storage volume and the water level \\(h\\). These are (currently) represented by piecewise linear relationships."
},
{
- "objectID": "index.html#water-balance-equations",
- "href": "index.html#water-balance-equations",
- "title": "Ribasim",
- "section": "3.1 Water balance equations",
- "text": "3.1 Water balance equations\nThe water balance equation for a drainage basin (Wikipedia contributors 2022) can be defined by a first-order ordinary differential equation (ODE), where the change of the storage \\(S\\) over time is determined by the inflow fluxes minus the outflow fluxes.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = Q_{in} - Q_{out}\n\\]\nWe can split out the fluxes into separate terms, such as precipitation \\(P\\), evapotranspiration \\(ET\\) and runoff \\(R\\). For now other fluxes are combined into \\(Q_{rest}\\). If we define all fluxes entering our reservoir as positive, and those leaving the system as negative, all fluxes can be summed up.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = R + P + ET + Q_{rest}\n\\]"
+ "objectID": "core/equations.html#the-jacobian",
+ "href": "core/equations.html#the-jacobian",
+ "title": "Equations",
+ "section": "1.1 The Jacobian",
+ "text": "1.1 The Jacobian\nThe Jacobian is a \\(n\\times n\\) matrix where \\(n\\) is the number of states in the simulation. The Jacobian is computed either using finite difference methods or automatic differentiation. For more details on the computation of the Jacobian and how it is used in the solvers see numerical considerations.\nThe entries of the Jacobian \\(J\\) are given by \\[\nJ[i,j] = \\frac{\\partial f_j}{\\partial u_i},\n\\]\nhence \\(J[i,j]\\) quantifies how \\(f_j\\), the derivative of state \\(j\\) with respect to time, changes with a change in state \\(i\\). If a node creates dependendies between basin storages (or other states), then this yields contributions to the Jacobian. If \\(j\\) corresponds to a storage state, then\n\\[\nJ[i,j] = \\sum_{(i',j') \\in E | j' = i} \\frac{\\partial Q_{i',j'}}{\\partial u_i} - \\sum_{(i',j') \\in E | i' = i} \\frac{\\partial Q_{i',j'}}{\\partial u_i},\n\\]\nMost of these terms are always \\(0\\), because a flow over an edge only depends on a small number of states. Therefore the matrix \\(J\\) is very sparse.\nFor many contributions to the Jacobian the derivative of the level \\(l(S)\\) of a basin with respect to its storage \\(S\\) is required. To get an expression for this, we first look at the storage as a function of the level:\n\\[\nS(l) = \\int_{l_0}^l A(\\ell)d\\ell.\n\\]\nFrom this we obtain \\(S'(l) = A(l)\\) and thus \\[\n\\frac{\\text{d}l}{\\text{d}S} = \\frac{1}{A(S)}.\n\\]\n\n\n\n\n\n\nNote\n\n\n\nThe presence of division by the basin area means that areas of size zero are not allowed."
},
{
- "objectID": "index.html#time",
- "href": "index.html#time",
- "title": "Ribasim",
- "section": "3.2 Time",
- "text": "3.2 Time\nThe water balance equation can be applied on many timescales; years, weeks, days or hours. Depending on the application and available data any of these can be the best choice. In Ribasim, we make use of DifferentialEquations.jl and its ODE solvers. Many of these solvers are based on adaptive time stepping, which means the solver will decide how large the time steps can be depending on the state of the system.\nThe forcing, like precipitation, is generally provided as a time series. Ribasim is set up to support unevenly spaced timeseries. The solver will stop on timestamps where new forcing values are available, so they can be loaded as the new value.\nRibasim is essentially a continuous model, rather than daily or hourly. If you want to use hourly forcing, you only need to make sure that your forcing data contains hourly updates. The output frequency can be configured independently. To be able to write a closed water balance, we accumulate the fluxes. This way any variations in between timesteps are also included, and we can output in m³ rather than m³s⁻¹."
+ "objectID": "core/equations.html#sec-reduction_factor",
+ "href": "core/equations.html#sec-reduction_factor",
+ "title": "Equations",
+ "section": "2.1 The reduction factor",
+ "text": "2.1 The reduction factor\nAt several points in the equations below a reduction factor is used. This is a term that makes certain transitions more smooth, for instance when a pump stops providing water when its source basin dries up. The reduction factor is given by\n\\[\\begin{align}\n \\phi(x; p) =\n \\begin{cases}\n 0 &\\text{if}\\quad x < 0 \\\\\n -2 \\left(\\frac{x}{p}\\right)^3 + 3\\left(\\frac{x}{p}\\right)^2 &\\text{if}\\quad 0 \\le x \\le p \\\\\n 1 &\\text{if}\\quad x > p\n \\end{cases}\n\\end{align}\\]\nHere \\(p > 0\\) is the threshold value which determines the interval \\([0,p]\\) of the smooth transition between \\(0\\) and \\(1\\), see the plot below.\n\n\nCode\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef f(x, p = 3):\n x_scaled = x / p\n phi = (-2 * x_scaled + 3) * x_scaled**2\n phi = np.where(x < 0, 0, phi)\n phi = np.where(x > p, 1, phi)\n\n return phi\n\nfontsize = 15\np = 3\nN = 100\nx_min = -1\nx_max = 4\nx = np.linspace(x_min,x_max,N)\nphi = f(x,p)\n\nfig,ax = plt.subplots(dpi=80)\nax.plot(x,phi)\n\ny_lim = ax.get_ylim()\n\nax.set_xticks([0,p], [0,\"$p$\"], fontsize=fontsize)\nax.set_yticks([0,1], [0,1], fontsize=fontsize)\nax.hlines([0,1],x_min,x_max, color = \"k\", ls = \":\", zorder=-1)\nax.vlines([0,p], *y_lim, color = \"k\", ls = \":\")\nax.set_xlim(x_min,x_max)\nax.set_xlabel(\"$x$\", fontsize=fontsize)\nax.set_ylabel(\"$\\phi(x;p)$\", fontsize=fontsize)\nax.set_ylim(y_lim)\n\nfig.tight_layout()\nplt.show()"
},
{
- "objectID": "index.html#sec-space",
- "href": "index.html#sec-space",
- "title": "Ribasim",
- "section": "3.3 Space",
- "text": "3.3 Space\nThe water balance equation can be applied on different spatial scales. Besides modelling a single lumped watershed, it allows you to divide the area into a network of connected representative elementary watersheds (REWs) (Reggiani, Sivapalan, and Majid Hassanizadeh 1998). At this scale global water balance laws can be formulated by means of integration of point-scale conservation equations over control volumes. Such an approach makes Ribasim a semi-distributed model. In this document we typically use the term “basin” to refer to the REW. (In Mozart the spatial unit was called Local Surface Water (LSW)). Each basin has an associated polygon, and the set of basins is connected to each other as described by a graph, which we call the network. Below is a representation of both on the map.\n\n\n\nMozart Local Surface Water polygons and their drainage.\n\n\nThe network is described as graph. Flow can be bi-directional, and the graph does not have to be acyclic.\n\n\n\n\ngraph LR;\n A[\"basin A\"] --- B[\"basin B\"];\n A --- C[\"basin C\"];\n B --- D[\"basin D\"];\n C --- D;\n\n\n\n\n\nInternally a directed graph is used. The direction is defined to be the positive flow direction, and is generally set in the dominant flow direction. The basins are the nodes of the network graph. Basin states and properties such storage volume and wetted area are associated with the nodes (A, B, C, D), as are most forcing data such as precipitation, evaporation, or water demand. Basin connection properties and interbasin flows are associated with the edges (the lines between A, B, C, and D) instead.\nMultiple basins may exist within the same spatial polygon, representing different aspects of the surface water system (perennial ditches, ephemeral ditches, or even surface ponding). Figure 1, Figure 2, Figure 3 show the 25.0 m rasterized primary, secondary, and tertiary surface waters as identified by BRT TOP10NL (PDOK 2022) in the Hupsel basin (as defined in the Mozart LSW’s). These systems may represented in multiple ways.\n\n\n\nFigure 1: Hupsel: primary surface water.\n\n\n\n\n\nFigure 2: Hupsel: secondary surface water.\n\n\n\n\n\nFigure 3: Hupsel: tertiary surface water.\n\n\nAs a single basin (A) containing all surface water, discharging to its downstream basin to the west (B):\n\n\n\n\ngraph LR;\n A[\"basin A\"] --> B[\"basin B\"];\n\n\n\n\n\nSuch a system may be capable of representing discharge, but it cannot represent residence times or differences in solute concentrations: within a single basin, drop of water is mixed instantaneously. Instead, we may the group primary (P), secondary (S), and tertiary (T) surface waters. Then T may flow into S, S into P, and P discharges to the downstream basin (B.)\n\n\n\n\ngraph LR;\n T[\"basin T\"] --> S[\"basin S\"];\n S --> P[\"basin P\"];\n P --> B[\"basin B\"];\n\n\n\n\n\nAs each (sub)basin has its own volume, low throughput (high volume, low discharge, long residence time) and high throughput (low volume, high discharge, short residence time) systems can be represented in a lumped manner; of course, more detail requires more parameters."
+ "objectID": "core/equations.html#precipitation",
+ "href": "core/equations.html#precipitation",
+ "title": "Equations",
+ "section": "2.2 Precipitation",
+ "text": "2.2 Precipitation\nThe precipitation term is given by\n\\[\n Q_P = P \\cdot A(S).\n\\tag{2}\\]\nHere \\(P = P(t)\\) is the precipitation rate and \\(A\\) is the wetted area. \\(A\\) is a function of the storage \\(S = S(t)\\): as the volume of water changes, the area of the free water surface may change as well, depending on the slopes of the surface waters."
},
{
- "objectID": "python/reference/LinearResistance.html",
- "href": "python/reference/LinearResistance.html",
- "title": "1 LinearResistance",
- "section": "",
- "text": "1 LinearResistance\nLinearResistance()"
+ "objectID": "core/equations.html#evaporation",
+ "href": "core/equations.html#evaporation",
+ "title": "Equations",
+ "section": "2.3 Evaporation",
+ "text": "2.3 Evaporation\nThe evaporation term is given by\n\\[\n Q_E = E_\\text{pot} \\cdot A(S) \\cdot \\phi(d;0.1).\n\\tag{3}\\]\nHere \\(E_\\text{pot} = E_\\text{pot}(t)\\) is the potential evaporation rate and \\(A\\) is the wetted area. \\(\\phi\\) is the reduction factor which depends on the depth \\(d\\). It provides a smooth gradient as \\(S \\rightarrow 0\\).\nA straightforward formulation \\(Q_E = \\mathrm{max}(E_\\text{pot} A(S), 0)\\) is unsuitable, as \\(\\frac{\\mathrm{d}Q_E}{\\mathrm{d}S}(S=0)\\) is then not well-defined.\n\nA non-smooth derivative results in extremely small timesteps and long computation time: ModelingToolkit identifies the singular behavior and adjusts its timestepping. In a physical interpretation, evaporation is switched on or off per individual droplet of water. In general, the effect of the reduction term is negligible, or not even necessary. As a surface water dries, its wetted area decreases and so does the evaporative flux. However, for (simplified) cases with constant wetted surface (a rectangular profile), evaporation only stops at \\(S = 0\\)."
},
{
- "objectID": "python/reference/Node.html",
- "href": "python/reference/Node.html",
- "title": "1 Node",
- "section": "",
- "text": "Node()\nThe Ribasim nodes as Point geometries.\n\n\n\n\n\nName\nDescription\n\n\n\n\nconnectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings\n\n\ngeometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\nNode.connectivity_from_geometry(lines)\nDerive from_node_id and to_node_id for every edge in lines. LineStrings may be used to connect multiple nodes in a sequence, but every linestring vertex must also a node.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int\n\n\n\n\n\n\n\n\nNode.geometry_from_connectivity(from_id, to_id)\nCreate edge shapely geometries from connectivities.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings.\n\n\n\n\n\n\n\nNode.plot(ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone"
+ "objectID": "core/equations.html#infiltration-and-drainage",
+ "href": "core/equations.html#infiltration-and-drainage",
+ "title": "Equations",
+ "section": "2.4 Infiltration and Drainage",
+ "text": "2.4 Infiltration and Drainage\nInfiltration is provided as a lump sum for the basin. If Ribasim is coupled with MODFLOW 6, the infiltration is computed as the sum of all positive flows of the MODFLOW 6 boundary conditions in the basin:\n\\[\n Q_\\text{inf} = \\sum_{i=1}^{n} \\sum_{j=1}^{m} \\max(Q_{\\mathrm{mf6}_{i,j}}, 0.0)\n\\] {#eq-inf}.\nWhere \\(i\\) is the index of the boundary condition, \\(j\\) the MODFLOW 6 cell index, \\(n\\) the number of boundary conditions, and \\(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\\).\nDrainage is a lump sump 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.\n\\[\n Q_\\text{drn} = \\sum_{i=1}^{n} \\sum_{j=1}^{m} \\left| \\min(Q_{\\mathrm{mf6}_{i,j}}, 0.0) \\right|\n\\tag{4}\\]\nThe interaction with MODFLOW 6 boundary conditions is explained in greater detail in the the MODFLOW coupling section of the documentation."
},
{
- "objectID": "python/reference/Node.html#methods",
- "href": "python/reference/Node.html#methods",
- "title": "1 Node",
- "section": "",
- "text": "Name\nDescription\n\n\n\n\nconnectivity_from_geometry\nDerive from_node_id and to_node_id for every edge in lines. LineStrings\n\n\ngeometry_from_connectivity\nCreate edge shapely geometries from connectivities.\n\n\nplot\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\nNode.connectivity_from_geometry(lines)\nDerive from_node_id and to_node_id for every edge in lines. LineStrings may be used to connect multiple nodes in a sequence, but every linestring vertex must also a node.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nNode\n\nrequired\n\n\nlines\nnp.ndarray\nArray of shapely linestrings.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray of int\n\n\n\nnp.ndarray of int\n\n\n\n\n\n\n\n\nNode.geometry_from_connectivity(from_id, to_id)\nCreate edge shapely geometries from connectivities.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nnode\nRibasim.Node\n\nrequired\n\n\nfrom_id\nSequence[int]\nFirst node of every edge.\nrequired\n\n\nto_id\nSequence[int]\nSecond node of every edge.\nrequired\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nnp.ndarray\nArray of shapely LineStrings.\n\n\n\n\n\n\n\nNode.plot(ax=None, zorder=None)\nPlot the nodes. Each node type is given a separate marker.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nOptional\nThe axis on which the nodes will be plotted.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nNone"
+ "objectID": "core/equations.html#upstream-and-downstream-flow",
+ "href": "core/equations.html#upstream-and-downstream-flow",
+ "title": "Equations",
+ "section": "2.5 Upstream and downstream flow",
+ "text": "2.5 Upstream and downstream flow\nRibasim’s basins can be connected to each other, and each basin expects an explicit connection. These connections are currently available for inter-basin flows:\n\n\nPump\nTabulatedRatingCurve\nLinearResistance\nManningResistance\n\nThe flow direction of the basin is not pre-determined: flow directions may freely reverse, provided the connection allows it. Currently, a LinearResistance allows bidirectional flow, but the\nAdditionally, three additional “connections” area available for the “outmost” basins (external nodes) in a network.\n\nTerminal\nLevelBoundary\nFlowBoundary\n\n\n2.5.1 Pump\nThe behaviour of pumps is very straight forward if these nodes are not PID controlled. Their flow is given by a fixed flow rate \\(q\\), multiplied by a reduction factor: \\[\nQ_\\text{pump} = \\phi(u; 10.0)q\n\\]\nHere \\(u\\) is the storage of the upstream basin. The reduction factor \\(\\phi\\) makes sure that the flow of the pump goes smootly to \\(0\\) as the upstream basin dries out.\n\n\n2.5.2 Outlet\nThe outlet is very similar to the pump, but it has a few extra reduction factors for physical constraints: \\[\nQ_\\text{outlet} = \\phi(u_a; 10.0)\\phi(\\Delta h; 0.1) \\phi(h_a-h_\\text{min};0.1)q.\n\\] The subscript \\(a\\) denotes the upstream node and \\(b\\) the downstream node. The first reduction factor is equivalent to the one for the pump. The second one makes sure that the outlet flow goes to zero as the head difference \\(\\Delta h = h_a - h_b\\) goes to zero. The last one makes sure that the outlet only produces flow when the upstream level is above the minimum chrest level \\(h_\\text{min}\\).\nNot all node types upstream or downstream of the outlet have a defined level. If this is the case, and therefore the reduction factor cannot be computed, it is defined to be \\(1.0\\).\n\n\n2.5.3 TabulatedRatingCurve\nThe Tabulated Rating Curve is a tabulation of a basin’s discharge behavior. It describes a piecewise linear relationship between the basin’s level and its discharge. It can be understood as an empirical description of a basin’s properties. This can include an outlet, but also the lumped hydraulic behavior of the upstream channels.\n\nThe Tabulated Rating Curve should indicate at which volume no discharge occurs (the dead storage volume).\n\n\n\n\n\n\nNote\n\n\n\nCurrently, the discharge relies only on the basin’s level; it could also use the volume of both connected basins to simulate backwater effects, submersion of outlets, or even reversal of flows for high precipitation events.\n\n\n\n\n2.5.4 LinearResistance\nA LinearResistance connects two basins together. The flow between the two basins is determined by a linear relationship:\n\\[\n Q = \\frac{h_a - h_b}{R}\n\\tag{5}\\]\nHere \\(h_a\\) is the water level in the first basin, \\(h_b\\) is the water level in the second basin, and \\(R\\) is the resistance of the link. A LinearResistance makes no assumptions about the direction of the flow: water flows from high to low.\n\n\n2.5.5 Terminal\nThis only allows outflow from a basin into a terminal node.\n\n\n2.5.6 LevelBoundary\nThis can be connected to a basin via a LinearResistance. This boundary node will then exchange water with the basin based on the difference in water level between the two.\n\n\n2.5.7 FlowBoundary\nThis can be connected directly to a basin and prescribes the flow to or from that basin. We require that the edge connecting the flow boundary to the basin should point towards the basin, so that positive flow corresponds to water being added to the model.\n\n\n2.5.8 Manning connection\nRibasim is capable of simulating steady flow between basins through a reach described by a trapezoidal profile and a Manning roughness coefficient.\nWe describe the discharge from basin \\(a\\) to basin \\(b\\) solely as a function of the water levels in \\(a\\) and \\(b\\).\n\\[\nQ = f(h_a, h_b)\n\\]\nwhere:\n\nThe subscripts \\(a,b\\) denote basins\n\\(h\\) is the hydraulic head, or water level\n\nThe energy equation for open channel flow is:\n\\[\nH = h + \\frac{v^2}{2g}\n\\]\nWhere\n\n\\(H\\) is total head\n\\(v\\) is average water velocity\n\\(g\\) is gravitational acceleration\n\nThe discharge \\(Q\\) is defined as:\n\\[\nQ = Av\n\\]\nwhere \\(A\\) is cross-sectional area.\nWe use conservation of energy to relate the total head at \\(a\\) to \\(b\\), with \\(H_a > H_b\\) as follows:\n\\[\nH_a = H_b + h_{\\text{loss}}\n\\]\nOr:\n\\[\nh_a + \\frac{v_a^2}{2g} = h_b + \\frac{v_b^2}{2g} + h_{\\text{loss}}\n\\]\nWhere \\(v\\) is the average water velocity. \\(h_{\\text{loss}}\\) is a combination of friction and contraction/expansion losses:\n\\[\nh_{\\text{loss}} = S_f L + \\frac{C}{2g} \\left(v_b^2 - v_a^2\\right)\n\\]\nWhere:\n\n\\(L\\) is the reach length\n\\(S_f\\) is the representative friction slope\n\\(C\\) is the expansion or contraction coefficient, \\(0 \\le C \\le1\\)\n\nWe assume velocity differences in a connection are negligible (\\(v_a = v_b\\)):\n\\[\nh_a = h_b + S_f L\n\\]\nFriction losses are computed with the Gauckler-Manning formula:\n\\[\nQ = \\frac{A}{n} R_h^\\frac{2}{3} \\sqrt{S_f}\n\\]\nWhere:\n\n\\(A\\) is the representative area.\n\\(R_h\\) is the representative wetted radius.\n\\(S_f\\) is the representative friction slope.\n\\(n\\) is Manning’s roughness coefficient.\n\nWe can rewrite to express \\(S_f\\) in terms of Q:\n\\[\nS_f = Q^2 \\frac{n^2}{A^2 R_h^{4/3}}\n\\]\nNo water is added or removed in a connection:\n\\[\nQ_a = Q_b = Q\n\\]\nSubstituting:\n\\[\nh_a = h_b + Q^2 \\frac{n^2}{A^2 R_h^{4/3}} L\n\\]\nWe can then express \\(Q\\) as a function of head difference \\(\\Delta h\\):\n\\[\nQ = \\textrm{sign}(\\Delta h) \\frac{A}{n} R_h^{2/3}\\sqrt{\\frac{|\\Delta h|}{L} }\n\\]\nThe \\(\\textrm{sign}(\\Delta h)\\) term causes the direction of the flow to reverse if the head in basin \\(b\\) is larger than in basin \\(a\\).\nThis expression however leads to problems in simulation since the derivative of \\(Q\\) with respect to \\(\\Delta h\\) tends to \\(\\pm \\infty\\) as \\(\\Delta h\\) tends to 0. Therefore we use the slightly modified expression\n\\[\nQ = \\textrm{sign}(\\Delta h) \\frac{A}{n} R_h^{2/3}\\sqrt{\\frac{\\Delta h}{L} s(\\Delta h)}\n\\]\nto smooth out this problem. Here \\(s(x) = \\frac{2}{\\pi}\\arctan{1000x}\\) can be thought of as a smooth approximation of the sign function.\n\n\n\n\n\n\nNote\n\n\n\nThe computation of \\(S_f\\) is not exact: we base it on a representative area and hydraulic radius, rather than integrating \\(S_f\\) along the length of a reach. Direct analytic solutions exist for e.g. parabolic profiles (Tolkmitt), but other profiles requires relatively complicated approaches (such as approximating the profile with a polynomial).\nWe use the average value of the cross-sectional area, the average value of the water depth, and the average value of the hydraulic radius to compute a friction slope. The size of the resulting error will depend on the water depth difference between the upstream and downstream basin.\n\n\nThe cross sectional area for a trapezoidal or rectangular profile:\n\\[\nA = w d + \\frac{\\Delta y}{\\Delta z} d^2\n\\]\nWhere\n\n\\(w\\) is the width at \\(d = 0\\) (A triangular profile has \\(w = 0\\))\n\\(\\frac{\\Delta y}{\\Delta z}\\) is the slope of the profile expressed as the horizontal length for one unit in the vertical (A slope of 45 degrees has \\(\\frac{\\Delta y}{\\Delta z} = 1\\); a rectangular profile 0).\n\nAccordingly, the wetted perimeter is:\n\\[\nB = w + 2 d \\sqrt{\\left(\\frac{\\Delta y}{\\Delta z}\\right)^2 + 1}\n\\]"
},
{
- "objectID": "python/reference/DiscreteControl.html",
- "href": "python/reference/DiscreteControl.html",
- "title": "1 DiscreteControl",
- "section": "",
- "text": "1 DiscreteControl\nDiscreteControl()"
+ "objectID": "core/equations.html#the-derivative-term",
+ "href": "core/equations.html#the-derivative-term",
+ "title": "Equations",
+ "section": "4.1 The derivative term",
+ "text": "4.1 The derivative term\nWhen \\(K_d \\ne 0\\) this adds a level of complexity. We can see this by looking at the error derivative more closely: \\[\n\\frac{\\text{d}e}{\\text{d}t} = \\frac{\\text{d}\\text{SP}}{\\text{d}t} - \\frac{1}{A(u_\\text{PID})}\\frac{\\text{d}u_\\text{PID}}{\\text{d}t},\n\\] where \\(A(u_\\text{PID})\\) is the area of the controlled basin as a function of the storage of the controlled basin \\(u_\\text{PID}\\). The complexity arises from the fact that \\(Q_\\text{PID}\\) is a contribution to \\(\\frac{\\text{d}u_\\text{PID}}{\\text{d}t} = f_\\text{PID}\\), which makes Equation 7 an implicit equation for \\(Q_\\text{PID}\\). We define\n\\[\nf_\\text{PID} = \\hat{f}_\\text{PID} \\pm Q_\\text{pump/outlet},\n\\]\nthat is, \\(\\hat{f}_\\text{PID}\\) is the right hand side of the ODE for the controlled basin storage state without the contribution of the PID controlled pump. The plus sign holds for an outlet and the minus sign for a pump, dictated by the way the pump and outlet connectivity to the controlled basin is enforced.\nUsing this, solving Equation 7 for \\(Q_\\text{PID}\\) yields \\[\nQ_\\text{pump/outlet} = \\text{clip}\\left(\\phi(u_\\text{us})\\frac{K_pe + K_iI + K_d \\left(\\frac{\\text{d}\\text{SP}}{\\text{d}t}-\\frac{\\hat{f}_\\text{PID}}{A(u_\\text{PID})}\\right)}{1\\pm\\phi(u_\\text{us})\\frac{K_d}{A(u_\\text{PID})}};Q_{\\min},Q_{\\max}\\right),\n\\] where the clipping is again done last. Note that to compute this, \\(\\hat{f}_\\text{PID}\\) has to be known first, meaning that the PID controlled pump/outlet flow rate has to be computed after all other contributions to the PID controlled basin’s storage are known."
},
{
- "objectID": "python/reference/PidControl.html",
- "href": "python/reference/PidControl.html",
- "title": "1 PidControl",
- "section": "",
- "text": "1 PidControl\nPidControl()"
+ "objectID": "core/equations.html#the-sign-of-the-parameters",
+ "href": "core/equations.html#the-sign-of-the-parameters",
+ "title": "Equations",
+ "section": "4.2 The sign of the parameters",
+ "text": "4.2 The sign of the parameters\nNote by Equation 6 that the error is positive if the setpoint is larger than the basin level and negative if the setpoint is smaller than the basin level.\nWe enforce the convention that when a pump is controlled, its edge points away from the basin, and when an outlet is controlled, its edge points towards the basin, so that the main flow direction along these edges is positive. Therefore, positive flows of the pump and outlet have opposite effects on the basin, and thus the parameters \\(K_p,K_i,K_d\\) of the pump and outlet must have oppositive signs to achieve the same goal."
},
{
- "objectID": "python/reference/index.html",
- "href": "python/reference/index.html",
- "title": "1 API Reference",
+ "objectID": "core/usage.html",
+ "href": "core/usage.html",
+ "title": "Usage",
"section": "",
- "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input.\n\n\n\n\n\n\nThe Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes.\n\n\n\n\n\n\nAvailable node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUser\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl"
+ "text": "Ribasim is typically used as a command-line interface (CLI). It is distributed as a .zip archive, that must be downloaded and unpacked. It can be placed anywhere, however it is important that the contents of the zip file are kept together in a directory. The Ribasim CLI executable is in the bin directory.\nTo download ribasim_cli.zip, see the download section.\nTo check whether the installation was performed successfully, run ribasim with no arguments in the command line. This will give the following message:"
},
{
- "objectID": "python/reference/index.html#model",
- "href": "python/reference/index.html#model",
- "title": "1 API Reference",
- "section": "",
- "text": "The Model class represents an entire Ribasim model.\n\n\n\nModel\nA full Ribasim model schematisation with all input."
+ "objectID": "core/usage.html#sec-solver-settings",
+ "href": "core/usage.html#sec-solver-settings",
+ "title": "Usage",
+ "section": "1.1 Solver settings",
+ "text": "1.1 Solver settings\nThe solver section in the configuration file is entirely optional, since we aim to use defaults that will generally work well. Common reasons to modify the solver settings are to adjust the calculation or result stepsizes: adaptive, dt, and saveat. If your model does not converge, or your performance is lower than expected, it can help to adjust other solver settings as well.\nThe default solver algorithm = \"QNDF\", which is a multistep method similar to Matlab’s ode15s (Shampine and Reichelt 1997). It is an implicit method that supports the default adaptive = true timestepping. The full list of available solvers is: QNDF, Rosenbrock23, TRBDF2, Rodas5, KenCarp4, Tsit5, RK4, ImplicitEuler, Euler. Information on the solver algorithms can be found on the ODE solvers page.\nThe dt controls the stepsize. When adaptive = true, dt only applies to the initial stepsize, and by default it is automatically determined. When adaptive = false a suitable dt must always be provided. The value is in seconds, so dt = 3600.0 corresponds to an hourly timestep. When adaptive = true, dtmin and dtmax control the minimum and maximum allowed dt. By default these depend on the problem and algorithm. If a smaller dt than dtmin is needed to meet the set error tolerances, the simulation stops, unless force_dtmin is set to true. force_dtmin is off by default to ensure an accurate solution.\nBy default the calculation and result stepsize are the same, with saveat = [], which will save every timestep. saveat can be a number, which is the saving interval in seconds, or it can be a list of numbers, which are the times in seconds since start that are saved. For instance, saveat = 86400.0 will save results after every day that passed.\nThe Jacobian matrix provides information about the local sensitivity of the model with respect to changes in the states. For implicit solvers it must be calculated often, which can be expensive to do. There are several methods to do this. By default Ribasim uses a Jacobian derived automatically using ForwardDiff.jl with memory management provided by PreallocationTools.jl. If this is not used by setting autodiff = false, the Jacobian is calculated with a finite difference method, which can be less accurate and more expensive.\nBy default the Jacobian matrix is a sparse matrix (sparse = true). Since each state typically only depends on a small number of other states, this is generally more efficient, especially for larger models. The sparsity structure is calculated from the network and provided as a Jacobian prototype to the solver. For small or highly connected models it could be faster to use a dense Jacobian matrix instead by setting sparse = false.\nThe total maximum number of iterations maxiters = 1e9, can normally stay as-is unless doing extremely long simulations.\nThe absolute and relative tolerance for adaptive timestepping can be set with abstol and reltol. For more information on these and other solver options, see the DifferentialEquations.jl docs."
},
{
- "objectID": "python/reference/index.html#network",
- "href": "python/reference/index.html#network",
- "title": "1 API Reference",
- "section": "",
- "text": "The Node and Edge database layers define the network layout.\n\n\n\nNode\nThe Ribasim nodes as Point geometries.\n\n\nEdge\nDefines the connections between nodes."
+ "objectID": "core/usage.html#allocation-settings",
+ "href": "core/usage.html#allocation-settings",
+ "title": "Usage",
+ "section": "1.2 Allocation settings",
+ "text": "1.2 Allocation settings\nCurrently there are the following allocation settings: - use_allocation: A boolean which says whether allocation should be used or not; - timestep: a float value in seconds which dictates the update interval for allocations."
},
{
- "objectID": "python/reference/index.html#node-types",
- "href": "python/reference/index.html#node-types",
- "title": "1 API Reference",
- "section": "",
- "text": "Available node types to model different situations.\n\n\n\nBasin\n\n\n\nFractionalFlow\n\n\n\nTabulatedRatingCurve\n\n\n\nPump\n\n\n\nOutlet\n\n\n\nUser\n\n\n\nLevelBoundary\n\n\n\nFlowBoundary\n\n\n\nLinearResistance\n\n\n\nManningResistance\n\n\n\nTerminal\n\n\n\nDiscreteControl\n\n\n\nPidControl"
+ "objectID": "core/usage.html#basin-time",
+ "href": "core/usage.html#basin-time",
+ "title": "Usage",
+ "section": "5.1 Basin / time",
+ "text": "5.1 Basin / time\nThis table is the transient form of the Basin table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. A linear interpolation between the given timesteps is currently done if the solver takes timesteps between the given data points. More options will be available later."
},
{
- "objectID": "python/reference/Basin.html",
- "href": "python/reference/Basin.html",
- "title": "1 Basin",
- "section": "",
- "text": "1 Basin\nBasin()"
+ "objectID": "core/usage.html#basin-state",
+ "href": "core/usage.html#basin-state",
+ "title": "Usage",
+ "section": "5.2 Basin / state",
+ "text": "5.2 Basin / state\nThe state table aims to capture the full state of the Basin, such that it can be used as an initial condition, potentially the outcome of an earlier simulation. Currently only the Basin node types have state.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlevel\nFloat64\n\\(m\\)\n\\(\\ge\\) basin bottom\n\n\n\nEach Basin ID needs to be in the table."
},
{
- "objectID": "python/reference/User.html",
- "href": "python/reference/User.html",
- "title": "1 User",
- "section": "",
- "text": "1 User\nUser()"
+ "objectID": "core/usage.html#basin-profile",
+ "href": "core/usage.html#basin-profile",
+ "title": "Usage",
+ "section": "5.3 Basin / profile",
+ "text": "5.3 Basin / profile\nThe profile table defines the physical dimensions of the storage reservoir of each basin.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\narea\nFloat64\n\\(m^2\\)\nnon-negative, per node_id: start positive and increasing\n\n\nlevel\nFloat64\n\\(m\\)\nper node_id: increasing\n\n\n\nThe 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 5 rows of such a table is given below. The first 4 rows define the profile of ID 2. The number of rows can vary per ID. Using a very large number of rows may impact performance.\n\n\n\nnode_id\narea\nlevel\n\n\n\n\n2\n1.0\n6.0\n\n\n2\n1000.0\n7.0\n\n\n2\n1000.0\n9.0\n\n\n3\n1.0\n2.2\n\n\n\nWe use the symbol \\(A\\) for area, \\(h\\) for level and \\(S\\) for storage. The profile provides a function \\(A(h)\\) for each basin. Internally this get converted to two functions, \\(A(S)\\) and \\(h(S)\\), by integrating over the function, setting the storage to zero for the bottom of the profile. The minimum area cannot be zero to avoid numerical issues. The maximum area is used to convert the precipitation flux into an inflow."
},
{
- "objectID": "python/reference/Edge.html",
- "href": "python/reference/Edge.html",
- "title": "1 Edge",
- "section": "",
- "text": "Edge()\nDefines the connections between nodes.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired"
+ "objectID": "core/usage.html#basin-subgrid",
+ "href": "core/usage.html#basin-subgrid",
+ "title": "Usage",
+ "section": "5.4 Basin / subgrid",
+ "text": "5.4 Basin / subgrid\nThe subgrid_level table defines a piecewise linear interpolation from a basin water level to a subgrid element water level. Many subgrid elements may be associated with a single basin, each with distinct interpolation functions. This functionality can be used to translate a single lumped basin level to a more spatially detailed representation (e.g comparable to the output of a hydrodynamic simulation).\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nsubgrid_id\nInt\n-\nsorted\n\n\nnode_id\nInt\n-\nconstant per subgrid_id\n\n\nbasin_level\nFloat64\n\\(m\\)\nsorted per subgrid_id\n\n\nsubgrid_level\nFloat64\n\\(m\\)\nsorted per subgrid_id\n\n\n\nThe table below shows example input for two subgrid elements:\n\n\n\nsubgrid_id\nnode_id\nbasin_level\nsubgrid_level\n\n\n\n\n1\n9\n0.0\n0.0\n\n\n1\n9\n1.0\n1.0\n\n\n1\n9\n2.0\n2.0\n\n\n2\n9\n0.0\n0.5\n\n\n2\n9\n1.0\n1.5\n\n\n2\n9\n2.0\n2.5\n\n\n\nBoth subgrid elements use the water level of the basin with node_id 9 to interpolate to their respective water levels. The first element has a one to one connection with the water level; the second also has a one to one connection, but is offset by half a meter. A basin water level of 0.3 would be translated to a water level of 0.3 for the first subgrid element, and 0.8 for the second. Water levels beyond the last basin_level are linearly extrapolated.\nNote that the interpolation to subgrid water level is not constrained by any water balance within Ribasim. Generally, to create physically meaningful subgrid water levels, the subgrid table must be parametrized properly such that the spatially integrated water volume of the subgrid elements agrees with the total storage volume of the basin."
},
{
- "objectID": "python/reference/Edge.html#parameters",
- "href": "python/reference/Edge.html#parameters",
- "title": "1 Edge",
- "section": "",
- "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstatic\npandas.DataFrame\nTable describing the flow connections.\nrequired"
- },
- {
- "objectID": "python/examples.html",
- "href": "python/examples.html",
- "title": "Examples",
- "section": "",
- "text": "1 Basic model with static forcing\n\nfrom pathlib import Path\n\nimport geopandas as gpd\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport ribasim\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1, 3, 3, 6, 6, 9, 9],\n \"area\": [0.01, 1000.0] * 4,\n \"level\": [0.0, 1.0] * 4,\n }\n)\n\n# Convert steady forcing to m/s\n# 2 mm/d precipitation, 1 mm/d evaporation\nseconds_in_day = 24 * 3600\nprecipitation = 0.002 / seconds_in_day\nevaporation = 0.001 / seconds_in_day\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [0],\n \"drainage\": [0.0],\n \"potential_evaporation\": [evaporation],\n \"infiltration\": [0.0],\n \"precipitation\": [precipitation],\n \"urban_runoff\": [0.0],\n }\n)\nstatic = static.iloc[[0, 0, 0, 0]]\nstatic[\"node_id\"] = [1, 3, 6, 9]\n\nbasin = ribasim.Basin(profile=profile, static=static)\n\nSetup linear resistance:\n\nlinear_resistance = ribasim.LinearResistance(\n static=pd.DataFrame(\n data={\"node_id\": [10, 12], \"resistance\": [5e3, (3600.0 * 24) / 100.0]}\n )\n)\n\nSetup Manning resistance:\n\nmanning_resistance = ribasim.ManningResistance(\n static=pd.DataFrame(\n data={\n \"node_id\": [2],\n \"length\": [900.0],\n \"manning_n\": [0.04],\n \"profile_width\": [6.0],\n \"profile_slope\": [3.0],\n }\n )\n)\n\nSet up a rating curve node:\n\n# Discharge: lose 1% of storage volume per day at storage = 1000.0.\nq1000 = 1000.0 * 0.01 / seconds_in_day\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\n \"node_id\": [4, 4],\n \"level\": [0.0, 1.0],\n \"discharge\": [0.0, q1000],\n }\n )\n)\n\nSetup fractional flows:\n\nfractional_flow = ribasim.FractionalFlow(\n static=pd.DataFrame(\n data={\n \"node_id\": [5, 8, 13],\n \"fraction\": [0.3, 0.6, 0.1],\n }\n )\n)\n\nSetup pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [7],\n \"flow_rate\": [0.5 / 3600],\n }\n )\n)\n\nSetup level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [11, 17],\n \"level\": [0.5, 1.5],\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [15, 16],\n \"flow_rate\": [1e-4, 1e-4],\n }\n )\n)\n\nSetup terminal:\n\nterminal = ribasim.Terminal(\n static=pd.DataFrame(\n data={\n \"node_id\": [14],\n }\n )\n)\n\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin,\n (1.0, 0.0), # 2: ManningResistance\n (2.0, 0.0), # 3: Basin\n (3.0, 0.0), # 4: TabulatedRatingCurve\n (3.0, 1.0), # 5: FractionalFlow\n (3.0, 2.0), # 6: Basin\n (4.0, 1.0), # 7: Pump\n (4.0, 0.0), # 8: FractionalFlow\n (5.0, 0.0), # 9: Basin\n (6.0, 0.0), # 10: LinearResistance\n (2.0, 2.0), # 11: LevelBoundary\n (2.0, 1.0), # 12: LinearResistance\n (3.0, -1.0), # 13: FractionalFlow\n (3.0, -2.0), # 14: Terminal\n (3.0, 3.0), # 15: FlowBoundary\n (0.0, 1.0), # 16: FlowBoundary\n (6.0, 1.0), # 17: LevelBoundary\n ]\n)\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_id, node_type = ribasim.Node.node_ids_and_types(\n basin,\n manning_resistance,\n rating_curve,\n pump,\n fractional_flow,\n linear_resistance,\n level_boundary,\n flow_boundary,\n terminal,\n)\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(node_id, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array(\n [1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 11, 12, 4, 13, 15, 16, 10], dtype=np.int64\n)\nto_id = np.array(\n [2, 3, 4, 5, 8, 6, 7, 9, 9, 10, 12, 3, 13, 14, 6, 1, 17], dtype=np.int64\n)\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": len(from_id) * [\"flow\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n level_boundary=level_boundary,\n flow_boundary=flow_boundary,\n pump=pump,\n linear_resistance=linear_resistance,\n manning_resistance=manning_resistance,\n tabulated_rating_curve=rating_curve,\n fractional_flow=fractional_flow,\n terminal=terminal,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"basic/ribasim.toml\")\n\nPosixPath('data/basic/ribasim.toml')\n\n\n\n\n2 Update the basic model with transient forcing\nThis assumes you have already created the basic model with static forcing.\n\nimport numpy as np\nimport pandas as pd\nimport ribasim\nimport xarray as xr\n\n\nmodel = ribasim.Model(filepath=datadir / \"basic/ribasim.toml\")\n\n\ntime = pd.date_range(model.starttime, model.endtime)\nday_of_year = time.day_of_year.to_numpy()\nseconds_per_day = 24 * 60 * 60\nevaporation = (\n (-1.0 * np.cos(day_of_year / 365.0 * 2 * np.pi) + 1.0) * 0.0025 / seconds_per_day\n)\nrng = np.random.default_rng(seed=0)\nprecipitation = (\n rng.lognormal(mean=-1.0, sigma=1.7, size=time.size) * 0.001 / seconds_per_day\n)\n\nWe’ll use xarray to easily broadcast the values.\n\ntimeseries = (\n pd.DataFrame(\n data={\n \"node_id\": 1,\n \"time\": pd.date_range(model.starttime, model.endtime),\n \"drainage\": 0.0,\n \"potential_evaporation\": evaporation,\n \"infiltration\": 0.0,\n \"precipitation\": precipitation,\n \"urban_runoff\": 0.0,\n }\n )\n .set_index(\"time\")\n .to_xarray()\n)\n\nbasin_ids = model.basin.static.df[\"node_id\"].to_numpy()\nbasin_nodes = xr.DataArray(\n np.ones(len(basin_ids)), coords={\"node_id\": basin_ids}, dims=[\"node_id\"]\n)\nforcing = (timeseries * basin_nodes).to_dataframe().reset_index()\n\n\nstate = pd.DataFrame(\n data={\n \"node_id\": basin_ids,\n \"level\": 1.4,\n }\n)\n\n\nmodel.basin.time.df = forcing\nmodel.basin.state.df = state\n\n\nmodel.write(datadir / \"basic_transient/ribasim.toml\")\n\nPosixPath('data/basic_transient/ribasim.toml')\n\n\nNow run the model with ribasim basic_transient/ribasim.toml. After running the model, read back the results:\n\ndf_basin = pd.read_feather(datadir / \"basic_transient/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\ndf_basin_wide[\"level\"].plot()\n\n<Axes: xlabel='time'>\n\n\n\n\n\n\ndf_flow = pd.read_feather(datadir / \"basic_transient/results/flow.arrow\")\ndf_flow[\"edge\"] = list(zip(df_flow.from_node_id, df_flow.to_node_id))\ndf_flow[\"flow_m3d\"] = df_flow.flow * 86400\nax = df_flow.pivot_table(index=\"time\", columns=\"edge\", values=\"flow_m3d\").plot()\nax.legend(bbox_to_anchor=(1.3, 1), title=\"Edge\")\n\n<matplotlib.legend.Legend at 0x7f2fe8083a50>\n\n\n\n\n\n\ntype(df_flow)\n\npandas.core.frame.DataFrame\n\n\n\n\n3 Model with discrete control\nThe model constructed below consists of a single basin which slowly drains trough a TabulatedRatingCurve, but is held within a range around a target level (setpoint) by two connected pumps. These two pumps behave like a reversible pump. When pumping can be done in only one direction, and the other direction is only possible under gravity, use an Outlet for that direction.\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: Basin\n (1.0, 1.0), # 2: Pump\n (1.0, -1.0), # 3: Pump\n (2.0, 0.0), # 4: LevelBoundary\n (-1.0, 0.0), # 5: TabulatedRatingCurve\n (-2.0, 0.0), # 6: Terminal\n (1.0, 0.0), # 7: DiscreteControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"Basin\",\n \"Pump\",\n \"Pump\",\n \"LevelBoundary\",\n \"TabulatedRatingCurve\",\n \"Terminal\",\n \"DiscreteControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 3, 4, 2, 1, 5, 7, 7], dtype=np.int64)\nto_id = np.array([3, 4, 2, 1, 5, 6, 2, 3], dtype=np.int64)\n\nedge_type = 6 * [\"flow\"] + 2 * [\"control\"]\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\"from_node_id\": from_id, \"to_node_id\": to_id, \"edge_type\": edge_type},\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\n \"node_id\": [1, 1],\n \"area\": [1000.0, 1000.0],\n \"level\": [0.0, 1.0],\n }\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [1],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(data={\"node_id\": [1], \"level\": [20.0]})\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the discrete control:\n\ncondition = pd.DataFrame(\n data={\n \"node_id\": 3 * [7],\n \"listen_feature_id\": 3 * [1],\n \"variable\": 3 * [\"level\"],\n \"greater_than\": [5.0, 10.0, 15.0], # min, setpoint, max\n }\n)\n\nlogic = pd.DataFrame(\n data={\n \"node_id\": 5 * [7],\n \"truth_state\": [\"FFF\", \"U**\", \"T*F\", \"**D\", \"TTT\"],\n \"control_state\": [\"in\", \"in\", \"none\", \"out\", \"out\"],\n }\n)\n\ndiscrete_control = ribasim.DiscreteControl(condition=condition, logic=logic)\n\nThe above control logic can be summarized as follows: - If the level gets above the maximum, activate the control state “out” until the setpoint is reached; - If the level gets below the minimum, active the control state “in” until the setpoint is reached; - Otherwise activate the control state “none”.\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": 3 * [2] + 3 * [3],\n \"control_state\": 2 * [\"none\", \"in\", \"out\"],\n \"flow_rate\": [0.0, 2e-3, 0.0, 0.0, 0.0, 2e-3],\n }\n )\n)\n\nThe pump data defines the following:\n\n\n\nControl state\nPump #2 flow rate (m/s)\nPump #3 flow rate (m/s)\n\n\n\n\n“none”\n0.0\n0.0\n\n\n“in”\n2e-3\n0.0\n\n\n“out”\n0.0\n2e-3\n\n\n\nSetup the level boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(data={\"node_id\": [4], \"level\": [10.0]})\n)\n\nSetup the rating curve:\n\nrating_curve = ribasim.TabulatedRatingCurve(\n static=pd.DataFrame(\n data={\"node_id\": 2 * [5], \"level\": [2.0, 15.0], \"discharge\": [0.0, 1e-3]}\n )\n)\n\nSetup the terminal:\n\nterminal = ribasim.Terminal(static=pd.DataFrame(data={\"node_id\": [6]}))\n\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n pump=pump,\n level_boundary=level_boundary,\n tabulated_rating_curve=rating_curve,\n terminal=terminal,\n discrete_control=discrete_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2021-01-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nListen edges are plotted with a dashed line since they are not present in the “Edge / static” schema but only in the “Control / condition” schema.\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"level_setpoint_with_minmax/ribasim.toml\")\n\nPosixPath('data/level_setpoint_with_minmax/ribasim.toml')\n\n\nNow run the model with level_setpoint_with_minmax/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"level_setpoint_with_minmax/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\n\nax = df_basin_wide[\"level\"].plot()\n\ngreater_than = model.discrete_control.condition.df.greater_than\n\nax.hlines(\n greater_than,\n df_basin.time[0],\n df_basin.time.max(),\n lw=1,\n ls=\"--\",\n color=\"k\",\n)\n\ndf_control = pd.read_feather(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\ny_min, y_max = ax.get_ybound()\nax.fill_between(df_control.time[:2], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\nax.fill_between(df_control.time[2:4], 2 * [y_min], 2 * [y_max], alpha=0.2, color=\"C0\")\n\nax.set_xticks(\n date2num(df_control.time).tolist(),\n df_control.control_state.tolist(),\n rotation=50,\n)\n\nax.set_yticks(greater_than, [\"min\", \"setpoint\", \"max\"])\nax.set_ylabel(\"level\")\nplt.show()\n\n\n\n\nThe highlighted regions show where a pump is active.\nLet’s print an overview of what happened with control:\n\nmodel.print_discrete_control_record(\n datadir / \"level_setpoint_with_minmax/results/control.arrow\"\n)\n\n0. At 2020-01-01 00:00:00 the control node with ID 7 reached truth state TTT:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level > 15.0\n\n This yielded control state \"out\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.002\n\n1. At 2020-02-08 19:02:21.861000 the control node with ID 7 reached truth state TFF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n2. At 2020-07-05 08:56:10.319000 the control node with ID 7 reached truth state FFF:\n For node ID 1 (Basin): level < 5.0\n For node ID 1 (Basin): level < 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"in\":\n For node ID 2 (Pump): flow_rate = 0.002\n For node ID 3 (Pump): flow_rate = 0.0\n\n3. At 2020-08-11 06:05:15.592000 the control node with ID 7 reached truth state TTF:\n For node ID 1 (Basin): level > 5.0\n For node ID 1 (Basin): level > 10.0\n For node ID 1 (Basin): level < 15.0\n\n This yielded control state \"none\":\n For node ID 2 (Pump): flow_rate = 0.0\n For node ID 3 (Pump): flow_rate = 0.0\n\n\n\nNote that crossing direction specific truth states (containing “U”, “D”) are not present in this overview even though they are part of the control logic. This is because in the control logic for this model these truth states are only used to sustain control states, while the overview only shows changes in control states.\n\n\n4 Model with PID control\nSet up the nodes:\n\nxy = np.array(\n [\n (0.0, 0.0), # 1: FlowBoundary\n (1.0, 0.0), # 2: Basin\n (2.0, 0.5), # 3: Pump\n (3.0, 0.0), # 4: LevelBoundary\n (1.5, 1.0), # 5: PidControl\n (2.0, -0.5), # 6: outlet\n (1.5, -1.0), # 7: PidControl\n ]\n)\n\nnode_xy = gpd.points_from_xy(x=xy[:, 0], y=xy[:, 1])\n\nnode_type = [\n \"FlowBoundary\",\n \"Basin\",\n \"Pump\",\n \"LevelBoundary\",\n \"PidControl\",\n \"Outlet\",\n \"PidControl\",\n]\n\n# Make sure the feature id starts at 1: explicitly give an index.\nnode = ribasim.Node(\n df=gpd.GeoDataFrame(\n data={\"type\": node_type},\n index=pd.Index(np.arange(len(xy)) + 1, name=\"fid\"),\n geometry=node_xy,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the edges:\n\nfrom_id = np.array([1, 2, 3, 4, 6, 5, 7], dtype=np.int64)\nto_id = np.array([2, 3, 4, 6, 2, 3, 6], dtype=np.int64)\n\nlines = node.geometry_from_connectivity(from_id, to_id)\nedge = ribasim.Edge(\n df=gpd.GeoDataFrame(\n data={\n \"from_node_id\": from_id,\n \"to_node_id\": to_id,\n \"edge_type\": 5 * [\"flow\"] + 2 * [\"control\"],\n },\n geometry=lines,\n crs=\"EPSG:28992\",\n )\n)\n\nSetup the basins:\n\nprofile = pd.DataFrame(\n data={\"node_id\": [2, 2], \"level\": [0.0, 1.0], \"area\": [1000.0, 1000.0]}\n)\n\nstatic = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"drainage\": [0.0],\n \"potential_evaporation\": [0.0],\n \"infiltration\": [0.0],\n \"precipitation\": [0.0],\n \"urban_runoff\": [0.0],\n }\n)\n\nstate = pd.DataFrame(\n data={\n \"node_id\": [2],\n \"level\": [6.0],\n }\n)\n\nbasin = ribasim.Basin(profile=profile, static=static, state=state)\n\nSetup the pump:\n\npump = ribasim.Pump(\n static=pd.DataFrame(\n data={\n \"node_id\": [3],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup the outlet:\n\noutlet = ribasim.Outlet(\n static=pd.DataFrame(\n data={\n \"node_id\": [6],\n \"flow_rate\": [0.0], # Will be overwritten by PID controller\n }\n )\n)\n\nSetup flow boundary:\n\nflow_boundary = ribasim.FlowBoundary(\n static=pd.DataFrame(data={\"node_id\": [1], \"flow_rate\": [1e-3]})\n)\n\nSetup flow boundary:\n\nlevel_boundary = ribasim.LevelBoundary(\n static=pd.DataFrame(\n data={\n \"node_id\": [4],\n \"level\": [1.0], # Not relevant\n }\n )\n)\n\nSetup PID control:\n\npid_control = ribasim.PidControl(\n time=pd.DataFrame(\n data={\n \"node_id\": 4 * [5, 7],\n \"time\": [\n \"2020-01-01 00:00:00\",\n \"2020-01-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-05-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-07-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n \"2020-12-01 00:00:00\",\n ],\n \"listen_node_id\": 4 * [2, 2],\n \"target\": [5.0, 5.0, 5.0, 5.0, 7.5, 7.5, 7.5, 7.5],\n \"proportional\": 4 * [-1e-3, 1e-3],\n \"integral\": 4 * [-1e-7, 1e-7],\n \"derivative\": 4 * [0.0, 0.0],\n }\n )\n)\n\nNote that the coefficients for the pump and the outlet are equal in magnitude but opposite in sign. This way the pump and the outlet equally work towards the same goal, while having opposite effects on the controlled basin due to their connectivity to this basin.\nSetup a model:\n\nmodel = ribasim.Model(\n network=ribasim.Network(\n node=node,\n edge=edge,\n ),\n basin=basin,\n flow_boundary=flow_boundary,\n level_boundary=level_boundary,\n pump=pump,\n outlet=outlet,\n pid_control=pid_control,\n starttime=\"2020-01-01 00:00:00\",\n endtime=\"2020-12-01 00:00:00\",\n)\n\nLet’s take a look at the model:\n\nmodel.plot()\n\n<Axes: >\n\n\n\n\n\nWrite the model to a TOML and GeoPackage:\n\ndatadir = Path(\"data\")\nmodel.write(datadir / \"pid_control/ribasim.toml\")\n\nPosixPath('data/pid_control/ribasim.toml')\n\n\nNow run the model with ribasim pid_control/ribasim.toml. After running the model, read back the results:\n\nfrom matplotlib.dates import date2num\n\ndf_basin = pd.read_feather(datadir / \"pid_control/results/basin.arrow\")\ndf_basin_wide = df_basin.pivot_table(\n index=\"time\", columns=\"node_id\", values=[\"storage\", \"level\"]\n)\nax = df_basin_wide[\"level\"].plot()\nax.set_ylabel(\"level [m]\")\n\n# Plot target level\ntarget_levels = model.pid_control.time.df.target.to_numpy()[:4]\ntimes = date2num(model.pid_control.time.df.time)[:4]\nax.plot(times, target_levels, color=\"k\", ls=\":\", label=\"target level\")\npass"
- },
- {
- "objectID": "qgis/index.html",
- "href": "qgis/index.html",
- "title": "QGIS plugin",
- "section": "",
- "text": "Install QGIS version 3.28 or higher.\n\n\nDownload ribasim_qgis.zip, see the download section.\nPlugins menu > Manage and Install Plugins…\n\n\n\n\n\nSelect “Install from ZIP”:\n\nBrowse to the ribasim_qgis.zip file containing the plugin that was downloaded earlier\nClick “Install Plugin”\n\n\n\n\n\n\nStart the Ribasim plugin.\n\n\n\n\n\n\n\n\nIn QGIS, navigate to “Plugins > Manage and Install Plugins > All”. In the search bar, type: “iMOD”. Select the iMOD plugin, and click “Install”.\nAt least version 0.4.0 of the iMOD plugin is required.\nThe Time Series widget from the iMOD plugin is used for visualizing Ribasim results, which is described in Section 1.5. Documentation on the Time Series widget can be found in the iMOD documentation.\n\n\n\nOpen an existing model or create a new model. As an example of an existing model, you can use the “basic” model from generated_testmodels.zip, see the download section.\n\n\n\n\n\nCheck if your coordinate reference system (CRS) is set correctly.\n\n\n\n\n\nIf you are working with an unknown CRS, right click the model database group in Layers, and click “Set Group CRS…”.\n\n\n\n\n\nIf you are modeling the Netherlands, select “Amersfoort / RD New” (EPSG:28992).\n\n\n\n\n\n\n\n\n\n\n\nSelect the Node layer.\n\n\n\n\n\nTurn on the edit mode to be able to add nodes on the map.\n\n\n\n\n\nAdd nodes to the map with a left click and select the node type.\n\n\n\n\n\nTurn the edit mode off and save the edits to the Nodes layer.\n\n\n\n\n\n\n\n\nRight click a layer and select “Open Attribute Table”.\n\n\n\n\n\nClick the yellow pencil icon on the top left to enable editing, and copy and paste a record. A record can be selected by clicking on the row number.\n\n\n\n\n\nAdjust the content. If you prefer, it also works to copy data with the same columns from Excel. Turn off edit mode and save changes to the layer.\n\n\n\n\n\n\n\n\n\n\n\nMake sure the Snapping Toolbar is visible, by going to the View > Toolbars menu. Turn on snapping mode by clicking the magnet and set the snapping distance to 25 pixels.\n\n\n\n\n\n\n\n\nSelect the Edge layer and turn on the edit mode.\n\n\n\n\n\nSelect “Add line feature”.\n\n\n\n\n\nCreate a connection by left clicking a source node and right clicking the destination node.\n\n\n\n\n\nNow leave the edit mode and save the results to the layer.\n\n\n\n\n\nUnzip the Ribasim command line interface, ribasim_cli.zip\nOpen your terminal and go to the directory where your TOML is stored. Now run path/to/ribasim_cli/ribasim ribasim.toml. Adjust the path to the ribasim_cli folder to where you placed it. This runs the model.\nIn your model directory there is now a results/ folder with basin.arrow and flow.arrow output files.\n\n\n\n\nBefore trying to inspect the results, verify that the run was successful and the output files are there.\nClick the “Time Series” button of the iMOD plugin.\n\n\n\n\n\nSelect the layer that you wish to plot. From the “Node” layer you can plot level or storage on Basin nodes. From the “Edge” layer you can plot flow over flow edges. Note that before switching between these, you typically have to click “Clear” to clear the selection. If you run a simulation with the model open in QGIS, you have to close and re-open the “iMOD Time Series Plot” panel for the new results to be loaded.\nSelect the variables that you want to plot.\n\n\n\n\n\nClick “Select points” and select a node by dragging a rectangle around it on the map. Hold the Ctrl key to select multiple nodes.\n\n\n\n\n\nThe associated time series are shown the the graph.\n\n\n\n\n\nOnly the “basin.arrow” and “flow.arrow” can be inspected with the “iMOD Time Series Plot” panel. All Arrow files can be loaded as a layer by dragging the files onto QGIS. Right click the layer and select “Open Attribute Table” to view the contents."
- },
- {
- "objectID": "qgis/index.html#start",
- "href": "qgis/index.html#start",
- "title": "QGIS plugin",
- "section": "",
- "text": "Install QGIS version 3.28 or higher.\n\n\nDownload ribasim_qgis.zip, see the download section.\nPlugins menu > Manage and Install Plugins…\n\n\n\n\n\nSelect “Install from ZIP”:\n\nBrowse to the ribasim_qgis.zip file containing the plugin that was downloaded earlier\nClick “Install Plugin”\n\n\n\n\n\n\nStart the Ribasim plugin.\n\n\n\n\n\n\n\n\nIn QGIS, navigate to “Plugins > Manage and Install Plugins > All”. In the search bar, type: “iMOD”. Select the iMOD plugin, and click “Install”.\nAt least version 0.4.0 of the iMOD plugin is required.\nThe Time Series widget from the iMOD plugin is used for visualizing Ribasim results, which is described in Section 1.5. Documentation on the Time Series widget can be found in the iMOD documentation.\n\n\n\nOpen an existing model or create a new model. As an example of an existing model, you can use the “basic” model from generated_testmodels.zip, see the download section.\n\n\n\n\n\nCheck if your coordinate reference system (CRS) is set correctly.\n\n\n\n\n\nIf you are working with an unknown CRS, right click the model database group in Layers, and click “Set Group CRS…”.\n\n\n\n\n\nIf you are modeling the Netherlands, select “Amersfoort / RD New” (EPSG:28992)."
- },
- {
- "objectID": "qgis/index.html#edit-nodes",
- "href": "qgis/index.html#edit-nodes",
- "title": "QGIS plugin",
- "section": "",
- "text": "Select the Node layer.\n\n\n\n\n\nTurn on the edit mode to be able to add nodes on the map.\n\n\n\n\n\nAdd nodes to the map with a left click and select the node type.\n\n\n\n\n\nTurn the edit mode off and save the edits to the Nodes layer.\n\n\n\n\n\n\n\n\nRight click a layer and select “Open Attribute Table”.\n\n\n\n\n\nClick the yellow pencil icon on the top left to enable editing, and copy and paste a record. A record can be selected by clicking on the row number.\n\n\n\n\n\nAdjust the content. If you prefer, it also works to copy data with the same columns from Excel. Turn off edit mode and save changes to the layer."
- },
- {
- "objectID": "qgis/index.html#connect-nodes",
- "href": "qgis/index.html#connect-nodes",
- "title": "QGIS plugin",
- "section": "",
- "text": "Make sure the Snapping Toolbar is visible, by going to the View > Toolbars menu. Turn on snapping mode by clicking the magnet and set the snapping distance to 25 pixels.\n\n\n\n\n\n\n\n\nSelect the Edge layer and turn on the edit mode.\n\n\n\n\n\nSelect “Add line feature”.\n\n\n\n\n\nCreate a connection by left clicking a source node and right clicking the destination node.\n\n\n\n\n\nNow leave the edit mode and save the results to the layer."
- },
- {
- "objectID": "qgis/index.html#run-a-model",
- "href": "qgis/index.html#run-a-model",
- "title": "QGIS plugin",
- "section": "",
- "text": "Unzip the Ribasim command line interface, ribasim_cli.zip\nOpen your terminal and go to the directory where your TOML is stored. Now run path/to/ribasim_cli/ribasim ribasim.toml. Adjust the path to the ribasim_cli folder to where you placed it. This runs the model.\nIn your model directory there is now a results/ folder with basin.arrow and flow.arrow output files."
- },
- {
- "objectID": "qgis/index.html#sec-results",
- "href": "qgis/index.html#sec-results",
- "title": "QGIS plugin",
- "section": "",
- "text": "Before trying to inspect the results, verify that the run was successful and the output files are there.\nClick the “Time Series” button of the iMOD plugin.\n\n\n\n\n\nSelect the layer that you wish to plot. From the “Node” layer you can plot level or storage on Basin nodes. From the “Edge” layer you can plot flow over flow edges. Note that before switching between these, you typically have to click “Clear” to clear the selection. If you run a simulation with the model open in QGIS, you have to close and re-open the “iMOD Time Series Plot” panel for the new results to be loaded.\nSelect the variables that you want to plot.\n\n\n\n\n\nClick “Select points” and select a node by dragging a rectangle around it on the map. Hold the Ctrl key to select multiple nodes.\n\n\n\n\n\nThe associated time series are shown the the graph.\n\n\n\n\n\nOnly the “basin.arrow” and “flow.arrow” can be inspected with the “iMOD Time Series Plot” panel. All Arrow files can be loaded as a layer by dragging the files onto QGIS. Right click the layer and select “Open Attribute Table” to view the contents."
- },
- {
- "objectID": "couple/modflow-demo.html",
- "href": "couple/modflow-demo.html",
- "title": "MODFLOW 6 Demonstration",
- "section": "",
- "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system."
- },
- {
- "objectID": "couple/modflow-demo.html#example-parametrization",
- "href": "couple/modflow-demo.html#example-parametrization",
- "title": "MODFLOW 6 Demonstration",
- "section": "",
- "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system."
- },
- {
- "objectID": "couple/modflow-demo.html#example-configuration",
- "href": "couple/modflow-demo.html#example-configuration",
- "title": "MODFLOW 6 Demonstration",
- "section": "2 Example: Configuration",
- "text": "2 Example: Configuration\nAn example of the MODFLOW 6 section of TOML configuration required for a coupled run can be seen below:\n[modflow]\nsimulation = \"../data/hupsel/mfsim.nam\"\nmode = \"sequential\"\ntimestep = 86400.0\n\n[modflow.models]\n\n[modflow.models.gwf]\ntype = \"gwf\"\ndataset = \"../data/volume_level_profile-hupsel.nc\"\nbasins = \"basin_id\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_P\"\ndrain = \"DRN_P\"\nprofile = \"profile_primary\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_S\"\ndrain = \"DRN_S\"\nprofile = \"profile_secondary\"\n\n[[modflow.models.gwf.bounds]]\ndrain = \"DRN_T\"\nprofile = \"profile_tertiary\"\nThe section starts by stating the path to the MODFLOW 6 simulation name file (simulation). The next section contains the information regarding the MODFLOW 6 model(s) to couple to Ribasim’s basins. Per model, a path to the coupling parameter dataset is provided (dataset), along with the variable to use as the basin identification number (basins). Next, for every boundary condition that should be coupled to Ribasim, the package names (river, drain) used by MODFLOW 6 must be specified (as we look for these names in the MODFLOW 6 memory), along with the name of the variable in the coupling dataset which provides the volume-level relationship (profile).\nAs can be seen in the example, the coupling mechanism supports coupling of:\n\nA single drainage package (drain entry).\nA single river package (river entry).\nA combination of river and drainage package (when infiltration conductance does not equal drainage conductance) both (river and drain entry)."
- },
- {
- "objectID": "couple/modflow-demo.html#test-case-hupsel",
- "href": "couple/modflow-demo.html#test-case-hupsel",
- "title": "MODFLOW 6 Demonstration",
- "section": "3 Test case: Hupsel",
- "text": "3 Test case: Hupsel\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\nFrom these tests, we expect the following behavior:\n\nDrainage terms should remain largely the same when the water level is lowered in a steady-state: the amount of recharge is fixed, and this is locally drained.\nIn case of negative recharge (evapotranspiration), infiltration occurs in the surface waters. Infiltration should be zero when the basin volume is 0.\n\n\n\n\nFigure 5: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 5 shows the water balance of steady-state for submodel of the LHM that has been by selecting the cells belonging to the district containing the Hupsel catch, the Berkel.\nThe Hupsel basin show the expected behavior: for a net groundwater recharge of 1.0 mm/d, most is precipitation with a minor part lateral inflow from higher areas. In terms of outgoing flows, most of the precipitation ends up in the surface water, primarily in the many ditches of the tertiary system. Only a relatively small part of the water leaves the basin via the groundwater. Interbasin flows through the groundwater form such a minor role, as the aquifer is thin and transmissivity is limited.\nReducing groundwater recharge to 0.5 mm/d reduces all flows, with the tertiary system playing a less dominant role, relatively speaking: as its elevation is the highest compared to the primary and secondary system, the head difference is reduced strongest for the tertiary system.\nWith evapotranspiration (ET) excess (-0.05 and 0.1 mm/d; low values are chosen here since most ET would be drawn from storage, which is not available in a steady-state model), the surface waters provide mostly inflow, and recharge is a negative term. In this case, the secondary system provides a small amount of infiltration; most of the water is drawn from the surroundings instead.\n\n\n\nFigure 6: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 6 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. Consequently, primary and secondary outflow terms are larger for positive groundwater recharge as they drain at a lower level and intercept the water before the tertiary system does. Secondly, with negative groundwater recharge, no infiltration occurs and the water is drawn from the surroundings instead. This shows the coupling mechanism adjusting MODFLOW 6 water levels successfully."
- },
- {
- "objectID": "couple/modflow-demo.html#test-case-de-tol",
- "href": "couple/modflow-demo.html#test-case-de-tol",
- "title": "MODFLOW 6 Demonstration",
- "section": "4 Test case: de Tol",
- "text": "4 Test case: de Tol\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\n\n\n\nFigure 7: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 7 shows the water balance of steady-state for a submodel of the LHM for the Polder de Tol and its surroundings. While groundwater recharge is the dominant ingoing flow, lateral groundwater flow (over the entire depth of the groundwater model) is a sizable inflow for the area; the larger lateral inflow shows that De Tol is a net receiver of groundwater which is mostly discharged through the secondary system. In contrast to the Hupsel, the tertiary system is almost entirely absent: drainage occurs not through ephemeral tertiary ditches, but by the permanently water-bearing ditches of the primary and secondary system. Unlike the Hupsel, the water balance does not shrink to very small discharges, as there is sizable regional groundwater flow.\n\n\n\nFigure 8: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 8 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. The total discharge is larger: the primary and secondary systems are set to lower levels, and so the head difference is larger. While De Tol’s evapotranspiration excess can be fed by the regional groundwater, the primary and secondary ditches also provide some part; as expected, they do not feed when the coupling mechanism adjusts MODFLOW 6’s water levels."
- },
- {
- "objectID": "couple/index.html",
- "href": "couple/index.html",
- "title": "Coupled models",
- "section": "",
- "text": "Ribasim can be coupled to other software, for instance to model another process or domain, or to control a simulation from another process.\nTo enable this, Ribasim can be compiled as a shared library (libribasim) instead of a command line interface (ribasim). This shared library exposes a C API in the form of the Basic Model Interface (BMI). Other software can then load libribasim and load a Ribasim model, exchange data, and control the time stepping.\nAn initial coupling to MODFLOW 6 was done in 2022 inside the Julia core as a proof of concept. Read about the coupling setup and see the demonstration. Going forward this and other coupling codes will be implemented outside of the core, by making use of iMOD Coupler and libribasim. iMOD Coupler is a generic coupling tool, and can be used to couple to other models as well."
- },
- {
- "objectID": "couple/modflow.html",
- "href": "couple/modflow.html",
- "title": "MODFLOW 6",
- "section": "",
- "text": "Ribasim has been designed to provide a computationally efficient representation of surface water for MODFLOW 6. It does so by connecting to basic MODFLOW 6 boundary conditions: the river and drainage packages.\nRibasim connects to MODFLOW 6 via the Basic Model Interface (BMI) and Extended Model Interface (XMI) (Hughes et al. 2022). BMI describes the interface to initialize a model, get values from its memory, run a timestep, etc. XMI extends this interface to allow much finer control into MODFLOW 6’s solution procedures. We have written a Julia package (Deltares 2022) which implements this interface for the Julia programming language. In coupling, Ribasim uses this interface to get the head values, the boundary condition water levels, and the budgets term of the MODFLOW 6 groundwater model while MODFLOW 6 is running.\nAdditionally, links can be made with other (BMI/XMI-compliant) processes and models. One example of such a link is using the surface runoff and the agricultural irrigation demand calculated by an unsaturated zone model; in the Netherlands Hydrological Instrument MetaSWAP provides this demand."
- },
- {
- "objectID": "couple/modflow.html#drainage",
- "href": "couple/modflow.html#drainage",
- "title": "MODFLOW 6",
- "section": "1.1 Drainage",
- "text": "1.1 Drainage\nThe drainage package can be simulated to agricultural drains, ditches, or draining streams. The amount of water removed from the aquifer is proportional to the difference between the groundwater head and the drainage elevation. Drainage only occurs when the head is larger than the elevation; this boundary condition does not allow infiltration into the groundwater.\n\\[\nQ_{drain} = \\left\\{\n \\begin{array}{ c l }\n C_{drain} (\\phi - h_{drain}) & \\quad \\textrm{if } \\phi > h_{drain} \\\\\n 0 & \\quad \\textrm{otherwise}\n \\end{array}\n\\right.\n\\]"
- },
- {
- "objectID": "couple/modflow.html#river",
- "href": "couple/modflow.html#river",
- "title": "MODFLOW 6",
- "section": "1.2 River",
- "text": "1.2 River\nThe river package can both drain the groundwater, or infiltrate surface water to the groundwater. It limits the amount of water that can infiltrate when the groundwater head falls below the river bottom, in which cases it assumes atmospheric pressure conditions underneath the surface water.\n\\[\nQ_{river} = \\left\\{\n \\begin{array}{ c l }\n C_{river} (\\phi - h_{river}) & \\quad \\textrm{if } \\phi > b_{river} \\\\\n C_{river} (h_{river} - b_{river}) & \\quad \\textrm{if } \\phi <= b_{river}\n \\end{array}\n\\right.\n\\]\nIn the Netherlands, it is somewhat common to make a distinction between the drainage and infiltration conductance of surface waters. Drainage conductance is often larger than the infiltration conductance due to clogging processes, seepage through sides of the ditches, less contracted flow lines, etc.\n\\[\nQ_{river} = \\left\\{\n \\begin{array}{ c l }\n C_{river,drn} (\\phi - h_{river}) & \\quad \\textrm{if } \\phi > h_{river} \\\\\n C_{river,inf} (\\phi - h_{river}) & \\quad \\textrm{if } \\phi <= h_{river} \\\\\n C_{river,inf} (h_{river} - b_{river}) & \\quad \\textrm{if } \\phi <= b_{river}\n \\end{array}\n\\right.\n\\]\nMODFLOW 6 does not support this (currently), but an identical effect may be achieved by “stacking” a drainage package on top of a river package with these values:\n\\[\n\\begin{array}{ c l }\n h_{drain} = h_{river} \\\\\n C_{drain} = C_{river,drn} - C_{river,inf}\n\\end{array}\n\\]"
+ "objectID": "core/usage.html#basin-results",
+ "href": "core/usage.html#basin-results",
+ "title": "Usage",
+ "section": "5.5 Basin results",
+ "text": "5.5 Basin results\nThe basin table contains results of the storage and level of each basin at every solver timestep. The initial condition is also written to the file.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nnode_id\nInt\n-\n\n\nstorage\nFloat64\n\\(m^3\\)\n\n\nlevel\nFloat64\n\\(m\\)\n\n\n\nThe table is sorted by time, and per time it is sorted by node_id."
},
{
- "objectID": "couple/modflow.html#numerical-solution-in-modflow",
- "href": "couple/modflow.html#numerical-solution-in-modflow",
- "title": "MODFLOW 6",
- "section": "2.1 Numerical solution in MODFLOW",
- "text": "2.1 Numerical solution in MODFLOW\nMODFLOW uses a backward-in-time implicit solution scheme. This creates a large system of equations (a water balance for every cell), which it solves by repeatedly solving a linearized system of equations instead. In matrix form, this system of equations is expressed by:\n\\[\n\\mathbf{Ax} = \\mathbf{b}\n\\]\nWhere \\(\\mathbf{x}\\) is a vector containing the head of every cell.\nFor the boundary conditions, this requires linearization of the flow equations. Flow from outside of the aquifer (cell) may be represented by:\n\\[\na = p\\phi + q\n\\]\n(Equation 2-6 in the MODFLOW 6 documentation (Langevin et al. 2017).)\nFor e.g. a draining boundary condition, the flow is head dependent:\n\\[\na = C(h - \\phi) = -C\\phi + Ch\n\\]\nWith \\(C\\) the conductance, \\(h\\) the boundary head or elevation, and \\(\\phi\\) the groundwater head.\nIn MODFLOW’s internal formulation, the term in \\(\\mathbf{A}\\) is called “coefficient of head” or hcof. Terms in \\(\\mathbf{b}\\) are called “right hand side” or rhs. We can separate the equation above:\n\\[\n\\begin{aligned}\np = \\text{hcof} = -C \\\\\nq = \\textrm{rhs} = -Ch \\\\\na = -C\\phi + Ch = \\text{hcof} * \\phi - \\text{rhs}\n\\end{aligned}\n\\]\nFor every boundary condition, MODFLOW 6 stores the hcof and rhs values. This makes it quite convenient for us to compute the water budget for every boundary condition: we simply multiply the hcof value by the head of the cell and subtract the rhs.\nNote that hcof may have a value of 0! For example, when for a river boundary the \\(\\phi <= b_{river}\\) condition occurs, the flow into the cell is controlled only by \\(h_{river}\\) and \\(b_{river}\\) (equal to recharge for the linear solution)."
+ "objectID": "core/usage.html#flow-results",
+ "href": "core/usage.html#flow-results",
+ "title": "Usage",
+ "section": "5.6 Flow results",
+ "text": "5.6 Flow results\nThe flow table contains results of the flow on every edge in the model, for each solver timestep.\n\n\n\ncolumn\ntype\nunit\n\n\n\n\ntime\nDateTime\n-\n\n\nedge_id\nUnion{Int, Missing}\n-\n\n\nfrom_node_id\nInt\n-\n\n\nto_node_id\nInt\n-\n\n\nflow\nFloat64\n\\(m^3 s^{-1}\\)\n\n\n\nThe table is sorted by time, and per time the same edge_id order is used, though not sorted. Flows that are added to the model at a node, have a missing edge_id, and identical from_node_id and to_node_id. Flows out of the model always have a negative sign, and additions a positive sign."
},
{
- "objectID": "couple/modflow.html#sequential-coupled-solution",
- "href": "couple/modflow.html#sequential-coupled-solution",
- "title": "MODFLOW 6",
- "section": "2.2 Sequential coupled solution",
- "text": "2.2 Sequential coupled solution\nA coupled run stars by initializing both models and creating the exchange information: which MODFLOW 6 boundary condition is connected to which Ribasim basin. While the model is running, the model proceeds through time as follows:\n\nRibasim solves the equations on a basin level; this occurs with adaptive timestepping via ModelingToolkit.\nAt a specified time (in accordance with the MODFLOW 6 time discretization), the volumes are converted to MODFLOW 6 boundary condition levels using a Callback function.\nMODFLOW 6 runs a timestep.\nThe drainage and infiltration budgets are computed from MODFLOW 6 using the equations described above and aggregated per basin.\nThe aggregated values are set as Ribasim boundary conditions, and Ribasim solves until the next preset exchange time.\n\nThese steps run until the final timestep is finished."
+ "objectID": "core/usage.html#tabulatedratingcurve-time",
+ "href": "core/usage.html#tabulatedratingcurve-time",
+ "title": "Usage",
+ "section": "7.1 TabulatedRatingCurve / time",
+ "text": "7.1 TabulatedRatingCurve / time\nThis table is the transient form of the TabulatedRatingCurve table. The only difference is that a time column is added. The table must by sorted by time, and per time it must be sorted by node_id. With this the rating curves can be updated over time. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-\n\n\ndischarge\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative"
},
{
- "objectID": "couple/modflow.html#iterative-coupled-solution",
- "href": "couple/modflow.html#iterative-coupled-solution",
- "title": "MODFLOW 6",
- "section": "2.3 Iterative coupled solution",
- "text": "2.3 Iterative coupled solution\n\n\n\n\n\n\nNote\n\n\n\nWe have not implemented an iterative coupled solution yet. The section below describes an approach.\n\n\nThe simplest form of a iterative solution occurs as follows:\n\nMODFLOW 6 computes drainage and infiltration flows.\nRibasim uses these flows to compute a storage volume for the basin.\nThe volume is translated to a water level for every MODFLOW 6 boundary condition in the basins.\nMODFLOW 6 recomputes drainage and infiltration flows with the updated water levels, etc.\n\nSuch a scheme is not maximally efficient: the discharge and infiltration terms are not constant, but are driven by a head difference. This head difference depends on the level of the boundary conditions and head of every cell of the groundwater model. If the surface waters of a basin empty, the water level will decrease and drainage and infiltration flows will change. Ideally, we can provide Ribasim with more information, so that it may estimate drainage and infiltration terms better.\nAs groundwater flow is often (approximately) linear, we can use linearization to more efficiently compute the flow from Ribasim’s side as well. In the iterative coupled solution, we are solving both MODFLOW 6 and Ribasim repeatedly, until they produce same drainage or infiltration (approximately). One of Ribasim’s basins contains many MODFLOW cells with boundary conditions. We could add every boundary condition to Ribasim’s equations, but this is costly and cumbersome. Fortunately, linearization allows us to “stack” (superpose) all the different boundary conditions into a single, simple equation. In linear form, every equation takes the form of:\n\\[\na = ph + q\n\\]\nNote the \\(h\\) rather than \\(\\phi\\), we are formulating from Ribasim’s perspective! We can sum all coefficients for p and q to provide a linear groundwater response to Ribasim.\n\n2.3.1 Drainage\nFrom Ribasim’s perspective, the groundwater head is constant given a timestep, so that:\n\\[\\begin{align}\np = -C \\\\\nq = -C\\phi\n\\end{align}\\]\nWhen the head falls below the drainage elevation, the coefficients are 0.\n\n\n2.3.2 River\nFrom Ribasim’s perspective, infiltration is never limited when the head falls below the bottom:\n\\[\\begin{align}\np = -C \\\\\nq = -Cb\n\\end{align}\\]\nOtherwise, infiltration and drainage occur with the same equation as for the drainage package:\n\\[\\begin{align}\np = -C \\\\\nq = -C\\phi\n\\end{align}\\]"
+ "objectID": "core/usage.html#user-time",
+ "href": "core/usage.html#user-time",
+ "title": "Usage",
+ "section": "10.1 User / time",
+ "text": "10.1 User / time\nThis table is the transient form of the User table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the demand can be updated over time. In between the given times the demand is interpolated linearly, and outside the demand is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntime\nDateTime\n-\nsorted per priority per node id\n\n\ndemand\nFloat64\n\\(m^3 s^{-1}\\)\n-\n\n\nreturn_factor\nFloat64\n-\nbetween [0 - 1]\n\n\nmin_level\nFloat64\n\\(m\\)\n(optional)\n\n\npriority\nInt\n-\nsorted per node id"
},
{
- "objectID": "couple/modflow.html#parametrization",
- "href": "couple/modflow.html#parametrization",
- "title": "MODFLOW 6",
- "section": "3.1 Parametrization",
- "text": "3.1 Parametrization\nIn coupling Ribasim to MODFLOW 6, relations translating the Ribasim volume must be given for every every cell of every boundary condition. These consist of piecewise linear relationships between the basin volume and its associated water level for the boundary condition in the cell.\nThese values are stored in a netCDF dataset. This dataset must meet the following requirements:\n\nIt must contain a x and y coordinate. The extent and cell size of these coordinates must match the domain of the coupled MODFLOW 6 model exactly.\nIt must contain a variable (x, y) denoting the basin IDs.\nIt must contain a volume-level variable (x, y, row, column) for every coupled MODFLOW 6 boundary condition, describing the volume-level lookup table per cell.\n\n\n\n\n\n\n\nNote\n\n\n\nThe x and y coordinates are valid for structured MODFLOW 6 models (DIS). Discretized-by-vertices (DISV) and fully unstructured discretization (DISU). are not yet supported, but require no fundamental changes: one basin is connected to multiple MODFLOW 6 cells, and the coupling parameters must match the (structured, unstructured) grid of the MODFLOW 6 model exactly.\n\n\nThe MODFLOW 6 coupling example cases show examples of such a parametrization."
+ "objectID": "core/usage.html#allocation-results",
+ "href": "core/usage.html#allocation-results",
+ "title": "Usage",
+ "section": "10.2 Allocation results",
+ "text": "10.2 Allocation results\nThe allocation table contains a record of allocation results: when it happened, for which user node, in which allocation network, and what the demand, allocated flow and abstracted flow were.\n\n\n\ncolumn\ntype\n\n\n\n\ntime\nDateTime\n\n\nallocation_network_id\nInt\n\n\nuser_node_id\nInt\n\n\npriority\nInt\n\n\ndemand\nFloat64\n\n\nallocated\nFloat64\n\n\nabstracted\nFloat64\n\n\n\n\n\n\n\n\n\nNote\n\n\n\nCurrently the stored demand and abstraction rate are those at the allocation timepoint (and the abstraction rate is based on the previous allocation optimization). In the future these will be an average over the previous allocation timestep."
},
{
- "objectID": "build/index.html#modules",
- "href": "build/index.html#modules",
- "title": "1 API Reference",
- "section": "1.1 Modules",
- "text": "1.1 Modules\n# Ribasim.Ribasim — Module.\nmodule Ribasim\nRibasim is a water resources model. The computational core is implemented in Julia in the Ribasim package. It is currently mainly designed to be used as an application. To run a simulation from Julia, use Ribasim.run.\nFor more granular access, see:\n\nConfig\nModel\nsolve!\nBMI.finalize\n\nsource\n# Ribasim.config — Module.\nmodule config\nRibasim.config is a submodule of Ribasim to handle the configuration of a Ribasim model. It is implemented using the Configurations package. A full configuration is represented by Config, which is the main API. Ribasim.config is a submodule mainly to avoid name clashes between the configuration sections and the rest of Ribasim.\nsource"
+ "objectID": "core/usage.html#levelboundary-time",
+ "href": "core/usage.html#levelboundary-time",
+ "title": "Usage",
+ "section": "11.1 LevelBoundary / time",
+ "text": "11.1 LevelBoundary / time\nThis table is the transient form of the LevelBoundary table. The only difference is that a time column is added and activity is assumed to be true. The table must by sorted by time, and per time it must be sorted by node_id. With this the levels can be updated over time. In between the given times the level is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nlevel\nFloat64\n\\(m\\)\n-"
},
{
- "objectID": "build/index.html#types",
- "href": "build/index.html#types",
- "title": "1 API Reference",
- "section": "1.2 Types",
- "text": "1.2 Types\n# Ribasim.AllocationModel — Type.\nStore information for a subnetwork used for allocation.\nobjectivetype: The name of the type of objective used allocationnetworkid: The ID of this allocation network capacity: The capacity per edge of the allocation graph, as constrained by nodes that have a maxflowrate problem: The JuMP.jl model for solving the allocation problem Δtallocation: The time interval between consecutive allocation solves\nsource\n# Ribasim.AllocationModel — Method.\nConstruct the JuMP.jl problem for allocation.\nInputs\nconfig: The model configuration with allocation configuration in config.allocation p: Ribasim problem parameters Δt_allocation: The timestep between successive allocation solves\nOutputs\nAn AllocationModel object.\nsource\n# Ribasim.Basin — Type.\nRequirements:\n\nMust be positive: precipitation, evaporation, infiltration, drainage\nIndex points to a Basin\nvolume, area, level must all be positive and monotonic increasing.\n\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of vectors or Arrow Tables, and is added to avoid type instabilities. The nodeid are Indices to support fast lookup of e.g. currentlevel using ID.\nif autodiff T = DiffCache{Vector{Float64}} else T = Vector{Float64} end\nsource\n# Ribasim.DiscreteControl — Type.\nnodeid: node ID of the DiscreteControl node; these are not unique but repeated by the amount of conditions of this DiscreteControl node listennodeid: the ID of the node being condition on variable: the name of the variable in the condition greaterthan: The threshold value in the condition conditionvalue: The current value of each condition controlstate: Dictionary: node ID => (control state, control state start) logic_mapping: Dictionary: (control node ID, truth state) => control state record: Namedtuple with discrete control information for results\nsource\n# Ribasim.EdgeMetadata — Type.\nType for storing metadata of edges in the graph: id: ID of the edge (only used for labeling flow output) type: type of the edge allocationnetworkidsource: ID of allocation network where this edge is a source (0 if not a source) fromid: the node ID of the source node toid: the node ID of the destination node allocationflow: whether this edge has a flow in an allocation graph\nsource\n# Ribasim.FlatVector — Type.\nstruct FlatVector{T} <: AbstractVector{T}\nA FlatVector is an AbstractVector that iterates the T of a Vector{Vector{T}}.\nEach inner vector is assumed to be of equal length.\nIt is similar to Iterators.flatten, though that doesn’t work with the Tables.Column interface, which needs length and getindex support.\nsource\n# Ribasim.FlowBoundary — Type.\nnodeid: node ID of the FlowBoundary node active: whether this node is active and thus contributes flow flowrate: target flow rate\nsource\n# Ribasim.FractionalFlow — Type.\nRequirements:\n\nfrom: must be (TabulatedRatingCurve,) node\nto: must be (Basin,) node\nfraction must be positive.\n\nnodeid: node ID of the TabulatedRatingCurve node fraction: The fraction in [0,1] of flow the node lets through controlmapping: dictionary from (nodeid, controlstate) to fraction\nsource\n# Ribasim.InNeighbors — Type.\nIterate over incoming neighbors of a given label in a MetaGraph, only for edges of edge_type\nsource\n# Ribasim.LevelBoundary — Type.\nnode_id: node ID of the LevelBoundary node active: whether this node is active level: the fixed level of this ‘infinitely big basin’\nsource\n# Ribasim.LinearResistance — Type.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\n\nnodeid: node ID of the LinearResistance node active: whether this node is active and thus contributes flows resistance: the resistance to flow; Q = Δh/resistance controlmapping: dictionary from (nodeid, controlstate) to resistance and/or active state\nsource\n# Ribasim.ManningResistance — Type.\nThis is a simple Manning-Gauckler reach connection.\n\nLength describes the reach length.\nroughness describes Manning’s n in (SI units).\n\nThe profile is described by a trapezoid:\n \\ / ^\n \\ / |\n \\ / | dz\nbottom \\______/ |\n^ <--->\n| dy\n| <------>\n| width\n|\n|\n+ datum (e.g. MSL)\nWith profile_slope = dy / dz. A rectangular profile requires a slope of 0.0.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\nlength > 0\nroughess > 0\nprofile_width >= 0\nprofile_slope >= 0\n(profilewidth == 0) xor (profileslope == 0)\n\nsource\n# Ribasim.Model — Type.\nModel(config_path::AbstractString)\nModel(config::Config)\nInitialize a Model.\nThe Model struct is an initialized model, combined with the Config used to create it and saved results. The Basic Model Interface (BMI) is implemented on the Model. A Model can be created from the path to a TOML configuration file, or a Config object.\nsource\n# Ribasim.NodeMetadata — Type.\nType for storing metadata of nodes in the graph type: type of the node allocationnetworkid: Allocation network ID (0 if not in subnetwork)\nsource\n# Ribasim.OutNeighbors — Type.\nIterate over outgoing neighbors of a given label in a MetaGraph, only for edges of edge_type\nsource\n# Ribasim.Outlet — Type.\nnodeid: node ID of the Outlet node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the outlet maxflowrate: The maximum flow rate of the outlet controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this outlet is governed by PID control\nsource\n# Ribasim.PidControl — Type.\nPID control currently only supports regulating basin levels.\nnodeid: node ID of the PidControl node active: whether this node is active and thus sets flow rates listennodeid: the id of the basin being controlled pidparams: a vector interpolation for parameters changing over time. The parameters are respectively target, proportional, integral, derivative, where the last three are the coefficients for the PID equation. error: the current error; basintarget - currentlevel\nsource\n# Ribasim.Pump — Type.\nnodeid: node ID of the Pump node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the pump maxflowrate: The maximum flow rate of the pump controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this pump is governed by PID control\nsource\n# Ribasim.Subgrid — Type.\nSubgrid linearly interpolates basin levels.\nsource\n# Ribasim.TabulatedRatingCurve — Type.\nstruct TabulatedRatingCurve{C}\nRating curve from level to discharge. The rating curve is a lookup table with linear interpolation in between. Relation can be updated in time, which is done by moving data from the time field into the tables, which is done in the update_tabulated_rating_curve callback.\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of Vectors or Arrow Primitives, and is added to avoid type instabilities.\nnodeid: node ID of the TabulatedRatingCurve node active: whether this node is active and thus contributes flows tables: The current Q(h) relationships time: The time table used for updating the tables controlmapping: dictionary from (nodeid, controlstate) to Q(h) and/or active state\nsource\n# Ribasim.Terminal — Type.\nnode_id: node ID of the Terminal node\nsource\n# Ribasim.User — Type.\ndemand: water flux demand of user per priority over time active: whether this node is active and thus demands water allocated: water flux currently allocated to user per priority returnfactor: the factor in [0,1] of how much of the abstracted water is given back to the system minlevel: The level of the source basin below which the user does not abstract priorities: All used priority values. Each user has a demand for all these priorities, which is 0.0 if it is not provided explicitly. record: Collected data of allocation optimizations for output file.\nsource\n# Ribasim.config.Config — Method.\nConfig(config_path::AbstractString; kwargs...)\nParse a TOML file to a Config. Keys can be overruled using keyword arguments. To overrule keys from a subsection, e.g. dt from the solver section, use underscores: solver_dt.\nsource"
+ "objectID": "core/usage.html#flowboundary-time",
+ "href": "core/usage.html#flowboundary-time",
+ "title": "Usage",
+ "section": "12.1 FlowBoundary / time",
+ "text": "12.1 FlowBoundary / time\nThis table is the transient form of the FlowBoundary table. The only differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the flow rates can be updated over time. In between the given times the flow rate is interpolated linearly, and outside the flow rate is constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\ntime\nDateTime\n-\nsorted\n\n\nnode_id\nInt\n-\nsorted per time\n\n\nflow_rate\nFloat64\n\\(m^3 s^{-1}\\)\nnon-negative"
},
{
- "objectID": "build/index.html#functions",
- "href": "build/index.html#functions",
- "title": "1 API Reference",
- "section": "1.3 Functions",
- "text": "1.3 Functions\n# BasicModelInterface.finalize — Method.\nBMI.finalize(model::Model)::Model\nWrite all results to the configured files.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config_path::AbstractString)::Model\nInitialize a Model from the path to the TOML configuration file.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config::Config)::Model\nInitialize a Model from a Config.\nsource\n# CommonSolve.solve! — Method.\nsolve!(model::Model)::ODESolution\nSolve a Model until the configured endtime.\nsource\n# Ribasim.add_constraints_absolute_value! — Method.\nMinimizing |expr| can be achieved by introducing a new variable exprabs and posing the following constraints: exprabs >= expr expr_abs >= -expr\nsource\n# Ribasim.add_constraints_capacity! — Method.\nAdd the flow capacity constraints to the allocation problem. Only finite capacities get a constraint. The constraint indices are (edgesourceid, edgedstid).\nConstraint: flow over edge <= edge capacity\nsource\n# Ribasim.add_constraints_flow_conservation! — Method.\nAdd the flow conservation constraints to the allocation problem. The constraint indices are user node IDs.\nConstraint: sum(flows out of node node) <= flows into node + flow from storage and vertical fluxes\nsource\n# Ribasim.add_constraints_source! — Method.\nAdd the source constraints to the allocation problem. The actual threshold values will be set before each allocation solve. The constraint indices are (edgesourceid, edgedstid).\nConstraint: flow over source edge <= source flow in subnetwork\nsource\n# Ribasim.add_constraints_user_returnflow! — Method.\nAdd the user returnflow constraints to the allocation problem. The constraint indices are user node IDs.\nConstraint: outflow from user <= return factor * inflow to user\nsource\n# Ribasim.add_flow! — Method.\nAdd the given flow q to the flow over the edge on the horizontal (self-loop) edge from id to id.\nsource\n# Ribasim.add_flow! — Method.\nAdd the given flow q to the existing flow over the edge between the given nodes.\nsource\n# Ribasim.add_variables_absolute_value! — Method.\nCertain allocation distribution types use absolute values in the objective function. Since most optimization packages do not support the absolute value function directly, New variables are introduced that act as the absolute value of an expression by posing the appropriate constraints.\nsource\n# Ribasim.add_variables_flow! — Method.\nAdd the flow variables F to the allocation problem. The variable indices are (edgesourceid, edgedstid). Non-negativivity constraints are also immediately added to the flow variables.\nsource\n# Ribasim.adjust_edge_capacities! — Method.\nSet the values of the edge capacities. 2 cases:\n\nBefore the first allocation solve, set the edge capacities to their full capacity;\nBefore an allocation solve, subtract the flow used by allocation for the previous priority from the edge capacities.\n\nsource\n# Ribasim.adjust_source_flows! — Method.\nAdjust the source flows.\nsource\n# Ribasim.all_neighbor_labels_type — Method.\nGet the in- and outneighbor node IDs of the given node ID (label) over the given edge type in the graph.\nsource\n# Ribasim.allocate! — Method.\nUpdate the allocation optimization problem for the given subnetwork with the problem state and flows, solve the allocation problem and assign the results to the users.\nsource\n# Ribasim.allocation_graph — Method.\nBuild the graph used for the allocation problem.\nsource\n# Ribasim.allocation_graph_used_nodes! — Method.\nFind all nodes in the subnetwork which will be used in the allocation network. Some nodes are skipped to optimize allocation optimization.\nsource\n# Ribasim.allocation_path_exists_in_graph — Method.\nFind out whether a path exists between a start node and end node in the given allocation graph.\nsource\n# Ribasim.allocation_problem — Method.\nConstruct the allocation problem for the current subnetwork as a JuMP.jl model.\nsource\n# Ribasim.allocation_table — Method.\nCreate an allocation result table for the saved data\nsource\n# Ribasim.assign_allocations! — Method.\nAssign the allocations to the users as determined by the solution of the allocation problem.\nsource\n# Ribasim.avoid_using_own_returnflow! — Method.\nRemove allocation user return flow edges that are upstream of the user itself.\nsource\n# Ribasim.basin_bottom — Method.\nReturn the bottom elevation of the basin with index i, or nothing if it doesn’t exist\nsource\n# Ribasim.basin_bottoms — Method.\nGet the bottom on both ends of a node. If only one has a bottom, use that for both.\nsource\n# Ribasim.basin_table — Method.\nCreate the basin result table from the saved data\nsource\n# Ribasim.create_callbacks — Method.\nCreate the different callbacks that are used to store results and feed the simulation with new data. The different callbacks are combined to a CallbackSet that goes to the integrator. Returns the CallbackSet and the SavedValues for flow.\nsource\n# Ribasim.create_graph — Method.\nReturn a directed metagraph with data of nodes (NodeMetadata): NodeMetadata\nand data of edges (EdgeMetadata): EdgeMetadata\nsource\n# Ribasim.create_storage_tables — Method.\nRead the Basin / profile table and return all area and level and computed storage values\nsource\n# Ribasim.datetime_since — Method.\ndatetime_since(t::Real, t0::DateTime)::DateTime\nConvert a Real that represents the seconds passed since the simulation start to the nearest DateTime. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.datetimes — Method.\nGet all saved times as a Vector{DateTime}\nsource\n# Ribasim.discrete_control_affect! — Method.\nChange parameters based on the control logic.\nsource\n# Ribasim.discrete_control_affect_downcrossing! — Method.\nAn downcrossing means that a condition (always greater than) becomes false.\nsource\n# Ribasim.discrete_control_affect_upcrossing! — Method.\nAn upcrossing means that a condition (always greater than) becomes true.\nsource\n# Ribasim.discrete_control_condition — Method.\nListens for changes in condition truths.\nsource\n# Ribasim.discrete_control_table — Method.\nCreate a discrete control result table from the saved data\nsource\n# Ribasim.expand_logic_mapping — Method.\nReplace the truth states in the logic mapping which contain wildcards with all possible explicit truth states.\nsource\n# Ribasim.find_allocation_graph_edges! — Method.\nThis loop finds allocation graph edges in several ways:\n\nBetween allocation graph nodes whose equivalent in the subnetwork are directly connected\nBetween allocation graph nodes whose equivalent in the subnetwork are connected with one or more allocation graph nodes in between\n\nsource\n# Ribasim.findlastgroup — Method.\nFor an element id and a vector of elements ids, get the range of indices of the last consecutive block of id. Returns the empty range 1:0 if id is not in ids.\n# 1 2 3 4 5 6 7 8 9\nRibasim.findlastgroup(2, [5,4,2,2,5,2,2,2,1])\n# output\n6:8\nsource\n# Ribasim.findsorted — Method.\nFind the index of element x in a sorted collection a. Returns the index of x if it exists, or nothing if it doesn’t. If x occurs more than once, throw an error.\nsource\n# Ribasim.flow_table — Method.\nCreate a flow result table from the saved data\nsource\n# Ribasim.formulate_basins! — Method.\nSmoothly let the evaporation flux go to 0 when at small water depths Currently at less than 0.1 m.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.formulate_flow! — Method.\nConservation of energy for two basins, a and b:\nh_a + v_a^2 / (2 * g) = h_b + v_b^2 / (2 * g) + S_f * L + C / 2 * g * (v_b^2 - v_a^2)\nWhere:\n\nha, hb are the heads at basin a and b.\nva, vb are the velocities at basin a and b.\ng is the gravitational constant.\nS_f is the friction slope.\nC is an expansion or extraction coefficient.\n\nWe assume velocity differences are negligible (va = vb):\nh_a = h_b + S_f * L\nThe friction losses are approximated by the Gauckler-Manning formula:\nQ = A * (1 / n) * R_h^(2/3) * S_f^(1/2)\nWhere:\n\nWhere A is the cross-sectional area.\nV is the cross-sectional average velocity.\nn is the Gauckler-Manning coefficient.\nR_h is the hydraulic radius.\nS_f is the friction slope.\n\nThe hydraulic radius is defined as:\nR_h = A / P\nWhere P is the wetted perimeter.\nThe average of the upstream and downstream water depth is used to compute cross-sectional area and hydraulic radius. This ensures that a basin can receive water after it has gone dry.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.get_area_and_level — Method.\nCompute the area and level of a basin given its storage. Also returns darea/dlevel as it is needed for the Jacobian.\nsource\n# Ribasim.get_chunk_sizes — Method.\nGet the chunk sizes for DiffCache; differentiation w.r.t. u and t (the latter only if a Rosenbrock algorithm is used).\nsource\n# Ribasim.get_compressor — Method.\nGet the compressor based on the Results section\nsource\n# Ribasim.get_flow — Method.\nGet the flow over the given horizontal (selfloop) edge (val is needed for get_tmp from ForwardDiff.jl).\nsource\n# Ribasim.get_flow — Method.\nGet the flow over the given edge (val is needed for get_tmp from ForwardDiff.jl).\nsource\n# Ribasim.get_fractional_flow_connected_basins — Method.\nGet the node type specific indices of the fractional flows and basins, that are consecutively connected to a node of given id.\nsource\n# Ribasim.get_jac_prototype — Method.\nGet a sparse matrix whose sparsity matches the sparsity of the Jacobian of the ODE problem. All nodes are taken into consideration, also the ones that are inactive.\nIn Ribasim the Jacobian is typically sparse because each state only depends on a small number of other states.\nNote: the name ‘prototype’ does not mean this code is a prototype, it comes from the naming convention of this sparsity structure in the differentialequations.jl docs.\nsource\n# Ribasim.get_level — Method.\nGet the current water level of a node ID. The ID can belong to either a Basin or a LevelBoundary. storage: tells ForwardDiff whether this call is for differentiation or not\nsource\n# Ribasim.get_scalar_interpolation — Method.\nLinear interpolation of a scalar with constant extrapolation.\nsource\n# Ribasim.get_storage_from_level — Method.\nGet the storage of a basin from its level.\nsource\n# Ribasim.get_storages_and_levels — Method.\nGet the storage and level of all basins as matrices of nbasin × ntime\nsource\n# Ribasim.get_storages_from_levels — Method.\nCompute the storages of the basins based on the water level of the basins.\nsource\n# Ribasim.get_tstops — Method.\nFrom an iterable of DateTimes, find the times the solver needs to stop\nsource\n# Ribasim.get_value — Method.\nGet a value for a condition. Currently supports getting levels from basins and flows from flow boundaries.\nsource\n# Ribasim.id_index — Method.\nGet the index of an ID in a set of indices.\nsource\n# Ribasim.indicate_allocation_flow! — Method.\nAdd to the edge metadata that the given edge is used for allocation flow. If the edge does not exist, it is created.\nsource\n# Ribasim.inflow_id — Method.\nGet the unique inneighbor over a flow edge.\nsource\n# Ribasim.inflow_ids — Method.\nGet the inneighbors over flow edges.\nsource\n# Ribasim.inflow_ids_allocation — Method.\nGet the inneighbors of the given ID such that the connecting edge is an allocation flow edge.\nsource\n# Ribasim.inneighbor_labels_type — Method.\nGet the inneighbor node IDs of the given node ID (label) over the given edge type in the graph.\nsource\n# Ribasim.inoutflow_ids — Method.\nGet the in- and outneighbors over flow edges.\nsource\n# Ribasim.is_allocation_source — Method.\nFind out whether the given edge is a source for an allocation network.\nsource\n# Ribasim.is_flow_constraining — Method.\nWhether the given node node is flow constraining by having a maximum flow rate.\nsource\n# Ribasim.is_flow_direction_constraining — Method.\nWhether the given node is flow direction constraining (only in direction of edges).\nsource\n# Ribasim.load_data — Method.\nload_data(db::DB, config::Config, nodetype::Symbol, kind::Symbol)::Union{Table, Query, Nothing}\nLoad data from Arrow files if available, otherwise the database. Returns either an Arrow.Table, SQLite.Query or nothing if the data is not present.\nsource\n# Ribasim.load_structvector — Method.\nload_structvector(db::DB, config::Config, ::Type{T})::StructVector{T}\nLoad data from Arrow files if available, otherwise the database. Always returns a StructVector of the given struct type T, which is empty if the table is not found. This function validates the schema, and enforces the required sort order.\nsource\n# Ribasim.metadata_from_edge — Method.\nGet the metadata of an edge in the graph from an edge of the underlying DiGraph.\nsource\n# Ribasim.nodefields — Method.\nGet all node fieldnames of the parameter object.\nsource\n# Ribasim.nodetype — Method.\nFrom a SchemaVersion(“ribasim.flowboundary.static”, 1) return (:FlowBoundary, :static)\nsource\n# Ribasim.outflow_id — Method.\nGet the unique outneighbor over a flow edge.\nsource\n# Ribasim.outflow_ids — Method.\nGet the outneighbors over flow edges.\nsource\n# Ribasim.outflow_ids_allocation — Method.\nGet the outneighbors of the given ID such that the connecting edge is an allocation flow edge.\nsource\n# Ribasim.outneighbor_labels_type — Method.\nGet the outneighbor node IDs of the given node ID (label) over the given edge type in the graph.\nsource\n# Ribasim.parse_static_and_time — Method.\nProcess the data in the static and time tables for a given node type. The ‘defaults’ named tuple dictates how missing data is filled in. ‘time_interpolatables’ is a vector of Symbols of parameter names for which a time interpolation (linear) object must be constructed. The control mapping for DiscreteControl is also constructed in this function. This function currently does not support node states that are defined by more than one row in a table, as is the case for TabulatedRatingCurve.\nsource\n# Ribasim.process_allocation_graph_edges! — Method.\nFor the composite allocation graph edges:\n\nFind out whether they are connected to allocation graph nodes on both ends\nCompute their capacity\nFind out their allowed flow direction(s)\n\nsource\n# Ribasim.profile_storage — Method.\nCalculate a profile storage by integrating the areas over the levels\nsource\n# Ribasim.qh_interpolation — Method.\nFrom a table with columns nodeid, discharge (Q) and level (h), create a LinearInterpolation from level to discharge for a given nodeid.\nsource\n# Ribasim.reduction_factor — Method.\nFunction that goes smoothly from 0 to 1 in the interval [0,threshold], and is constant outside this interval.\nsource\n# Ribasim.run — Method.\nrun(config_file::AbstractString)::Model\nrun(config::Config)::Model\nRun a Model, given a path to a TOML configuration file, or a Config object. Running a model includes initialization, solving to the end with [solve!](@ref) and writing results with BMI.finalize.\nsource\n# Ribasim.save_flow — Method.\nCopy the current flow to the SavedValues\nsource\n# Ribasim.save_subgrid_level — Method.\nInterpolate the levels and save them to SavedValues\nsource\n# Ribasim.scalar_interpolation_derivative — Method.\nDerivative of scalar interpolation.\nsource\n# Ribasim.seconds_since — Method.\nseconds_since(t::DateTime, t0::DateTime)::Float64\nConvert a DateTime to a float that is the number of seconds since the start of the simulation. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.set_current_value! — Method.\nFrom a timeseries table time, load the most recent applicable data into table. table must be a NamedTuple of vectors with all variables that must be loaded. The most recent applicable data is non-NaN data for a given ID that is on or before t.\nsource\n# Ribasim.set_flow! — Method.\nSet the given flow q on the horizontal (self-loop) edge from id to id.\nsource\n# Ribasim.set_flow! — Method.\nSet the given flow q over the edge between the given nodes.\nsource\n# Ribasim.set_initial_discrete_controlled_parameters! — Method.\nSet parameters of nodes that are controlled by DiscreteControl to the values corresponding to the initial state of the model.\nsource\n# Ribasim.set_objective_priority! — Method.\nSet the objective for the given priority. For an objective with absolute values this also involves adjusting constraints.\nsource\n# Ribasim.set_static_value! — Method.\nLoad data from a source table static into a destination table. Data is matched based on the node_id, which is sorted.\nsource\n# Ribasim.set_table_row! — Method.\nUpdate table at row index i, with the values of a given row. table must be a NamedTuple of vectors with all variables that must be loaded. The row must contain all the column names that are present in the table. If a value is NaN, it is not set.\nsource\n# Ribasim.sorted_table! — Method.\nDepending on if a table can be sorted, either sort it or assert that it is sorted.\nTables loaded from the database into memory can be sorted. Tables loaded from Arrow files are memory mapped and can therefore not be sorted.\nsource\n# Ribasim.timesteps — Method.\nGet all saved times in seconds since start\nsource\n# Ribasim.update_allocation! — Method.\nSolve the allocation problem for all users and assign allocated abstractions to user nodes.\nsource\n# Ribasim.update_basin — Method.\nLoad updates from ‘Basin / time’ into the parameters\nsource\n# Ribasim.update_jac_prototype! — Method.\nMethod for nodes that do not contribute to the Jacobian\nsource\n# Ribasim.update_jac_prototype! — Method.\nThe controlled basin affects itself and the basins upstream and downstream of the controlled pump affect eachother if there is a basin upstream of the pump. The state for the integral term and the controlled basin affect eachother, and the same for the integral state and the basin upstream of the pump if it is indeed a basin.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the unique node downstream of these nodes are basins, then these directly depend on eachother and affect the Jacobian 2x Basins always depend on themselves.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the nodes down stream (or one node further if a fractional flow is in between) are basins, then the downstream basin depends on the upstream basin(s) and affect the Jacobian as many times as there are downstream basins Upstream basins always depend on themselves.\nsource\n# Ribasim.update_tabulated_rating_curve! — Method.\nLoad updates from ‘TabulatedRatingCurve / time’ into the parameters\nsource\n# Ribasim.valid_discrete_control — Method.\nCheck:\n\nwhether control states are defined for discrete controlled nodes;\nWhether the supplied truth states have the proper length;\nWhether look_ahead is only supplied for condition variables given by a time-series.\n\nsource\n# Ribasim.valid_edge_types — Method.\nCheck that only supported edge types are declared.\nsource\n# Ribasim.valid_edges — Method.\nTest for each node given its node type whether the nodes that\nare downstream (‘down-edge’) of this node are of an allowed type\nsource\n# Ribasim.valid_flow_rates — Method.\nTest whether static or discrete controlled flow rates are indeed non-negative.\nsource\n# Ribasim.valid_fractional_flow — Method.\nCheck that nodes that have fractional flow outneighbors do not have any other type of outneighbor, that the fractions leaving a node add up to ≈1 and that the fractions are non-negative.\nsource\n# Ribasim.valid_n_neighbors — Method.\nTest for each node given its node type whether it has an allowed number of flow/control inneighbors and outneighbors\nsource\n# Ribasim.valid_profiles — Method.\nCheck whether the profile data has no repeats in the levels and the areas start positive.\nsource\n# Ribasim.valid_sources — Method.\nThe source nodes must only have one allocation outneighbor and no allocation inneighbors.\nsource\n# Ribasim.valid_subgrid — Method.\nValidate the entries for a single subgrid element.\nsource\n# Ribasim.water_balance! — Method.\nThe right hand side function of the system of ODEs set up by Ribasim.\nsource\n# Ribasim.write_arrow — Method.\nWrite a result table to disk as an Arrow file\nsource\n# Ribasim.config.algorithm — Method.\nCreate an OrdinaryDiffEqAlgorithm from solver config\nsource\n# Ribasim.config.input_path — Method.\nConstruct a path relative to both the TOML directory and the optional input_dir\nsource\n# Ribasim.config.results_path — Method.\nConstruct a path relative to both the TOML directory and the optional results_dir\nsource\n# Ribasim.config.snake_case — Method.\nConvert a string from CamelCase to snake_case.\nsource"
+ "objectID": "core/usage.html#sec-condition",
+ "href": "core/usage.html#sec-condition",
+ "title": "Usage",
+ "section": "16.1 DiscreteControl / condition",
+ "text": "16.1 DiscreteControl / condition\nThe condition schema defines conditions of the form ‘the discrete_control node with this node id listens to whether the given variable of the node with the given listen feature id is grater than the given value’. If the condition variable comes from a time-series, a look ahead \\(\\Delta t\\) can be supplied.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\nlisten_feature_id\nInt\n-\n-\n\n\nvariable\nString\n-\nmust be “level” or “flow_rate”\n\n\ngreater_than\nFloat64\nvarious\n-\n\n\nlook_ahead\nFloat64\n\\(s\\)\nOnly on transient boundary conditions, non-negative (optional, default 0)"
},
{
- "objectID": "build/index.html#constants",
- "href": "build/index.html#constants",
- "title": "1 API Reference",
- "section": "1.4 Constants",
- "text": "1.4 Constants\n# Ribasim.config.algorithms — Constant.\nconst algorithms::Dict{String, Type}\nMap from config string to a supported algorithm type from OrdinaryDiffEq.\nSupported algorithms:\n\nQNDF\nRosenbrock23\nTRBDF2\nRodas5\nKenCarp4\nTsit5\nRK4\nImplicitEuler\nEuler\n\nsource"
+ "objectID": "core/usage.html#discretecontrol-logic",
+ "href": "core/usage.html#discretecontrol-logic",
+ "title": "Usage",
+ "section": "16.2 DiscreteControl / logic",
+ "text": "16.2 DiscreteControl / logic\nThe logic schema defines which control states are triggered based on the truth of the conditions a discrete_control node listens to. DiscreteControl is applied in the Julia core as follows:\n\nDuring the simulation it is checked whether the truth of any of the conditions changes.\nWhen a condition changes, the corresponding discrrete_control node id is retrieved (node_id in the condition schema above).\nThe truth value of all the conditions this discrete_control node lisens to are retrieved, in the order as they are specified in the condition schema. This is then converted into a string of “T” for true and “F” for false. This string we call the truth state.*\nThe table below determines for the given discrete_control node ID and truth state what the corresponding control state is.\nFor all the nodes this discrete_control node affects (as given by the “control” edges in Edges / static), their parameters are set to those parameters in NodeType / static corresponding to the determined control state.\n\n*. There is also a second truth state created in which for the last condition that changed it is specified whether it was an upcrossing (“U”) or downcrossing (“D”) of the threshold (greater than) value. If a control state is specified for a truth state that is crossing-specific, this takes precedence over the control state for the truth state that contains only “T” and “F”.\n\n\n\n\n\n\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted\n\n\ntruth_state\nString\n-\nConsists of the characters “T” (true), “F” (false), “U” (upcrossing), “D” (downcrossing) and “*” (any)\n\n\ncontrol_state\nString\n-"
},
{
- "objectID": "build/index.html#macros",
- "href": "build/index.html#macros",
- "title": "1 API Reference",
- "section": "1.5 Macros",
- "text": "1.5 Macros\n# Ribasim.config.@addfields — Macro.\nAdd fieldnames with Union{String, Nothing} type to struct expression. Requires (option?) use before it.\nsource\n# Ribasim.config.@addnodetypes — Macro.\nAdd all TableOption subtypes as fields to struct expression. Requires (option?) use before it.\nsource"
+ "objectID": "core/usage.html#discretecontrol-results",
+ "href": "core/usage.html#discretecontrol-results",
+ "title": "Usage",
+ "section": "16.3 DiscreteControl results",
+ "text": "16.3 DiscreteControl results\nThe control table contains a record of each change of control state: when it happened, which control node was involved, to which control state it changed and based on which truth state.\n\n\n\ncolumn\ntype\n\n\n\n\ntime\nDateTime\n\n\ncontrol_node_id\nInt\n\n\ntruth_state\nString\n\n\ncontrol_state\nString"
},
{
- "objectID": "build/index.html#index",
- "href": "build/index.html#index",
- "title": "1 API Reference",
- "section": "1.6 Index",
- "text": "1.6 Index\n\nRibasim.Ribasim\nRibasim.config\nRibasim.config.algorithms\nRibasim.AllocationModel\nRibasim.AllocationModel\nRibasim.Basin\nRibasim.DiscreteControl\nRibasim.EdgeMetadata\nRibasim.FlatVector\nRibasim.FlowBoundary\nRibasim.FractionalFlow\nRibasim.InNeighbors\nRibasim.LevelBoundary\nRibasim.LinearResistance\nRibasim.ManningResistance\nRibasim.Model\nRibasim.NodeMetadata\nRibasim.OutNeighbors\nRibasim.Outlet\nRibasim.PidControl\nRibasim.Pump\nRibasim.Subgrid\nRibasim.TabulatedRatingCurve\nRibasim.Terminal\nRibasim.User\nRibasim.config.Config\nBasicModelInterface.finalize\nBasicModelInterface.initialize\nBasicModelInterface.initialize\nCommonSolve.solve!\nRibasim.add_constraints_absolute_value!\nRibasim.add_constraints_capacity!\nRibasim.add_constraints_flow_conservation!\nRibasim.add_constraints_source!\nRibasim.add_constraints_user_returnflow!\nRibasim.add_flow!\nRibasim.add_flow!\nRibasim.add_variables_absolute_value!\nRibasim.add_variables_flow!\nRibasim.adjust_edge_capacities!\nRibasim.adjust_source_flows!\nRibasim.all_neighbor_labels_type\nRibasim.allocate!\nRibasim.allocation_graph\nRibasim.allocation_graph_used_nodes!\nRibasim.allocation_path_exists_in_graph\nRibasim.allocation_problem\nRibasim.allocation_table\nRibasim.assign_allocations!\nRibasim.avoid_using_own_returnflow!\nRibasim.basin_bottom\nRibasim.basin_bottoms\nRibasim.basin_table\nRibasim.config.algorithm\nRibasim.config.input_path\nRibasim.config.results_path\nRibasim.config.snake_case\nRibasim.create_callbacks\nRibasim.create_graph\nRibasim.create_storage_tables\nRibasim.datetime_since\nRibasim.datetimes\nRibasim.discrete_control_affect!\nRibasim.discrete_control_affect_downcrossing!\nRibasim.discrete_control_affect_upcrossing!\nRibasim.discrete_control_condition\nRibasim.discrete_control_table\nRibasim.expand_logic_mapping\nRibasim.find_allocation_graph_edges!\nRibasim.findlastgroup\nRibasim.findsorted\nRibasim.flow_table\nRibasim.formulate_basins!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.get_area_and_level\nRibasim.get_chunk_sizes\nRibasim.get_compressor\nRibasim.get_flow\nRibasim.get_flow\nRibasim.get_fractional_flow_connected_basins\nRibasim.get_jac_prototype\nRibasim.get_level\nRibasim.get_scalar_interpolation\nRibasim.get_storage_from_level\nRibasim.get_storages_and_levels\nRibasim.get_storages_from_levels\nRibasim.get_tstops\nRibasim.get_value\nRibasim.id_index\nRibasim.indicate_allocation_flow!\nRibasim.inflow_id\nRibasim.inflow_ids\nRibasim.inflow_ids_allocation\nRibasim.inneighbor_labels_type\nRibasim.inoutflow_ids\nRibasim.is_allocation_source\nRibasim.is_flow_constraining\nRibasim.is_flow_direction_constraining\nRibasim.load_data\nRibasim.load_structvector\nRibasim.metadata_from_edge\nRibasim.nodefields\nRibasim.nodetype\nRibasim.outflow_id\nRibasim.outflow_ids\nRibasim.outflow_ids_allocation\nRibasim.outneighbor_labels_type\nRibasim.parse_static_and_time\nRibasim.process_allocation_graph_edges!\nRibasim.profile_storage\nRibasim.qh_interpolation\nRibasim.reduction_factor\nRibasim.run\nRibasim.save_flow\nRibasim.save_subgrid_level\nRibasim.scalar_interpolation_derivative\nRibasim.seconds_since\nRibasim.set_current_value!\nRibasim.set_flow!\nRibasim.set_flow!\nRibasim.set_initial_discrete_controlled_parameters!\nRibasim.set_objective_priority!\nRibasim.set_static_value!\nRibasim.set_table_row!\nRibasim.sorted_table!\nRibasim.timesteps\nRibasim.update_allocation!\nRibasim.update_basin\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_tabulated_rating_curve!\nRibasim.valid_discrete_control\nRibasim.valid_edge_types\nRibasim.valid_edges\nRibasim.valid_flow_rates\nRibasim.valid_fractional_flow\nRibasim.valid_n_neighbors\nRibasim.valid_profiles\nRibasim.valid_sources\nRibasim.valid_subgrid\nRibasim.water_balance!\nRibasim.write_arrow\nRibasim.config.@addfields\nRibasim.config.@addnodetypes"
+ "objectID": "core/usage.html#pidcontrol-time",
+ "href": "core/usage.html#pidcontrol-time",
+ "title": "Usage",
+ "section": "17.1 PidControl / time",
+ "text": "17.1 PidControl / time\nThis table is the transient form of the PidControl table. The differences are that a time column is added and the nodes are assumed to be active so this column is removed. The table must by sorted by time, and per time it must be sorted by node_id. With this the target level and PID coefficients can be updated over time. In between the given times the these values interpolated linearly, and outside these values area constant given by the nearest time value. Note that a node_id can be either in this table or in the static one, but not both.\n\n\n\ncolumn\ntype\nunit\nrestriction\n\n\n\n\nnode_id\nInt\n-\nsorted per time\n\n\ntime\nDateTime\n-\nsorted\n\n\nlisten_node_id\nInt\n-\n-\n\n\ntarget\nFloat64\n\\(m\\)\n-\n\n\nproportional\nFloat64\n\\(s^{-1}\\)\n-\n\n\nintegral\nFloat64\n\\(s^{-2}\\)\n-\n\n\nderivative\nFloat64\n-\n-"
},
{
- "objectID": "python/index.html",
- "href": "python/index.html",
- "title": "Python tooling",
+ "objectID": "core/index.html",
+ "href": "core/index.html",
+ "title": "Julia core",
"section": "",
- "text": "The Ribasim Python package (named ribasim) aims to make it easy to build, update and analyze Ribasim models programmatically.\nThe Ribasim QGIS plugin allows users to construct a model from scratch without programming. For specific tasks, like adding observed rainfall timeseries, it can be faster to use Python instead.\nOne can also use Ribasim Python to build entire models from base data, such that your model setup is fully reproducible.\nThe package is registered in PyPI and can therefore be installed with pip:\npip install ribasim\nFor wheel (.whl) downloads, including nightly builds, see the download section. After downloading wheels can be installed by referring to the correct path:\npip install path/to/ribasim-*.whl\nFor documentation please see the examples and API reference."
+ "text": "With the term “core”, we mean the computational engine of Ribasim. As detailed in the usage documentation, it is generally used as a command line tool.\nThe theory is described on the equations page, and more in-depth numerical considerations are described on the numerical considerations page. As allocation is a large and self-contained part of the Ribasim core, it is described on the separate allocation page.\nThe core is implemented in the Julia programming language, and can be found in the Ribasim repository under the core/ folder. For developers we also advise to read the developer documentation.\n\n\n\n\nflowchart TB\nmodeler([Modeler]):::user\n\napi[\"Ribasim Python\\n[python]\"]:::system\nmodeler-->|prepare model|api\n\nsubgraph ribasimBoundary[Ribasim]\n ribasim[\"Ribasim.jl\\n[julia]\"]:::system\n libribasim[\"libribasim\\n[julia + python + BMI]\"]:::system\n cli[\"Ribasim CLI\\n[julia]\"]:::system\n cli-->ribasim\n libribasim-->ribasim\nend\nmodeler-->|start|cli\nmodeler-->|coupled simulation|libribasim\n\nsubgraph qgisBoundary[QGIS]\n QGIS[QGIS Application]:::system_ext\n qgisPlugin[\"Ribasim QGIS plugin\\n[python]\"]:::system\n QGIS-->qgisPlugin\nend\nmodeler-->|prepare model|qgisBoundary\n\nmodel[(\"input model data\\n[toml + geopackage + arrow]\")]:::system\nqgisPlugin-->|read/write|model\napi-->|read/write|model\nribasim-->|simulate|model\n\noutput[(\"simulation output\\n[arrow]\")]:::system\nribasim-->|write|output\n\nclass qgisBoundary,ribasimBoundary boundary\n\n%% class definitions for C4 model\nclassDef default stroke-width:1px,stroke:white,color:white\nclassDef system fill:#1168bd\nclassDef user fill:#08427b\nclassDef system_ext fill:#999999\nclassDef boundary fill:transparent,stroke-dasharray:5 5,stroke:black,color:black\n\n\nComponent overview of Ribasim\n\n\n\n\n1 The simulation loop\nThe figure below shows a simple flowchart of the simulation in Ribasim.jl.\n\n\n\n\nflowchart LR\nStart((Start))\nInit[Initialize model]\nCon[Conditional: allocation, control]\nSim[Simulate flows over timestep]\nFinished{End of simulation period?}\nDone((Done))\n\nStart --> Init\nInit --> Con\nCon --> Sim\nSim --> Finished\nFinished -->|no| Con\nFinished -->|yes| Done"
},
{
- "objectID": "python/reference/TabulatedRatingCurve.html",
- "href": "python/reference/TabulatedRatingCurve.html",
- "title": "1 TabulatedRatingCurve",
+ "objectID": "core/numerics.html",
+ "href": "core/numerics.html",
+ "title": "Numerical considerations",
"section": "",
- "text": "1 TabulatedRatingCurve\nTabulatedRatingCurve()"
+ "text": "We want to solve the following initial value problem: \\[\n\\begin{cases}\n \\frac{\\text{d}\\mathbf{u}}{\\text{d}t} = \\mathbf{f}(\\mathbf{u},t) \\quad t_0 < t < t_\\text{end} \\\\\n \\mathbf{u}(t_0) = \\mathbf{u}_0\n\\end{cases},\n\\tag{1}\\]\nwhere \\(\\mathbf{f}\\) denotes water_balance! and \\(\\mathbf{u_0}\\) the initial storages (and the PID integrals which start out at \\(0\\)).\nIn general \\(\\mathbf{f}\\) is a non-linear function in \\(\\mathbf{u}\\). These non-linearities are introduced by:\nThe problem Equation 1 can be solved by various numerical time-integration methods. To do this the time interval \\([t_0,t_\\text{end}]\\) is discretized into a finite number of time points \\(t_0 < t_1 < \\ldots < t_N = t_\\text{end}\\) for which approximate solutions \\(\\mathbf{w}_n \\approx \\mathbf{u}(t_n)\\) are computed. In general we do not assume a fixed timestep (the interval between successive points in time). Rather, the solver attempts to make as large a step as possible while keeping error tolerances within requirements. The solver settings section details the available configuration options."
},
{
- "objectID": "python/reference/Model.html",
- "href": "python/reference/Model.html",
- "title": "1 Model",
- "section": "",
- "text": "Model()\nA full Ribasim model schematisation with all input.\nRibasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime\nEnd time of the simulation.\nrequired\n\n\nupdate_timestep\n\nThe output time step of the simulation in seconds (default of 1 day)\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nFractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nLevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nFlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nTabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nPump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOutlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nTerminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nDiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nPidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nUser\nUser node type with demand and priority.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nread\nRead model from TOML file.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_field_ids\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_ids\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\nwrite\nWrite the contents of the model to disk and save it as a TOML configuration file.\n\n\n\n\n\nModel.plot(ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.read(filepath)\nRead model from TOML file.\n\n\n\nModel.validate_model()\nValidate the model.\nChecks: - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_field_ids()\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_ids()\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\n\nModel.write(filepath)\nWrite the contents of the model to disk and save it as a TOML configuration file.\nIf filepath.parent does not exist, it is created before writing.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nfilepath\nPath | str\n\nrequired"
+ "objectID": "core/numerics.html#euler-forward",
+ "href": "core/numerics.html#euler-forward",
+ "title": "Numerical considerations",
+ "section": "1.1 Euler forward",
+ "text": "1.1 Euler forward\nThe simplest numerical method is Euler forward: \\[\n\\mathbf{w}_{n+1} = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{w}_n, t_n).\n\\tag{2}\\]\nHere \\(\\mathbf{w}_{n+1}\\) is given as a simple explicit function of \\(\\mathbf{w}_n\\)."
},
{
- "objectID": "python/reference/Model.html#parameters",
- "href": "python/reference/Model.html#parameters",
- "title": "1 Model",
- "section": "",
- "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime\nEnd time of the simulation.\nrequired\n\n\nupdate_timestep\n\nThe output time step of the simulation in seconds (default of 1 day)\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nFractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nLevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nFlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nTabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nPump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOutlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nTerminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nDiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nPidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nUser\nUser node type with demand and priority.\nrequired"
+ "objectID": "core/numerics.html#euler-backward",
+ "href": "core/numerics.html#euler-backward",
+ "title": "Numerical considerations",
+ "section": "1.2 Euler backward",
+ "text": "1.2 Euler backward\nEuler backward is formulated as follows: \\[\n\\mathbf{w}_{n+1} = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{w}_{n+1},t_{n+1}).\n\\tag{3}\\]\nNote that this is an implicit equation for \\(\\mathbf{w}_{n+1}\\), which is non-linear because of the non-linearity of \\(\\mathbf{f}\\).\nGenerally one of the following iterative methods is used for finding solutions to non-linear equations like this:\n\nPicard iteration for fixed points. This method aims to approximate \\(\\mathbf{w}_{n+1}\\) as a fixed point of the function \\[\n\\mathbf{g}(\\mathbf{x}) = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{x},t_{n+1})\n\\] by iterating \\(\\mathbf{g}\\) on an initial guess of \\(\\mathbf{w}_{n+1}\\);\nNewton iterations: approximate \\(\\mathbf{w}_{n+1}\\) as a root of the function \\[\n\\mathbf{h}(\\mathbf{x}) = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{x},t_{n+1}) - \\mathbf{x},\n\\] by iteratively finding the root of its linearized form:\n\n\\[\\begin{align}\n\\mathbf{0} =& \\mathbf{h}(\\mathbf{w}_{n+1}^k) + \\mathbf{J}(\\mathbf{h})(\\mathbf{w}_{n+1}^k)(\\mathbf{w}_{n+1}^{k+1}-\\mathbf{w}_{n+1}^k) \\\\\n=& \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{w}_{n+1}^k,t_{n+1}) - \\mathbf{w}_{n+1}^k \\\\ +&\\left[(t_{n+1}-t_n)\\mathbf{J}(\\mathbf{f})(\\mathbf{w}_{n+1}^k)-\\mathbf{I}\\right](\\mathbf{w}_{n+1}^{k+1}-\\mathbf{w}_{n+1}^k).\n\\end{align}\\] Note that this thus requires an evaluation of the Jacobian of \\(\\mathbf{f}\\) and solving a linear system per iteration."
},
{
- "objectID": "python/reference/Model.html#methods",
- "href": "python/reference/Model.html#methods",
- "title": "1 Model",
- "section": "",
- "text": "Name\nDescription\n\n\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nread\nRead model from TOML file.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_field_ids\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_ids\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\nwrite\nWrite the contents of the model to disk and save it as a TOML configuration file.\n\n\n\n\n\nModel.plot(ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.read(filepath)\nRead model from TOML file.\n\n\n\nModel.validate_model()\nValidate the model.\nChecks: - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_field_ids()\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_ids()\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\n\nModel.write(filepath)\nWrite the contents of the model to disk and save it as a TOML configuration file.\nIf filepath.parent does not exist, it is created before writing.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nfilepath\nPath | str\n\nrequired"
+ "objectID": "core/numerics.html#basin-profiles",
+ "href": "core/numerics.html#basin-profiles",
+ "title": "Numerical considerations",
+ "section": "4.1 Basin profiles",
+ "text": "4.1 Basin profiles\nThe basin profiles affect \\(\\mathbf{f}\\) in many ways, anywhere where a basin level or area is required.\n\n\n\n\n\n\nNote\n\n\n\nThis section needs to be updated and extended after once this issue is resolved."
},
{
- "objectID": "python/reference/FractionalFlow.html",
- "href": "python/reference/FractionalFlow.html",
- "title": "1 FractionalFlow",
- "section": "",
- "text": "1 FractionalFlow\nFractionalFlow()"
+ "objectID": "core/numerics.html#qh-relations",
+ "href": "core/numerics.html#qh-relations",
+ "title": "Numerical considerations",
+ "section": "4.2 Q(h) relations",
+ "text": "4.2 Q(h) relations\nTabulatedRatingCurve nodes contribute to \\(\\mathbf{f}\\) with terms of the following form:\n\\[\n Q(h(S))\n\\]\nwhere the continuity of this term is given by the least continuous of \\(Q\\) and \\(h\\)."
},
{
- "objectID": "python/reference/Outlet.html",
- "href": "python/reference/Outlet.html",
- "title": "1 Outlet",
- "section": "",
- "text": "1 Outlet\nOutlet()"
+ "objectID": "core/numerics.html#empty-basins",
+ "href": "core/numerics.html#empty-basins",
+ "title": "Numerical considerations",
+ "section": "4.3 Empty basins",
+ "text": "4.3 Empty basins\nReduction factors are introduced at several points in the definition of \\(\\mathbf{f}\\) to smooth out otherwise discontinuous transitions (e.g. the flow rate of a pump going to zero when the source basin dries out). If flows are not too large with respect to basin storage, this will prevent basins from reaching 0. Rather, the basin gets a very small storage. The reduction factors help with performance, but are also an important tool to avoid getting negative storage in basins. Negative storage needs to be avoided since it is not a real solution, and would introduce water into the model that doesn’t exist. Another tool used to avoid negative storage is the isoutoutofdomain option, which Ribasim makes use of. This reject timesteps that lead to negative storage, instead retrying with a smaller timestep."
},
{
- "objectID": "python/reference/Terminal.html",
- "href": "python/reference/Terminal.html",
- "title": "1 Terminal",
+ "objectID": "core/allocation.html",
+ "href": "core/allocation.html",
+ "title": "Allocation",
"section": "",
- "text": "1 Terminal\nTerminal()"
+ "text": "Allocation is the process of assigning an allocated abstraction flow rate to user nodes in the model based on information about sources, user demands over various priorities, constraints introduced by nodes, local water availability and graph topology. The allocation procedure implemented in Ribasim is heavily inspired by the maximum flow problem.\nThe allocation problem is solved per subnetwork of the Ribasim model. The subnetwork is used to formulate an optimization problem with the JuMP package, which is solved using the HiGHS solver. For more information see also the example of solving the maximum flow problem with JuMP.jl here."
},
{
- "objectID": "python/reference/LevelBoundary.html",
- "href": "python/reference/LevelBoundary.html",
- "title": "1 LevelBoundary",
- "section": "",
- "text": "1 LevelBoundary\nLevelBoundary()"
+ "objectID": "core/allocation.html#allocation-problem-input",
+ "href": "core/allocation.html#allocation-problem-input",
+ "title": "Allocation",
+ "section": "1.1 Allocation problem input",
+ "text": "1.1 Allocation problem input\n\n1.1.1 The subnetwork\nThe allocation problem is solved per subnetwork, which is given by a subset \\(S \\subset V\\) of node ids. Different subnetworks are disjoint from eachother.\n\n\n1.1.2 Source flows\nSources are indicated by a set of edges in the subnetwork \\[\nE_S^\\text{source} \\subset \\left(S \\times S\\right) \\cap E.\n\\] That is, if \\((i,j) \\in E_S^\\text{source}\\), then \\(Q_{ij}\\) (see the formal model description) is treated as a source flow in the allocation problem.\n\n\n1.1.3 User demands\nThe subnetwork contains a subset of user nodes \\(U_S \\subset S\\), who all have time varying demands over various priorities \\(p\\): \\[\n d^p_i(t), \\quad i \\in U_S, p = 1,2,\\ldots, p_{\\max}.\n\\]\n\n\n\n\n\n\nNote\n\n\n\nOn this page we assume that the priorities are given by all integers from \\(1\\) to some \\(p_{\\max} \\in \\mathbb{N}\\). However, in the Ribasim input this is not a requirement; some of these in between priority values can be missing, only the ordering of the given priorities is taken into account.\n\n\n\n\n1.1.4 Vertical fluxes and local storage\nApart from the source flows denoted by edges, there are other sources of water in the subnetwork, associated with the basins in the subnetwork \\(B_S = B \\cap S\\). Firstly there is the sum of the vertical fluxes (precipitation, evaporation, infiltration and drainage) for each basin \\[\n \\phi_i(t), \\quad \\forall i \\in B_S.\n\\]\nSecondly, there is the available water in each basin above the minimum level \\(l_{\\min,i}\\) corresponding to a minimum storage \\(s_{\\min,i}\\): \\[\n u_i(t)-s_{\\min,i}, \\quad \\forall i \\in B_S.\n\\] Note that this value can be negative, which we interpret as a demand from the basin.\n\n\n1.1.5 Flow magnitude and direction constraints\nNodes in the Ribasim model that have a max_flow_rate, i.e. pumps and outlets, put a constraint on the flow through that node. Some nodes only allow flow in one direction, like pumps, outlets and tabulated rating curves.\n\n\n1.1.6 Fractional flows and user return flows\nBoth fractional flow nodes and user nodes dictate proportional relationships between flows over edges in the subnetwork. Users have a return factor \\(0 \\le r_i \\le 1, i \\in U_S\\)."
},
{
- "objectID": "python/reference/FlowBoundary.html",
- "href": "python/reference/FlowBoundary.html",
- "title": "1 FlowBoundary",
- "section": "",
- "text": "1 FlowBoundary\nFlowBoundary()"
+ "objectID": "core/allocation.html#the-allocation-optimization-problem",
+ "href": "core/allocation.html#the-allocation-optimization-problem",
+ "title": "Allocation",
+ "section": "1.2 The allocation optimization problem",
+ "text": "1.2 The allocation optimization problem\n\n1.2.1 The allocation subgraph\nA new graph is created from the subnetwork, which we call an allocation graph. The allocation graph is almost a subgraph of the main (flow) model, apart from the fact that an allocation graph can contain edges which are not in the main model.\nThe allocation graph consists of:\n\nNodes \\(V'_S \\subset V_S\\), where each basin, source and user in the subnetwork get a node in the allocation graph. Also nodes that have fractional flow outneighbors get a node in the allocation graph.\nEdges \\(E_S\\), which are either edges that also appear between nodes in the subnetwork or represent a sequence of those, creating a shortcut.\n\nFor notational convenience, we use the notation\n\\[\\begin{align}\n V^{\\text{out}}_S(i) = \\left\\{j \\in V'_S : (i,j) \\in E_S\\right\\} \\\\\n V^{\\text{in}}_S(j) = \\left\\{i \\in V'_S : (i,j) \\in E_S\\right\\}\n\\end{align}\\]\nfor the set of in-neighbors and out-neighbors of a node in the allocation graph respectively\n\n\n1.2.2 The allocation graph capacities\nThe capacities of the edges of the allocation graph are collected in the sparse capacity matrix \\(C_S \\in \\overline{\\mathbb{R}}_{\\ge 0}^{n'\\times n'}\\) where \\(n' = \\#V'_S\\) is the number of nodes in the allocation graph. The capacities can be infinite.\nThe capacities are determined in 4 different ways:\n\nIf an edge does not exist in the allocation graph, i.e. \\((i,j) \\notin E_S\\) for certain \\(1 \\le i,j\\le n'\\), then \\((C_S)_{i,j} = 0\\);\nThe capacity of the edge \\(e \\in E_S\\) is given by the smallest max_flow_rate of the nodes along the equivalent edges in the subnetwork. If there are no nodes with a max_flow_rate, the edge capacity is infinite;\nIf the edge is a source, the capacity of the edge is given by the flow rate of that source.\n\n\n\n1.2.3 The optimization variables\nThere are 2 types of variables whose value has to be determined to solve the allocation problem:\n\nThe flows \\(F \\in \\mathbb{R}_{\\ge 0}^{n'\\times n'}\\) over the edges in the allocation graph;\nThe allocations to the basins \\[\n A^\\text{basin}_{i} \\ge 0, \\quad B_S.\n\\]\n\n\n\n\n\n\n\nNote\n\n\n\nCurrently the basin allocations are not taken into account in the implementation.\n\n\n\n\n1.2.4 The optimization objective\nThe goal of allocation is to get the flow to the users as close as possible to their demand. To achieve this, the following objectives are supported:\n\nquadratic_absolute: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left( F_{ij} - d_j^p(t)\\right)^2\n\\]\nquadratic_relative: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left( 1 - \\frac{F_{ij}}{d_j^p(t)}\\right)^2\n\\]\nlinear_absolute: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left| F_{ij} - d_j^p(t)\\right|\n\\]\nlinear_relative: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left|1 - \\frac{F_{ij}}{d_j^p(t)}\\right|\n\\]\n\nTo avoid division by \\(0\\) errors, if a *_relative objective is used and a demand is \\(0\\), the coefficient of the flow \\(F_{ij}\\) is set to \\(0\\).\nFor *_absolute objectives the optimizer cares about the actual amount of water allocated to a user, for *_relative objectives it cares about the fraction of the demand allocated to the user. For quadratic_* objectives the optimizer cares about avoiding large shortages, for linear_* objectives it treats all deviations equally.\n\n\n\n\n\n\nNote\n\n\n\nThese options for objectives for allocation to users have not been tested thoroughly, and might change in the future.\n\n\nThe absolute value applied here is not supported in a linear programming context directly; this requires introduction of new variables and constraints. For more details see here.\n\n\n\n\n\n\nNote\n\n\n\nIn the future new optimization objectives will be introduced, for demands of basins and priorities over sources. These will be used in combination with the above, in the form of goal programming.\n\n\n\n\n1.2.5 The optimization variable constraints\n\nFlow conservation: For the basins in the allocation graph we have that \\[\n \\sum_{j=1}^{n'} F_{kj} \\le \\sum_{i=1}^{n'} F_{ik}, \\quad \\forall k \\in B_S.\n\\tag{1}\\] Note that we do not require equality here; in the allocation we do not mind that excess flow is ‘forgotten’ if it cannot contribute to the allocation to the users.\nCapacity: the flows over the edges are positive and bounded by the edge capacity: \\[\n F_{ij} \\le \\left(C_S\\right)_{ij}, \\quad \\forall(i,j) \\in E_S.\n\\tag{2}\\] By the definition of \\(C_S\\) this also includes the source flows.\nUser outflow: The outflow of the user is dictated by the inflow and the return factor: \\[\n F_{ik} = r_k \\cdot F_{kj} \\quad\n \\quad \\forall k \\in U_S, \\quad\n V^{\\text{in}}_S(k) = \\{i\\},\\;\n V^{\\text{out}}_S(k) = \\{j\\}.\n\\tag{3}\\] Here we use that each user node in the allocation graph has a unique in-edge and out-edge.\nUser demand: user demand constraints are discussed in the next section.\nFractional flow: Let \\(L_S \\subset V_S\\) be the set of nodes in the max flow graph with fractional flow outneighbors, and \\(f_j\\) the flow fraction associated with fractional flow node \\(j \\in V_S\\). Then \\[\n F_{ij} \\le f_j \\sum_{k\\in V^\\text{in}_S(i)} F_{ki} \\qquad\n \\forall i \\in L_S, \\;\n j \\in V_S^\\text{out}(i).\n\\tag{4}\\]\nFlow sign: Furthermore there are the non-negativity constraints for the flows and allocations, see The optimization variables.\n\n\n\n\n\n\n\nNote\n\n\n\nCurrently the fractional flow constraints are not taken into account in the implementation."
},
{
- "objectID": "python/reference/ManningResistance.html",
- "href": "python/reference/ManningResistance.html",
- "title": "1 ManningResistance",
- "section": "",
- "text": "1 ManningResistance\nManningResistance()"
+ "objectID": "core/allocation.html#final-notes-on-the-allocation-problem",
+ "href": "core/allocation.html#final-notes-on-the-allocation-problem",
+ "title": "Allocation",
+ "section": "1.3 Final notes on the allocation problem",
+ "text": "1.3 Final notes on the allocation problem\n\n1.3.1 Users using their own return flow\nIf not explicitly avoided, users can use their own return flow in this allocation problem formulation. Therefore, return flow of users is only taken into account by allocation if that return flow is downstream of the user where it comes from. That is, if there is no path in the directed allocation graph from the user outflow node back to the user."
},
{
- "objectID": "python/reference/Pump.html",
- "href": "python/reference/Pump.html",
- "title": "1 Pump",
- "section": "",
- "text": "1 Pump\nPump()"
+ "objectID": "core/allocation.html#example",
+ "href": "core/allocation.html#example",
+ "title": "Allocation",
+ "section": "2.1 Example",
+ "text": "2.1 Example\n\n\n\n\n\n\nNote\n\n\n\nAn example with figures and data will be added here after addition of allocation output files."
},
{
"objectID": "contribute/index.html",
@@ -665,67 +630,11 @@
"text": "Ribasim welcomes contributions.\nThere is developer documentation for the Julia core, Python tooling, and the QGIS plugin. A guide on how to add a new node type to both is written in adding node types. Release process describes the steps to follow when creating a new Ribasim release."
},
{
- "objectID": "contribute/core.html",
- "href": "contribute/core.html",
- "title": "Julia core development",
+ "objectID": "contribute/qgis.html",
+ "href": "contribute/qgis.html",
+ "title": "QGIS plugin development",
"section": "",
- "text": "This section is about the Julia core in Ribasim.jl. See the component overview here for the context of this computational core.\nRibasim.jl can be divided into 3 parts:\n\nModel initialization\nRunning the simulation loop\nWriting the output files\n\nThe figure below gives a more detailed description of the simulation loop in the form of a sequence diagram. From top to bottom, it contains the following blocks:\n\nAllocation optimization; activated when the allocation timestep has been passed;\nControl actions; activated when some discrete control callback is triggered;\nWater balance; computing the flows over flow edges happens each timestep;\nTime integration step; done by the integrator from OrdinaryDiffEq.jl.\n\n\n\n\n\nsequenceDiagram\n autonumber\n participant Int as Process: Integrator\n participant Optim as Process: Allocation optimization\n participant Param as Data: Parameters\n participant State as Data: State\n participant Sim as Process: Water balance\n loop Simulation loop (OrdinaryDiffEq.jl)\n activate Int\n %% Allocation\n rect rgb(200, 200, 200)\n opt Allocation optimization, per allocation network (JuMP.jl, HiGHS)\n activate Optim\n Int->>Optim: Callback: allocation timestep has passed\n Param-->>Optim: Input\n State-->>Optim: Input\n Optim->>Optim: Optimize Basin allocations if below target level\n Optim->>Optim: Optimize User allocation, per priority\n Optim-->>Param: Set allocated flow rates\n deactivate Optim\n end\n end\n %% Control\n rect rgb(200, 200, 200)\n opt Control actions\n Int->>Int: DiscreteControl callback\n Int-->>Param: Parameter updates by control\n end\n end\n %% water_balance!\n rect rgb(200, 200, 200)\n activate Sim\n State-->>Sim: Input\n Param-->>Sim: Input\n Sim->>Sim: Compute flows over edges per node type\n Sim-->>Param: Set flows\n deactivate Sim\n end\n %% Time integration\n rect rgb(200, 200, 200)\n State-->>Int: Input\n Param-->>Int: Input\n Int->>Int: Time integration step\n Int-->>State: Update state\n end\n deactivate Int\n end"
- },
- {
- "objectID": "contribute/core.html#install-julia-via-juliaup",
- "href": "contribute/core.html#install-julia-via-juliaup",
- "title": "Julia core development",
- "section": "2.1 Install Julia via Juliaup",
- "text": "2.1 Install Julia via Juliaup\nInstall Julia via Juliaup as described in its README."
- },
- {
- "objectID": "contribute/core.html#install-julia-libraries",
- "href": "contribute/core.html#install-julia-libraries",
- "title": "Julia core development",
- "section": "2.2 Install Julia libraries",
- "text": "2.2 Install Julia libraries\nStart the Julia REPL by executing julia in your terminal. Within the REPL type ] to enter the Pkg REPL. For more information on how to use Pkg, see the Getting Started page in its documentation. There you can add Revise and TestEnv to your global environment.\npkg> add Revise TestEnv"
- },
- {
- "objectID": "contribute/core.html#setup-revise.jl",
- "href": "contribute/core.html#setup-revise.jl",
- "title": "Julia core development",
- "section": "2.3 Setup Revise.jl",
- "text": "2.3 Setup Revise.jl\nRevise.jl is a library that allows you to modify code and use the changes without restarting Julia. You can let it start automatically by following these instructions."
- },
- {
- "objectID": "contribute/core.html#clone-ribasim",
- "href": "contribute/core.html#clone-ribasim",
- "title": "Julia core development",
- "section": "2.4 Clone Ribasim",
- "text": "2.4 Clone Ribasim\nIn order to have the Ribasim repository locally available, you can clone it with Git. Git can be installed from git-scm.com. Once installed, run the following command at a directory of your choice:\nIn order to have the Ribasim repository locally available, run the following command at a directory of your choice:\ngit clone https://github.com/Deltares/Ribasim.git\nTo continue with the following steps, make the root of the repository your working directory by running\ncd Ribasim"
- },
- {
- "objectID": "contribute/core.html#install-visual-studio-code-optional",
- "href": "contribute/core.html#install-visual-studio-code-optional",
- "title": "Julia core development",
- "section": "2.5 Install Visual Studio Code (optional)",
- "text": "2.5 Install Visual Studio Code (optional)\nThere is a section on editors and IDEs for Julia on https://julialang.org/, scroll down to see it. We use and recommend Microsoft’s free editor Visual Studio Code. When combined with the Julia extension it provides a powerful and interactive development experience."
- },
- {
- "objectID": "contribute/core.html#sec-test",
- "href": "contribute/core.html#sec-test",
- "title": "Julia core development",
- "section": "3.1 Running tests",
- "text": "3.1 Running tests\nYou will want to run the testsuite on a regular basis to check if your changes had unexpected side effects. It is also a good way to find out if your development environment is set up correctly.\nBefore the tests can run, you need to prepare model input.\nWith the root of the repository as your working directory you can start the REPL with activated Ribasim environment by running the following:\njulia --project\nWhile not technically required, it is advised to import Ribasim first to catch installation issues early on.\njulia> using Ribasim\nThen open the Pkg REPL by typing ] and execute:\npkg> test Ribasim\nIn order to debug tests, it is very useful to run them in a REPL. However, here, you don’t have the dependencies available in the [extras] section of your Project.toml. TestEnv.jl that we installed earlier solves that problem.\nWhen you then debug your tests inside the REPL, you can include the [extras] dependencies as follows:\nusing TestEnv,\nTestEnv.activate(\"Ribasim\")"
- },
- {
- "objectID": "contribute/core.html#render-documentation",
- "href": "contribute/core.html#render-documentation",
- "title": "Julia core development",
- "section": "3.2 Render documentation",
- "text": "3.2 Render documentation\nExample models are created and simulated as part of the rendering of the documentation. The Julia API reference is created using Documenter.jl by running this command:\npixi run build-julia-docs\nIn order to preview documentation you can run the following command from the docs/ folder. Afterwards, a browser tab will open with the rendered documentation, updating it as you make changes.\npixi run quarto-preview\nThe documentation also includes Jupyter notebooks. Note that they are stored in the repository without any output, and this should stay this way to keep the repository small. The documentation rendering process adds the output by running the notebooks.\n\n\n\n\n\n\nTip\n\n\n\nThe Jupyter VS Code extension allows you to run Jupyter notebooks directly in VS Code."
- },
- {
- "objectID": "contribute/core.html#run-ribasim-simulations",
- "href": "contribute/core.html#run-ribasim-simulations",
- "title": "Julia core development",
- "section": "3.3 Run Ribasim simulations",
- "text": "3.3 Run Ribasim simulations\nAssuming your working directory is the root of the repository, you can activate this project by entering the Pkg mode of the REPL with ] and execute:\npkg> activate .\npkg> instantiate\nPress backspace to go back to the Julia REPL. There you can run a model with:\njulia> Ribasim.run(\"path/to/model/ribasim.toml\")\n\n\n\n\n\n\nTip\n\n\n\nThe Julia VS Code extension allows you to execute code cells in REPL. This is a very convenient way of executing only parts of your source file."
+ "text": "1 Set up the developer environment\nFirst set up pixi as described in the Python tooling page.\nQGIS is installed with Pixi when running pixi run install. To further set up the development environment, you must add the plugins to QGIS. The simplest way to do this is by running pixi run install-qgis-plugins. It grabs the latest version of the iMOD QGIS plugin and it makes a symlink to the ribasim_qgis folder so that QGIS can find it.\n\n\n\n\n\n\nNote\n\n\n\nOn Windows you need to have Developer mode enabled. Otherwise you will not have enough access rights to create symlinks. For more info, see this Windows blog.\n\n\n\n\n2 Running QGIS\nIn order to run QGIS with the plugins, simply call pixi run qgis. You will find the Ribasim and iMOD plugins in the tool bars.\n\n\n\n\n\n\nNote\n\n\n\nOn Windows, running QGIS from the start menu will disable Python, and thus the plugins. QGIS needs some more paths during the startup and the Pixi environment provides those.\n\n\n\n\n3 Running tests\nTo run the QGIS plugin tests in the application environment of QGIS, it is best to make use of the Docker environment provided in this repository. Make sure that docker is installed and available in your path.\nThen simply call pixi run test-ribasim-qgis.\n\n\n4 Debugging\nAfter installing the plugins via pixi run install-qgis-plugins. Extra debugging tools are also installed in QGIS that is installed within your pixi environment.\nAfter you have started pixi run qgis, you can make alterations to the python code and use the Plugin Reloader to reload the plugin without restarting QGIS. The shortcut in QGIS is CTRL+F5.\nIt is also possible to connect the debugger of Visual Studio Code. For this the debugvs plugin is installed in QGIS. In QGIS press the button to Enable Debug for Visual Studio. Then go to Visual Studio Code and start the launch task Ribasim QGIS: Attach to QGIS. Now you can place breakpoints.\n\n\n\n\n\n\nNote\n\n\n\nWe are currently using debugvs 0.7 with ptvsd as service, since there is an open issue that breaks debugvs 0.8 with debugpy."
},
{
"objectID": "contribute/release.html",
@@ -763,185 +672,276 @@
"text": "2.4 Create a new release\nCreate a new release. Give it a tag like v2023.08.0, filling in the current year, month and a sequential “MICRO” number. This follows vYYYY.0M.MICRO from calver. For v2023.09.0 I used the “Generate release notes” button, which I then manually edited to copy the most important changes for users to the top in the form of Keep a Changelog. The possibly long list of generated release notes can put below an “All changes” collapsed item as such:\n<details>\n<summary>\nAll changes\n</summary>\n\n# Put Github flavored markdown here\n\n</details>"
},
{
- "objectID": "contribute/release.html#release-ribasim-python-to-pypi",
- "href": "contribute/release.html#release-ribasim-python-to-pypi",
- "title": "Release process",
- "section": "2.5 Release Ribasim Python to PyPI",
- "text": "2.5 Release Ribasim Python to PyPI\nTo be able to install packages with pip, they need to be released on the Python Package Index (PyPI). In order to publish Ribasim Python or Ribasim API follow the following steps:\n\nUpdate __version__ in __init__.py\nOpen a terminal and run pixi run build-ribasim-python-wheel or pixi run build-ribasim-api-wheel\nMake a new commit with the updated version number, and push to remote\nPublish on PyPI with pixi run publish-ribasim-python or pixi run publish-ribasim-api"
+ "objectID": "contribute/release.html#release-ribasim-python-to-pypi",
+ "href": "contribute/release.html#release-ribasim-python-to-pypi",
+ "title": "Release process",
+ "section": "2.5 Release Ribasim Python to PyPI",
+ "text": "2.5 Release Ribasim Python to PyPI\nTo be able to install packages with pip, they need to be released on the Python Package Index (PyPI). In order to publish Ribasim Python or Ribasim API follow the following steps:\n\nUpdate __version__ in __init__.py\nOpen a terminal and run pixi run build-ribasim-python-wheel or pixi run build-ribasim-api-wheel\nMake a new commit with the updated version number, and push to remote\nPublish on PyPI with pixi run publish-ribasim-python or pixi run publish-ribasim-api"
+ },
+ {
+ "objectID": "contribute/release.html#sec-teamcity",
+ "href": "contribute/release.html#sec-teamcity",
+ "title": "Release process",
+ "section": "2.6 Wait for TeamCity to build the new release",
+ "text": "2.6 Wait for TeamCity to build the new release\nCurrently TeamCity is set to build a release at the night after it has been tagged.\nIf this succeeds, the release assets are uploaded to an S3 link with the version number in the URL, as show here:\nhttps://ribasim.s3.eu-west-3.amazonaws.com/teamcity/Ribasim_Ribasim/BuildRibasimCliWindows/v2023.07.0/ribasim_cli.zip\nhttps://ribasim.s3.eu-west-3.amazonaws.com/teamcity/Ribasim_Ribasim/BuildRibasimCliWindows/v2023.07.0/ribasim-0.6.0-py3-none-any.whl\nhttps://ribasim.s3.eu-west-3.amazonaws.com/teamcity/Ribasim_Ribasim/BuildRibasimCliWindows/v2023.07.0/ribasim_qgis.zip\n\n\n\n\n\n\nNote\n\n\n\nA non-existent version number v2023.07.0 is used in these links. Replace with the version number of the new release. Similarly the filename of the Ribasim Python wheel needs to be updated as in Section 2.3."
+ },
+ {
+ "objectID": "contribute/release.html#do-manual-checks",
+ "href": "contribute/release.html#do-manual-checks",
+ "title": "Release process",
+ "section": "2.7 Do manual checks",
+ "text": "2.7 Do manual checks\nOur continuous integration (CI) should have caught most issues. A current weak spot in our testing is the QGIS plugin, so it is a good idea to do some manual checks to see if it works properly. It is a good idea to load new test models if there are any, or test any other changed functionality."
+ },
+ {
+ "objectID": "contribute/release.html#generate-and-upload-test-models",
+ "href": "contribute/release.html#generate-and-upload-test-models",
+ "title": "Release process",
+ "section": "2.8 Generate and upload test models",
+ "text": "2.8 Generate and upload test models\nThe test models are currently not automatically uploaded. Create them locally with:\npixi run generate-testmodels\nNote that this only includes the test model data, no results. And zip the generated_testmodels directory to generated_testmodels.zip, and add these to the release assets. Click the edit pencil icon to be able to upload it."
+ },
+ {
+ "objectID": "contribute/release.html#upload-artifacts-from-s3-to-github-release-assets",
+ "href": "contribute/release.html#upload-artifacts-from-s3-to-github-release-assets",
+ "title": "Release process",
+ "section": "2.9 Upload artifacts from S3 to GitHub release assets",
+ "text": "2.9 Upload artifacts from S3 to GitHub release assets\nAgain edit the release assets. Now upload the files downloaded from S3 as mentioned in Section 2.6."
+ },
+ {
+ "objectID": "contribute/release.html#announce-release",
+ "href": "contribute/release.html#announce-release",
+ "title": "Release process",
+ "section": "2.10 Announce release",
+ "text": "2.10 Announce release\nAnnounce the release in appropriate channels. Include a link to the release notes and assets, which is whatever this resolves to at that time. Also include a link to the documentation."
+ },
+ {
+ "objectID": "index.html",
+ "href": "index.html",
+ "title": "Ribasim",
+ "section": "",
+ "text": "Ribasim is a water resources model, designed to be the replacement of the regional surface water modules Mozart and SIMRES in the Netherlands Hydrological Instrument (NHI). Ribasim is a work in progress, it is a prototype that demonstrates all essential functionalities. Further development of the prototype in a software release is planned in 2022 and 2023.\nRibasim is written in the Julia programming language and is built on top of the SciML: Open Source Software for Scientific Machine Learning libraries, notably DifferentialEquations.jl."
+ },
+ {
+ "objectID": "index.html#water-balance-equations",
+ "href": "index.html#water-balance-equations",
+ "title": "Ribasim",
+ "section": "3.1 Water balance equations",
+ "text": "3.1 Water balance equations\nThe water balance equation for a drainage basin (Wikipedia contributors 2022) can be defined by a first-order ordinary differential equation (ODE), where the change of the storage \\(S\\) over time is determined by the inflow fluxes minus the outflow fluxes.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = Q_{in} - Q_{out}\n\\]\nWe can split out the fluxes into separate terms, such as precipitation \\(P\\), evapotranspiration \\(ET\\) and runoff \\(R\\). For now other fluxes are combined into \\(Q_{rest}\\). If we define all fluxes entering our reservoir as positive, and those leaving the system as negative, all fluxes can be summed up.\n\\[\n\\frac{\\mathrm{d}S}{\\mathrm{d}t} = R + P + ET + Q_{rest}\n\\]"
+ },
+ {
+ "objectID": "index.html#time",
+ "href": "index.html#time",
+ "title": "Ribasim",
+ "section": "3.2 Time",
+ "text": "3.2 Time\nThe water balance equation can be applied on many timescales; years, weeks, days or hours. Depending on the application and available data any of these can be the best choice. In Ribasim, we make use of DifferentialEquations.jl and its ODE solvers. Many of these solvers are based on adaptive time stepping, which means the solver will decide how large the time steps can be depending on the state of the system.\nThe forcing, like precipitation, is generally provided as a time series. Ribasim is set up to support unevenly spaced timeseries. The solver will stop on timestamps where new forcing values are available, so they can be loaded as the new value.\nRibasim is essentially a continuous model, rather than daily or hourly. If you want to use hourly forcing, you only need to make sure that your forcing data contains hourly updates. The output frequency can be configured independently. To be able to write a closed water balance, we accumulate the fluxes. This way any variations in between timesteps are also included, and we can output in m³ rather than m³s⁻¹."
+ },
+ {
+ "objectID": "index.html#sec-space",
+ "href": "index.html#sec-space",
+ "title": "Ribasim",
+ "section": "3.3 Space",
+ "text": "3.3 Space\nThe water balance equation can be applied on different spatial scales. Besides modelling a single lumped watershed, it allows you to divide the area into a network of connected representative elementary watersheds (REWs) (Reggiani, Sivapalan, and Majid Hassanizadeh 1998). At this scale global water balance laws can be formulated by means of integration of point-scale conservation equations over control volumes. Such an approach makes Ribasim a semi-distributed model. In this document we typically use the term “basin” to refer to the REW. (In Mozart the spatial unit was called Local Surface Water (LSW)). Each basin has an associated polygon, and the set of basins is connected to each other as described by a graph, which we call the network. Below is a representation of both on the map.\n\n\n\nMozart Local Surface Water polygons and their drainage.\n\n\nThe network is described as graph. Flow can be bi-directional, and the graph does not have to be acyclic.\n\n\n\n\ngraph LR;\n A[\"basin A\"] --- B[\"basin B\"];\n A --- C[\"basin C\"];\n B --- D[\"basin D\"];\n C --- D;\n\n\n\n\n\nInternally a directed graph is used. The direction is defined to be the positive flow direction, and is generally set in the dominant flow direction. The basins are the nodes of the network graph. Basin states and properties such storage volume and wetted area are associated with the nodes (A, B, C, D), as are most forcing data such as precipitation, evaporation, or water demand. Basin connection properties and interbasin flows are associated with the edges (the lines between A, B, C, and D) instead.\nMultiple basins may exist within the same spatial polygon, representing different aspects of the surface water system (perennial ditches, ephemeral ditches, or even surface ponding). Figure 1, Figure 2, Figure 3 show the 25.0 m rasterized primary, secondary, and tertiary surface waters as identified by BRT TOP10NL (PDOK 2022) in the Hupsel basin (as defined in the Mozart LSW’s). These systems may represented in multiple ways.\n\n\n\nFigure 1: Hupsel: primary surface water.\n\n\n\n\n\nFigure 2: Hupsel: secondary surface water.\n\n\n\n\n\nFigure 3: Hupsel: tertiary surface water.\n\n\nAs a single basin (A) containing all surface water, discharging to its downstream basin to the west (B):\n\n\n\n\ngraph LR;\n A[\"basin A\"] --> B[\"basin B\"];\n\n\n\n\n\nSuch a system may be capable of representing discharge, but it cannot represent residence times or differences in solute concentrations: within a single basin, drop of water is mixed instantaneously. Instead, we may the group primary (P), secondary (S), and tertiary (T) surface waters. Then T may flow into S, S into P, and P discharges to the downstream basin (B.)\n\n\n\n\ngraph LR;\n T[\"basin T\"] --> S[\"basin S\"];\n S --> P[\"basin P\"];\n P --> B[\"basin B\"];\n\n\n\n\n\nAs each (sub)basin has its own volume, low throughput (high volume, low discharge, long residence time) and high throughput (low volume, high discharge, short residence time) systems can be represented in a lumped manner; of course, more detail requires more parameters."
+ },
+ {
+ "objectID": "build/index.html#modules",
+ "href": "build/index.html#modules",
+ "title": "1 API Reference",
+ "section": "1.1 Modules",
+ "text": "1.1 Modules\n# Ribasim.Ribasim — Module.\nmodule Ribasim\nRibasim is a water resources model. The computational core is implemented in Julia in the Ribasim package. It is currently mainly designed to be used as an application. To run a simulation from Julia, use Ribasim.run.\nFor more granular access, see:\n\nConfig\nModel\nsolve!\nBMI.finalize\n\nsource\n# Ribasim.config — Module.\nmodule config\nRibasim.config is a submodule of Ribasim to handle the configuration of a Ribasim model. It is implemented using the Configurations package. A full configuration is represented by Config, which is the main API. Ribasim.config is a submodule mainly to avoid name clashes between the configuration sections and the rest of Ribasim.\nsource"
+ },
+ {
+ "objectID": "build/index.html#types",
+ "href": "build/index.html#types",
+ "title": "1 API Reference",
+ "section": "1.2 Types",
+ "text": "1.2 Types\n# Ribasim.AllocationModel — Type.\nStore information for a subnetwork used for allocation.\nobjectivetype: The name of the type of objective used allocationnetworkid: The ID of this allocation network capacity: The capacity per edge of the allocation graph, as constrained by nodes that have a maxflowrate problem: The JuMP.jl model for solving the allocation problem Δtallocation: The time interval between consecutive allocation solves\nsource\n# Ribasim.AllocationModel — Method.\nConstruct the JuMP.jl problem for allocation.\nInputs\nconfig: The model configuration with allocation configuration in config.allocation p: Ribasim problem parameters Δt_allocation: The timestep between successive allocation solves\nOutputs\nAn AllocationModel object.\nsource\n# Ribasim.Basin — Type.\nRequirements:\n\nMust be positive: precipitation, evaporation, infiltration, drainage\nIndex points to a Basin\nvolume, area, level must all be positive and monotonic increasing.\n\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of vectors or Arrow Tables, and is added to avoid type instabilities. The nodeid are Indices to support fast lookup of e.g. currentlevel using ID.\nif autodiff T = DiffCache{Vector{Float64}} else T = Vector{Float64} end\nsource\n# Ribasim.DiscreteControl — Type.\nnodeid: node ID of the DiscreteControl node; these are not unique but repeated by the amount of conditions of this DiscreteControl node listennodeid: the ID of the node being condition on variable: the name of the variable in the condition greaterthan: The threshold value in the condition conditionvalue: The current value of each condition controlstate: Dictionary: node ID => (control state, control state start) logic_mapping: Dictionary: (control node ID, truth state) => control state record: Namedtuple with discrete control information for results\nsource\n# Ribasim.EdgeMetadata — Type.\nType for storing metadata of edges in the graph: id: ID of the edge (only used for labeling flow output) type: type of the edge allocationnetworkidsource: ID of allocation network where this edge is a source (0 if not a source) fromid: the node ID of the source node toid: the node ID of the destination node allocationflow: whether this edge has a flow in an allocation graph\nsource\n# Ribasim.FlatVector — Type.\nstruct FlatVector{T} <: AbstractVector{T}\nA FlatVector is an AbstractVector that iterates the T of a Vector{Vector{T}}.\nEach inner vector is assumed to be of equal length.\nIt is similar to Iterators.flatten, though that doesn’t work with the Tables.Column interface, which needs length and getindex support.\nsource\n# Ribasim.FlowBoundary — Type.\nnodeid: node ID of the FlowBoundary node active: whether this node is active and thus contributes flow flowrate: target flow rate\nsource\n# Ribasim.FractionalFlow — Type.\nRequirements:\n\nfrom: must be (TabulatedRatingCurve,) node\nto: must be (Basin,) node\nfraction must be positive.\n\nnodeid: node ID of the TabulatedRatingCurve node fraction: The fraction in [0,1] of flow the node lets through controlmapping: dictionary from (nodeid, controlstate) to fraction\nsource\n# Ribasim.InNeighbors — Type.\nIterate over incoming neighbors of a given label in a MetaGraph, only for edges of edge_type\nsource\n# Ribasim.LevelBoundary — Type.\nnode_id: node ID of the LevelBoundary node active: whether this node is active level: the fixed level of this ‘infinitely big basin’\nsource\n# Ribasim.LinearResistance — Type.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\n\nnodeid: node ID of the LinearResistance node active: whether this node is active and thus contributes flows resistance: the resistance to flow; Q = Δh/resistance controlmapping: dictionary from (nodeid, controlstate) to resistance and/or active state\nsource\n# Ribasim.ManningResistance — Type.\nThis is a simple Manning-Gauckler reach connection.\n\nLength describes the reach length.\nroughness describes Manning’s n in (SI units).\n\nThe profile is described by a trapezoid:\n \\ / ^\n \\ / |\n \\ / | dz\nbottom \\______/ |\n^ <--->\n| dy\n| <------>\n| width\n|\n|\n+ datum (e.g. MSL)\nWith profile_slope = dy / dz. A rectangular profile requires a slope of 0.0.\nRequirements:\n\nfrom: must be (Basin,) node\nto: must be (Basin,) node\nlength > 0\nroughess > 0\nprofile_width >= 0\nprofile_slope >= 0\n(profilewidth == 0) xor (profileslope == 0)\n\nsource\n# Ribasim.Model — Type.\nModel(config_path::AbstractString)\nModel(config::Config)\nInitialize a Model.\nThe Model struct is an initialized model, combined with the Config used to create it and saved results. The Basic Model Interface (BMI) is implemented on the Model. A Model can be created from the path to a TOML configuration file, or a Config object.\nsource\n# Ribasim.NodeMetadata — Type.\nType for storing metadata of nodes in the graph type: type of the node allocationnetworkid: Allocation network ID (0 if not in subnetwork)\nsource\n# Ribasim.OutNeighbors — Type.\nIterate over outgoing neighbors of a given label in a MetaGraph, only for edges of edge_type\nsource\n# Ribasim.Outlet — Type.\nnodeid: node ID of the Outlet node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the outlet maxflowrate: The maximum flow rate of the outlet controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this outlet is governed by PID control\nsource\n# Ribasim.PidControl — Type.\nPID control currently only supports regulating basin levels.\nnodeid: node ID of the PidControl node active: whether this node is active and thus sets flow rates listennodeid: the id of the basin being controlled pidparams: a vector interpolation for parameters changing over time. The parameters are respectively target, proportional, integral, derivative, where the last three are the coefficients for the PID equation. error: the current error; basintarget - currentlevel\nsource\n# Ribasim.Pump — Type.\nnodeid: node ID of the Pump node active: whether this node is active and thus contributes flow flowrate: target flow rate minflowrate: The minimal flow rate of the pump maxflowrate: The maximum flow rate of the pump controlmapping: dictionary from (nodeid, controlstate) to target flow rate ispid_controlled: whether the flow rate of this pump is governed by PID control\nsource\n# Ribasim.Subgrid — Type.\nSubgrid linearly interpolates basin levels.\nsource\n# Ribasim.TabulatedRatingCurve — Type.\nstruct TabulatedRatingCurve{C}\nRating curve from level to discharge. The rating curve is a lookup table with linear interpolation in between. Relation can be updated in time, which is done by moving data from the time field into the tables, which is done in the update_tabulated_rating_curve callback.\nType parameter C indicates the content backing the StructVector, which can be a NamedTuple of Vectors or Arrow Primitives, and is added to avoid type instabilities.\nnodeid: node ID of the TabulatedRatingCurve node active: whether this node is active and thus contributes flows tables: The current Q(h) relationships time: The time table used for updating the tables controlmapping: dictionary from (nodeid, controlstate) to Q(h) and/or active state\nsource\n# Ribasim.Terminal — Type.\nnode_id: node ID of the Terminal node\nsource\n# Ribasim.User — Type.\ndemand: water flux demand of user per priority over time active: whether this node is active and thus demands water allocated: water flux currently allocated to user per priority returnfactor: the factor in [0,1] of how much of the abstracted water is given back to the system minlevel: The level of the source basin below which the user does not abstract priorities: All used priority values. Each user has a demand for all these priorities, which is 0.0 if it is not provided explicitly. record: Collected data of allocation optimizations for output file.\nsource\n# Ribasim.config.Config — Method.\nConfig(config_path::AbstractString; kwargs...)\nParse a TOML file to a Config. Keys can be overruled using keyword arguments. To overrule keys from a subsection, e.g. dt from the solver section, use underscores: solver_dt.\nsource"
+ },
+ {
+ "objectID": "build/index.html#functions",
+ "href": "build/index.html#functions",
+ "title": "1 API Reference",
+ "section": "1.3 Functions",
+ "text": "1.3 Functions\n# BasicModelInterface.finalize — Method.\nBMI.finalize(model::Model)::Model\nWrite all results to the configured files.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config_path::AbstractString)::Model\nInitialize a Model from the path to the TOML configuration file.\nsource\n# BasicModelInterface.initialize — Method.\nBMI.initialize(T::Type{Model}, config::Config)::Model\nInitialize a Model from a Config.\nsource\n# CommonSolve.solve! — Method.\nsolve!(model::Model)::ODESolution\nSolve a Model until the configured endtime.\nsource\n# Ribasim.add_constraints_absolute_value! — Method.\nMinimizing |expr| can be achieved by introducing a new variable exprabs and posing the following constraints: exprabs >= expr expr_abs >= -expr\nsource\n# Ribasim.add_constraints_capacity! — Method.\nAdd the flow capacity constraints to the allocation problem. Only finite capacities get a constraint. The constraint indices are (edgesourceid, edgedstid).\nConstraint: flow over edge <= edge capacity\nsource\n# Ribasim.add_constraints_flow_conservation! — Method.\nAdd the flow conservation constraints to the allocation problem. The constraint indices are user node IDs.\nConstraint: sum(flows out of node node) <= flows into node + flow from storage and vertical fluxes\nsource\n# Ribasim.add_constraints_source! — Method.\nAdd the source constraints to the allocation problem. The actual threshold values will be set before each allocation solve. The constraint indices are (edgesourceid, edgedstid).\nConstraint: flow over source edge <= source flow in subnetwork\nsource\n# Ribasim.add_constraints_user_returnflow! — Method.\nAdd the user returnflow constraints to the allocation problem. The constraint indices are user node IDs.\nConstraint: outflow from user <= return factor * inflow to user\nsource\n# Ribasim.add_flow! — Method.\nAdd the given flow q to the flow over the edge on the horizontal (self-loop) edge from id to id.\nsource\n# Ribasim.add_flow! — Method.\nAdd the given flow q to the existing flow over the edge between the given nodes.\nsource\n# Ribasim.add_variables_absolute_value! — Method.\nCertain allocation distribution types use absolute values in the objective function. Since most optimization packages do not support the absolute value function directly, New variables are introduced that act as the absolute value of an expression by posing the appropriate constraints.\nsource\n# Ribasim.add_variables_flow! — Method.\nAdd the flow variables F to the allocation problem. The variable indices are (edgesourceid, edgedstid). Non-negativivity constraints are also immediately added to the flow variables.\nsource\n# Ribasim.adjust_edge_capacities! — Method.\nSet the values of the edge capacities. 2 cases:\n\nBefore the first allocation solve, set the edge capacities to their full capacity;\nBefore an allocation solve, subtract the flow used by allocation for the previous priority from the edge capacities.\n\nsource\n# Ribasim.adjust_source_flows! — Method.\nAdjust the source flows.\nsource\n# Ribasim.all_neighbor_labels_type — Method.\nGet the in- and outneighbor node IDs of the given node ID (label) over the given edge type in the graph.\nsource\n# Ribasim.allocate! — Method.\nUpdate the allocation optimization problem for the given subnetwork with the problem state and flows, solve the allocation problem and assign the results to the users.\nsource\n# Ribasim.allocation_graph — Method.\nBuild the graph used for the allocation problem.\nsource\n# Ribasim.allocation_graph_used_nodes! — Method.\nFind all nodes in the subnetwork which will be used in the allocation network. Some nodes are skipped to optimize allocation optimization.\nsource\n# Ribasim.allocation_path_exists_in_graph — Method.\nFind out whether a path exists between a start node and end node in the given allocation graph.\nsource\n# Ribasim.allocation_problem — Method.\nConstruct the allocation problem for the current subnetwork as a JuMP.jl model.\nsource\n# Ribasim.allocation_table — Method.\nCreate an allocation result table for the saved data\nsource\n# Ribasim.assign_allocations! — Method.\nAssign the allocations to the users as determined by the solution of the allocation problem.\nsource\n# Ribasim.avoid_using_own_returnflow! — Method.\nRemove allocation user return flow edges that are upstream of the user itself.\nsource\n# Ribasim.basin_bottom — Method.\nReturn the bottom elevation of the basin with index i, or nothing if it doesn’t exist\nsource\n# Ribasim.basin_bottoms — Method.\nGet the bottom on both ends of a node. If only one has a bottom, use that for both.\nsource\n# Ribasim.basin_table — Method.\nCreate the basin result table from the saved data\nsource\n# Ribasim.create_callbacks — Method.\nCreate the different callbacks that are used to store results and feed the simulation with new data. The different callbacks are combined to a CallbackSet that goes to the integrator. Returns the CallbackSet and the SavedValues for flow.\nsource\n# Ribasim.create_graph — Method.\nReturn a directed metagraph with data of nodes (NodeMetadata): NodeMetadata\nand data of edges (EdgeMetadata): EdgeMetadata\nsource\n# Ribasim.create_storage_tables — Method.\nRead the Basin / profile table and return all area and level and computed storage values\nsource\n# Ribasim.datetime_since — Method.\ndatetime_since(t::Real, t0::DateTime)::DateTime\nConvert a Real that represents the seconds passed since the simulation start to the nearest DateTime. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.datetimes — Method.\nGet all saved times as a Vector{DateTime}\nsource\n# Ribasim.discrete_control_affect! — Method.\nChange parameters based on the control logic.\nsource\n# Ribasim.discrete_control_affect_downcrossing! — Method.\nAn downcrossing means that a condition (always greater than) becomes false.\nsource\n# Ribasim.discrete_control_affect_upcrossing! — Method.\nAn upcrossing means that a condition (always greater than) becomes true.\nsource\n# Ribasim.discrete_control_condition — Method.\nListens for changes in condition truths.\nsource\n# Ribasim.discrete_control_table — Method.\nCreate a discrete control result table from the saved data\nsource\n# Ribasim.expand_logic_mapping — Method.\nReplace the truth states in the logic mapping which contain wildcards with all possible explicit truth states.\nsource\n# Ribasim.find_allocation_graph_edges! — Method.\nThis loop finds allocation graph edges in several ways:\n\nBetween allocation graph nodes whose equivalent in the subnetwork are directly connected\nBetween allocation graph nodes whose equivalent in the subnetwork are connected with one or more allocation graph nodes in between\n\nsource\n# Ribasim.findlastgroup — Method.\nFor an element id and a vector of elements ids, get the range of indices of the last consecutive block of id. Returns the empty range 1:0 if id is not in ids.\n# 1 2 3 4 5 6 7 8 9\nRibasim.findlastgroup(2, [5,4,2,2,5,2,2,2,1])\n# output\n6:8\nsource\n# Ribasim.findsorted — Method.\nFind the index of element x in a sorted collection a. Returns the index of x if it exists, or nothing if it doesn’t. If x occurs more than once, throw an error.\nsource\n# Ribasim.flow_table — Method.\nCreate a flow result table from the saved data\nsource\n# Ribasim.formulate_basins! — Method.\nSmoothly let the evaporation flux go to 0 when at small water depths Currently at less than 0.1 m.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.formulate_flow! — Method.\nConservation of energy for two basins, a and b:\nh_a + v_a^2 / (2 * g) = h_b + v_b^2 / (2 * g) + S_f * L + C / 2 * g * (v_b^2 - v_a^2)\nWhere:\n\nha, hb are the heads at basin a and b.\nva, vb are the velocities at basin a and b.\ng is the gravitational constant.\nS_f is the friction slope.\nC is an expansion or extraction coefficient.\n\nWe assume velocity differences are negligible (va = vb):\nh_a = h_b + S_f * L\nThe friction losses are approximated by the Gauckler-Manning formula:\nQ = A * (1 / n) * R_h^(2/3) * S_f^(1/2)\nWhere:\n\nWhere A is the cross-sectional area.\nV is the cross-sectional average velocity.\nn is the Gauckler-Manning coefficient.\nR_h is the hydraulic radius.\nS_f is the friction slope.\n\nThe hydraulic radius is defined as:\nR_h = A / P\nWhere P is the wetted perimeter.\nThe average of the upstream and downstream water depth is used to compute cross-sectional area and hydraulic radius. This ensures that a basin can receive water after it has gone dry.\nsource\n# Ribasim.formulate_flow! — Method.\nDirected graph: outflow is positive!\nsource\n# Ribasim.get_area_and_level — Method.\nCompute the area and level of a basin given its storage. Also returns darea/dlevel as it is needed for the Jacobian.\nsource\n# Ribasim.get_chunk_sizes — Method.\nGet the chunk sizes for DiffCache; differentiation w.r.t. u and t (the latter only if a Rosenbrock algorithm is used).\nsource\n# Ribasim.get_compressor — Method.\nGet the compressor based on the Results section\nsource\n# Ribasim.get_flow — Method.\nGet the flow over the given horizontal (selfloop) edge (val is needed for get_tmp from ForwardDiff.jl).\nsource\n# Ribasim.get_flow — Method.\nGet the flow over the given edge (val is needed for get_tmp from ForwardDiff.jl).\nsource\n# Ribasim.get_fractional_flow_connected_basins — Method.\nGet the node type specific indices of the fractional flows and basins, that are consecutively connected to a node of given id.\nsource\n# Ribasim.get_jac_prototype — Method.\nGet a sparse matrix whose sparsity matches the sparsity of the Jacobian of the ODE problem. All nodes are taken into consideration, also the ones that are inactive.\nIn Ribasim the Jacobian is typically sparse because each state only depends on a small number of other states.\nNote: the name ‘prototype’ does not mean this code is a prototype, it comes from the naming convention of this sparsity structure in the differentialequations.jl docs.\nsource\n# Ribasim.get_level — Method.\nGet the current water level of a node ID. The ID can belong to either a Basin or a LevelBoundary. storage: tells ForwardDiff whether this call is for differentiation or not\nsource\n# Ribasim.get_scalar_interpolation — Method.\nLinear interpolation of a scalar with constant extrapolation.\nsource\n# Ribasim.get_storage_from_level — Method.\nGet the storage of a basin from its level.\nsource\n# Ribasim.get_storages_and_levels — Method.\nGet the storage and level of all basins as matrices of nbasin × ntime\nsource\n# Ribasim.get_storages_from_levels — Method.\nCompute the storages of the basins based on the water level of the basins.\nsource\n# Ribasim.get_tstops — Method.\nFrom an iterable of DateTimes, find the times the solver needs to stop\nsource\n# Ribasim.get_value — Method.\nGet a value for a condition. Currently supports getting levels from basins and flows from flow boundaries.\nsource\n# Ribasim.id_index — Method.\nGet the index of an ID in a set of indices.\nsource\n# Ribasim.indicate_allocation_flow! — Method.\nAdd to the edge metadata that the given edge is used for allocation flow. If the edge does not exist, it is created.\nsource\n# Ribasim.inflow_id — Method.\nGet the unique inneighbor over a flow edge.\nsource\n# Ribasim.inflow_ids — Method.\nGet the inneighbors over flow edges.\nsource\n# Ribasim.inflow_ids_allocation — Method.\nGet the inneighbors of the given ID such that the connecting edge is an allocation flow edge.\nsource\n# Ribasim.inneighbor_labels_type — Method.\nGet the inneighbor node IDs of the given node ID (label) over the given edge type in the graph.\nsource\n# Ribasim.inoutflow_ids — Method.\nGet the in- and outneighbors over flow edges.\nsource\n# Ribasim.is_allocation_source — Method.\nFind out whether the given edge is a source for an allocation network.\nsource\n# Ribasim.is_flow_constraining — Method.\nWhether the given node node is flow constraining by having a maximum flow rate.\nsource\n# Ribasim.is_flow_direction_constraining — Method.\nWhether the given node is flow direction constraining (only in direction of edges).\nsource\n# Ribasim.load_data — Method.\nload_data(db::DB, config::Config, nodetype::Symbol, kind::Symbol)::Union{Table, Query, Nothing}\nLoad data from Arrow files if available, otherwise the database. Returns either an Arrow.Table, SQLite.Query or nothing if the data is not present.\nsource\n# Ribasim.load_structvector — Method.\nload_structvector(db::DB, config::Config, ::Type{T})::StructVector{T}\nLoad data from Arrow files if available, otherwise the database. Always returns a StructVector of the given struct type T, which is empty if the table is not found. This function validates the schema, and enforces the required sort order.\nsource\n# Ribasim.low_storage_factor — Method.\nIf id is a Basin with storage below the threshold, return a reduction factor != 1\nsource\n# Ribasim.metadata_from_edge — Method.\nGet the metadata of an edge in the graph from an edge of the underlying DiGraph.\nsource\n# Ribasim.nodefields — Method.\nGet all node fieldnames of the parameter object.\nsource\n# Ribasim.nodetype — Method.\nFrom a SchemaVersion(“ribasim.flowboundary.static”, 1) return (:FlowBoundary, :static)\nsource\n# Ribasim.outflow_id — Method.\nGet the unique outneighbor over a flow edge.\nsource\n# Ribasim.outflow_ids — Method.\nGet the outneighbors over flow edges.\nsource\n# Ribasim.outflow_ids_allocation — Method.\nGet the outneighbors of the given ID such that the connecting edge is an allocation flow edge.\nsource\n# Ribasim.outneighbor_labels_type — Method.\nGet the outneighbor node IDs of the given node ID (label) over the given edge type in the graph.\nsource\n# Ribasim.parse_static_and_time — Method.\nProcess the data in the static and time tables for a given node type. The ‘defaults’ named tuple dictates how missing data is filled in. ‘time_interpolatables’ is a vector of Symbols of parameter names for which a time interpolation (linear) object must be constructed. The control mapping for DiscreteControl is also constructed in this function. This function currently does not support node states that are defined by more than one row in a table, as is the case for TabulatedRatingCurve.\nsource\n# Ribasim.process_allocation_graph_edges! — Method.\nFor the composite allocation graph edges:\n\nFind out whether they are connected to allocation graph nodes on both ends\nCompute their capacity\nFind out their allowed flow direction(s)\n\nsource\n# Ribasim.profile_storage — Method.\nCalculate a profile storage by integrating the areas over the levels\nsource\n# Ribasim.qh_interpolation — Method.\nFrom a table with columns nodeid, discharge (Q) and level (h), create a LinearInterpolation from level to discharge for a given nodeid.\nsource\n# Ribasim.reduction_factor — Method.\nFunction that goes smoothly from 0 to 1 in the interval [0,threshold], and is constant outside this interval.\nsource\n# Ribasim.run — Method.\nrun(config_file::AbstractString)::Model\nrun(config::Config)::Model\nRun a Model, given a path to a TOML configuration file, or a Config object. Running a model includes initialization, solving to the end with [solve!](@ref) and writing results with BMI.finalize.\nsource\n# Ribasim.save_flow — Method.\nCopy the current flow to the SavedValues\nsource\n# Ribasim.save_subgrid_level — Method.\nInterpolate the levels and save them to SavedValues\nsource\n# Ribasim.scalar_interpolation_derivative — Method.\nDerivative of scalar interpolation.\nsource\n# Ribasim.seconds_since — Method.\nseconds_since(t::DateTime, t0::DateTime)::Float64\nConvert a DateTime to a float that is the number of seconds since the start of the simulation. This is used to convert between the solver’s inner float time, and the calendar.\nsource\n# Ribasim.set_current_value! — Method.\nFrom a timeseries table time, load the most recent applicable data into table. table must be a NamedTuple of vectors with all variables that must be loaded. The most recent applicable data is non-NaN data for a given ID that is on or before t.\nsource\n# Ribasim.set_flow! — Method.\nSet the given flow q on the horizontal (self-loop) edge from id to id.\nsource\n# Ribasim.set_flow! — Method.\nSet the given flow q over the edge between the given nodes.\nsource\n# Ribasim.set_initial_discrete_controlled_parameters! — Method.\nSet parameters of nodes that are controlled by DiscreteControl to the values corresponding to the initial state of the model.\nsource\n# Ribasim.set_objective_priority! — Method.\nSet the objective for the given priority. For an objective with absolute values this also involves adjusting constraints.\nsource\n# Ribasim.set_static_value! — Method.\nLoad data from a source table static into a destination table. Data is matched based on the node_id, which is sorted.\nsource\n# Ribasim.set_table_row! — Method.\nUpdate table at row index i, with the values of a given row. table must be a NamedTuple of vectors with all variables that must be loaded. The row must contain all the column names that are present in the table. If a value is NaN, it is not set.\nsource\n# Ribasim.sorted_table! — Method.\nDepending on if a table can be sorted, either sort it or assert that it is sorted.\nTables loaded from the database into memory can be sorted. Tables loaded from Arrow files are memory mapped and can therefore not be sorted.\nsource\n# Ribasim.timesteps — Method.\nGet all saved times in seconds since start\nsource\n# Ribasim.update_allocation! — Method.\nSolve the allocation problem for all users and assign allocated abstractions to user nodes.\nsource\n# Ribasim.update_basin — Method.\nLoad updates from ‘Basin / time’ into the parameters\nsource\n# Ribasim.update_jac_prototype! — Method.\nMethod for nodes that do not contribute to the Jacobian\nsource\n# Ribasim.update_jac_prototype! — Method.\nThe controlled basin affects itself and the basins upstream and downstream of the controlled pump affect eachother if there is a basin upstream of the pump. The state for the integral term and the controlled basin affect eachother, and the same for the integral state and the basin upstream of the pump if it is indeed a basin.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the unique node downstream of these nodes are basins, then these directly depend on eachother and affect the Jacobian 2x Basins always depend on themselves.\nsource\n# Ribasim.update_jac_prototype! — Method.\nIf both the unique node upstream and the nodes down stream (or one node further if a fractional flow is in between) are basins, then the downstream basin depends on the upstream basin(s) and affect the Jacobian as many times as there are downstream basins Upstream basins always depend on themselves.\nsource\n# Ribasim.update_tabulated_rating_curve! — Method.\nLoad updates from ‘TabulatedRatingCurve / time’ into the parameters\nsource\n# Ribasim.valid_discrete_control — Method.\nCheck:\n\nwhether control states are defined for discrete controlled nodes;\nWhether the supplied truth states have the proper length;\nWhether look_ahead is only supplied for condition variables given by a time-series.\n\nsource\n# Ribasim.valid_edge_types — Method.\nCheck that only supported edge types are declared.\nsource\n# Ribasim.valid_edges — Method.\nTest for each node given its node type whether the nodes that\nare downstream (‘down-edge’) of this node are of an allowed type\nsource\n# Ribasim.valid_flow_rates — Method.\nTest whether static or discrete controlled flow rates are indeed non-negative.\nsource\n# Ribasim.valid_fractional_flow — Method.\nCheck that nodes that have fractional flow outneighbors do not have any other type of outneighbor, that the fractions leaving a node add up to ≈1 and that the fractions are non-negative.\nsource\n# Ribasim.valid_n_neighbors — Method.\nTest for each node given its node type whether it has an allowed number of flow/control inneighbors and outneighbors\nsource\n# Ribasim.valid_profiles — Method.\nCheck whether the profile data has no repeats in the levels and the areas start positive.\nsource\n# Ribasim.valid_sources — Method.\nThe source nodes must only have one allocation outneighbor and no allocation inneighbors.\nsource\n# Ribasim.valid_subgrid — Method.\nValidate the entries for a single subgrid element.\nsource\n# Ribasim.water_balance! — Method.\nThe right hand side function of the system of ODEs set up by Ribasim.\nsource\n# Ribasim.write_arrow — Method.\nWrite a result table to disk as an Arrow file\nsource\n# Ribasim.config.algorithm — Method.\nCreate an OrdinaryDiffEqAlgorithm from solver config\nsource\n# Ribasim.config.input_path — Method.\nConstruct a path relative to both the TOML directory and the optional input_dir\nsource\n# Ribasim.config.results_path — Method.\nConstruct a path relative to both the TOML directory and the optional results_dir\nsource\n# Ribasim.config.snake_case — Method.\nConvert a string from CamelCase to snake_case.\nsource"
+ },
+ {
+ "objectID": "build/index.html#constants",
+ "href": "build/index.html#constants",
+ "title": "1 API Reference",
+ "section": "1.4 Constants",
+ "text": "1.4 Constants\n# Ribasim.config.algorithms — Constant.\nconst algorithms::Dict{String, Type}\nMap from config string to a supported algorithm type from OrdinaryDiffEq.\nSupported algorithms:\n\nQNDF\nRosenbrock23\nTRBDF2\nRodas5\nKenCarp4\nTsit5\nRK4\nImplicitEuler\nEuler\n\nsource"
},
{
- "objectID": "contribute/release.html#sec-teamcity",
- "href": "contribute/release.html#sec-teamcity",
- "title": "Release process",
- "section": "2.6 Wait for TeamCity to build the new release",
- "text": "2.6 Wait for TeamCity to build the new release\nCurrently TeamCity is set to build a release at the night after it has been tagged.\nIf this succeeds, the release assets are uploaded to an S3 link with the version number in the URL, as show here:\nhttps://ribasim.s3.eu-west-3.amazonaws.com/teamcity/Ribasim_Ribasim/BuildRibasimCliWindows/v2023.07.0/ribasim_cli.zip\nhttps://ribasim.s3.eu-west-3.amazonaws.com/teamcity/Ribasim_Ribasim/BuildRibasimCliWindows/v2023.07.0/ribasim-0.6.0-py3-none-any.whl\nhttps://ribasim.s3.eu-west-3.amazonaws.com/teamcity/Ribasim_Ribasim/BuildRibasimCliWindows/v2023.07.0/ribasim_qgis.zip\n\n\n\n\n\n\nNote\n\n\n\nA non-existent version number v2023.07.0 is used in these links. Replace with the version number of the new release. Similarly the filename of the Ribasim Python wheel needs to be updated as in Section 2.3."
+ "objectID": "build/index.html#macros",
+ "href": "build/index.html#macros",
+ "title": "1 API Reference",
+ "section": "1.5 Macros",
+ "text": "1.5 Macros\n# Ribasim.config.@addfields — Macro.\nAdd fieldnames with Union{String, Nothing} type to struct expression. Requires (option?) use before it.\nsource\n# Ribasim.config.@addnodetypes — Macro.\nAdd all TableOption subtypes as fields to struct expression. Requires (option?) use before it.\nsource"
},
{
- "objectID": "contribute/release.html#do-manual-checks",
- "href": "contribute/release.html#do-manual-checks",
- "title": "Release process",
- "section": "2.7 Do manual checks",
- "text": "2.7 Do manual checks\nOur continuous integration (CI) should have caught most issues. A current weak spot in our testing is the QGIS plugin, so it is a good idea to do some manual checks to see if it works properly. It is a good idea to load new test models if there are any, or test any other changed functionality."
+ "objectID": "build/index.html#index",
+ "href": "build/index.html#index",
+ "title": "1 API Reference",
+ "section": "1.6 Index",
+ "text": "1.6 Index\n\nRibasim.Ribasim\nRibasim.config\nRibasim.config.algorithms\nRibasim.AllocationModel\nRibasim.AllocationModel\nRibasim.Basin\nRibasim.DiscreteControl\nRibasim.EdgeMetadata\nRibasim.FlatVector\nRibasim.FlowBoundary\nRibasim.FractionalFlow\nRibasim.InNeighbors\nRibasim.LevelBoundary\nRibasim.LinearResistance\nRibasim.ManningResistance\nRibasim.Model\nRibasim.NodeMetadata\nRibasim.OutNeighbors\nRibasim.Outlet\nRibasim.PidControl\nRibasim.Pump\nRibasim.Subgrid\nRibasim.TabulatedRatingCurve\nRibasim.Terminal\nRibasim.User\nRibasim.config.Config\nBasicModelInterface.finalize\nBasicModelInterface.initialize\nBasicModelInterface.initialize\nCommonSolve.solve!\nRibasim.add_constraints_absolute_value!\nRibasim.add_constraints_capacity!\nRibasim.add_constraints_flow_conservation!\nRibasim.add_constraints_source!\nRibasim.add_constraints_user_returnflow!\nRibasim.add_flow!\nRibasim.add_flow!\nRibasim.add_variables_absolute_value!\nRibasim.add_variables_flow!\nRibasim.adjust_edge_capacities!\nRibasim.adjust_source_flows!\nRibasim.all_neighbor_labels_type\nRibasim.allocate!\nRibasim.allocation_graph\nRibasim.allocation_graph_used_nodes!\nRibasim.allocation_path_exists_in_graph\nRibasim.allocation_problem\nRibasim.allocation_table\nRibasim.assign_allocations!\nRibasim.avoid_using_own_returnflow!\nRibasim.basin_bottom\nRibasim.basin_bottoms\nRibasim.basin_table\nRibasim.config.algorithm\nRibasim.config.input_path\nRibasim.config.results_path\nRibasim.config.snake_case\nRibasim.create_callbacks\nRibasim.create_graph\nRibasim.create_storage_tables\nRibasim.datetime_since\nRibasim.datetimes\nRibasim.discrete_control_affect!\nRibasim.discrete_control_affect_downcrossing!\nRibasim.discrete_control_affect_upcrossing!\nRibasim.discrete_control_condition\nRibasim.discrete_control_table\nRibasim.expand_logic_mapping\nRibasim.find_allocation_graph_edges!\nRibasim.findlastgroup\nRibasim.findsorted\nRibasim.flow_table\nRibasim.formulate_basins!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.formulate_flow!\nRibasim.get_area_and_level\nRibasim.get_chunk_sizes\nRibasim.get_compressor\nRibasim.get_flow\nRibasim.get_flow\nRibasim.get_fractional_flow_connected_basins\nRibasim.get_jac_prototype\nRibasim.get_level\nRibasim.get_scalar_interpolation\nRibasim.get_storage_from_level\nRibasim.get_storages_and_levels\nRibasim.get_storages_from_levels\nRibasim.get_tstops\nRibasim.get_value\nRibasim.id_index\nRibasim.indicate_allocation_flow!\nRibasim.inflow_id\nRibasim.inflow_ids\nRibasim.inflow_ids_allocation\nRibasim.inneighbor_labels_type\nRibasim.inoutflow_ids\nRibasim.is_allocation_source\nRibasim.is_flow_constraining\nRibasim.is_flow_direction_constraining\nRibasim.load_data\nRibasim.load_structvector\nRibasim.low_storage_factor\nRibasim.metadata_from_edge\nRibasim.nodefields\nRibasim.nodetype\nRibasim.outflow_id\nRibasim.outflow_ids\nRibasim.outflow_ids_allocation\nRibasim.outneighbor_labels_type\nRibasim.parse_static_and_time\nRibasim.process_allocation_graph_edges!\nRibasim.profile_storage\nRibasim.qh_interpolation\nRibasim.reduction_factor\nRibasim.run\nRibasim.save_flow\nRibasim.save_subgrid_level\nRibasim.scalar_interpolation_derivative\nRibasim.seconds_since\nRibasim.set_current_value!\nRibasim.set_flow!\nRibasim.set_flow!\nRibasim.set_initial_discrete_controlled_parameters!\nRibasim.set_objective_priority!\nRibasim.set_static_value!\nRibasim.set_table_row!\nRibasim.sorted_table!\nRibasim.timesteps\nRibasim.update_allocation!\nRibasim.update_basin\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_jac_prototype!\nRibasim.update_tabulated_rating_curve!\nRibasim.valid_discrete_control\nRibasim.valid_edge_types\nRibasim.valid_edges\nRibasim.valid_flow_rates\nRibasim.valid_fractional_flow\nRibasim.valid_n_neighbors\nRibasim.valid_profiles\nRibasim.valid_sources\nRibasim.valid_subgrid\nRibasim.water_balance!\nRibasim.write_arrow\nRibasim.config.@addfields\nRibasim.config.@addnodetypes"
},
{
- "objectID": "contribute/release.html#generate-and-upload-test-models",
- "href": "contribute/release.html#generate-and-upload-test-models",
- "title": "Release process",
- "section": "2.8 Generate and upload test models",
- "text": "2.8 Generate and upload test models\nThe test models are currently not automatically uploaded. Create them locally with:\npixi run generate-testmodels\nNote that this only includes the test model data, no results. And zip the generated_testmodels directory to generated_testmodels.zip, and add these to the release assets. Click the edit pencil icon to be able to upload it."
+ "objectID": "python/reference/DiscreteControl.html",
+ "href": "python/reference/DiscreteControl.html",
+ "title": "1 DiscreteControl",
+ "section": "",
+ "text": "1 DiscreteControl\nDiscreteControl()"
},
{
- "objectID": "contribute/release.html#upload-artifacts-from-s3-to-github-release-assets",
- "href": "contribute/release.html#upload-artifacts-from-s3-to-github-release-assets",
- "title": "Release process",
- "section": "2.9 Upload artifacts from S3 to GitHub release assets",
- "text": "2.9 Upload artifacts from S3 to GitHub release assets\nAgain edit the release assets. Now upload the files downloaded from S3 as mentioned in Section 2.6."
+ "objectID": "python/reference/Pump.html",
+ "href": "python/reference/Pump.html",
+ "title": "1 Pump",
+ "section": "",
+ "text": "1 Pump\nPump()"
},
{
- "objectID": "contribute/release.html#announce-release",
- "href": "contribute/release.html#announce-release",
- "title": "Release process",
- "section": "2.10 Announce release",
- "text": "2.10 Announce release\nAnnounce the release in appropriate channels. Include a link to the release notes and assets, which is whatever this resolves to at that time. Also include a link to the documentation."
+ "objectID": "python/reference/ManningResistance.html",
+ "href": "python/reference/ManningResistance.html",
+ "title": "1 ManningResistance",
+ "section": "",
+ "text": "1 ManningResistance\nManningResistance()"
},
{
- "objectID": "core/equations.html",
- "href": "core/equations.html",
- "title": "Equations",
+ "objectID": "python/reference/Model.html",
+ "href": "python/reference/Model.html",
+ "title": "1 Model",
"section": "",
- "text": "Ribasim currently simulates the following “natural” water balance terms:\nAdditionally, Ribasim simulates the following “allocated” water balance terms:\nDepending on the type of boundary conditions, Ribasim requires relation between storage volume and wetted area \\(A\\), and between the storage volume and the water level \\(h\\). These are (currently) represented by piecewise linear relationships."
+ "text": "Model()\nA full Ribasim model schematisation with all input.\nRibasim model containing the location of the nodes, the edges between the nodes, and the node parametrization.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime\nEnd time of the simulation.\nrequired\n\n\nupdate_timestep\n\nThe output time step of the simulation in seconds (default of 1 day)\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nFractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nLevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nFlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nTabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nPump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOutlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nTerminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nDiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nPidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nUser\nUser node type with demand and priority.\nrequired\n\n\n\n\n\n\n\n\n\nName\nDescription\n\n\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nread\nRead model from TOML file.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_field_ids\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_ids\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\nwrite\nWrite the contents of the model to disk and save it as a TOML configuration file.\n\n\n\n\n\nModel.plot(ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.read(filepath)\nRead model from TOML file.\n\n\n\nModel.validate_model()\nValidate the model.\nChecks: - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_field_ids()\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_ids()\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\n\nModel.write(filepath)\nWrite the contents of the model to disk and save it as a TOML configuration file.\nIf filepath.parent does not exist, it is created before writing.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nfilepath\nPath | str\n\nrequired"
},
{
- "objectID": "core/equations.html#the-jacobian",
- "href": "core/equations.html#the-jacobian",
- "title": "Equations",
- "section": "1.1 The Jacobian",
- "text": "1.1 The Jacobian\nThe Jacobian is a \\(n\\times n\\) matrix where \\(n\\) is the number of states in the simulation. The Jacobian is computed either using finite difference methods or automatic differentiation. For more details on the computation of the Jacobian and how it is used in the solvers see numerical considerations.\nThe entries of the Jacobian \\(J\\) are given by \\[\nJ[i,j] = \\frac{\\partial f_j}{\\partial u_i},\n\\]\nhence \\(J[i,j]\\) quantifies how \\(f_j\\), the derivative of state \\(j\\) with respect to time, changes with a change in state \\(i\\). If a node creates dependendies between basin storages (or other states), then this yields contributions to the Jacobian. If \\(j\\) corresponds to a storage state, then\n\\[\nJ[i,j] = \\sum_{(i',j') \\in E | j' = i} \\frac{\\partial Q_{i',j'}}{\\partial u_i} - \\sum_{(i',j') \\in E | i' = i} \\frac{\\partial Q_{i',j'}}{\\partial u_i},\n\\]\nMost of these terms are always \\(0\\), because a flow over an edge only depends on a small number of states. Therefore the matrix \\(J\\) is very sparse.\nFor many contributions to the Jacobian the derivative of the level \\(l(S)\\) of a basin with respect to its storage \\(S\\) is required. To get an expression for this, we first look at the storage as a function of the level:\n\\[\nS(l) = \\int_{l_0}^l A(\\ell)d\\ell.\n\\]\nFrom this we obtain \\(S'(l) = A(l)\\) and thus \\[\n\\frac{\\text{d}l}{\\text{d}S} = \\frac{1}{A(S)}.\n\\]\n\n\n\n\n\n\nNote\n\n\n\nThe presence of division by the basin area means that areas of size zero are not allowed."
+ "objectID": "python/reference/Model.html#parameters",
+ "href": "python/reference/Model.html#parameters",
+ "title": "1 Model",
+ "section": "",
+ "text": "Name\nType\nDescription\nDefault\n\n\n\n\nstarttime\ndatetime.datetime\nStarting time of the simulation.\nrequired\n\n\nendtime\ndatetime.datetime\nEnd time of the simulation.\nrequired\n\n\nupdate_timestep\n\nThe output time step of the simulation in seconds (default of 1 day)\nrequired\n\n\ninput_dir\n\nThe directory of the input files.\nrequired\n\n\nresults_dir\n\nThe directory of the results files.\nrequired\n\n\nnetwork\n\nClass containing the topology (nodes and edges) of the model.\nrequired\n\n\nresults\n\nResults configuration options.\nrequired\n\n\nsolver\n\nSolver configuration options.\nrequired\n\n\nlogging\n\nLogging configuration options.\nrequired\n\n\nallocation\n\nThe allocation configuration.\nrequired\n\n\nbasin\nBasin\nThe waterbodies.\nrequired\n\n\nfractional_flow\nFractionalFlow\nSplit flows into fractions.\nrequired\n\n\nlevel_boundary\nLevelBoundary\nBoundary condition specifying the water level.\nrequired\n\n\nflow_boundary\nFlowBoundary\nBoundary conditions specifying the flow.\nrequired\n\n\nlinear_resistance\n\nLinear flow resistance.\nrequired\n\n\nmanning_resistance\nManningResistance\nFlow resistance based on the Manning formula.\nrequired\n\n\ntabulated_rating_curve\nTabulatedRatingCurve\nTabulated rating curve describing flow based on the upstream water level.\nrequired\n\n\npump\nPump\nPrescribed flow rate from one basin to the other.\nrequired\n\n\noutlet\nOutlet\nPrescribed flow rate from one basin to the other.\nrequired\n\n\nterminal\nTerminal\nWater sink without state or properties.\nrequired\n\n\ndiscrete_control\nDiscreteControl\nDiscrete control logic.\nrequired\n\n\npid_control\nPidControl\nPID controller attempting to set the level of a basin to a desired value using a pump/outlet.\nrequired\n\n\nuser\nUser\nUser node type with demand and priority.\nrequired"
},
{
- "objectID": "core/equations.html#sec-reduction_factor",
- "href": "core/equations.html#sec-reduction_factor",
- "title": "Equations",
- "section": "2.1 The reduction factor",
- "text": "2.1 The reduction factor\nAt several points in the equations below a reduction factor is used. This is a term that makes certain transitions more smooth, for instance when a pump stops providing water when its source basin dries up. The reduction factor is given by\n\\[\\begin{align}\n \\phi(x; p) =\n \\begin{cases}\n 0 &\\text{if}\\quad x < 0 \\\\\n -2 \\left(\\frac{x}{p}\\right)^3 + 3\\left(\\frac{x}{p}\\right)^2 &\\text{if}\\quad 0 \\le x \\le p \\\\\n 1 &\\text{if}\\quad x > p\n \\end{cases}\n\\end{align}\\]\nHere \\(p > 0\\) is the threshold value which determines the interval \\([0,p]\\) of the smooth transition between \\(0\\) and \\(1\\), see the plot below.\n\n\nCode\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef f(x, p = 3):\n x_scaled = x / p\n phi = (-2 * x_scaled + 3) * x_scaled**2\n phi = np.where(x < 0, 0, phi)\n phi = np.where(x > p, 1, phi)\n\n return phi\n\nfontsize = 15\np = 3\nN = 100\nx_min = -1\nx_max = 4\nx = np.linspace(x_min,x_max,N)\nphi = f(x,p)\n\nfig,ax = plt.subplots(dpi=80)\nax.plot(x,phi)\n\ny_lim = ax.get_ylim()\n\nax.set_xticks([0,p], [0,\"$p$\"], fontsize=fontsize)\nax.set_yticks([0,1], [0,1], fontsize=fontsize)\nax.hlines([0,1],x_min,x_max, color = \"k\", ls = \":\", zorder=-1)\nax.vlines([0,p], *y_lim, color = \"k\", ls = \":\")\nax.set_xlim(x_min,x_max)\nax.set_xlabel(\"$x$\", fontsize=fontsize)\nax.set_ylabel(\"$\\phi(x;p)$\", fontsize=fontsize)\nax.set_ylim(y_lim)\n\nfig.tight_layout()\nplt.show()"
+ "objectID": "python/reference/Model.html#methods",
+ "href": "python/reference/Model.html#methods",
+ "title": "1 Model",
+ "section": "",
+ "text": "Name\nDescription\n\n\n\n\nplot\nPlot the nodes and edges of the model.\n\n\nread\nRead model from TOML file.\n\n\nvalidate_model\nValidate the model.\n\n\nvalidate_model_node_field_ids\nCheck whether the node IDs of the node_type fields are valid.\n\n\nvalidate_model_node_ids\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\nwrite\nWrite the contents of the model to disk and save it as a TOML configuration file.\n\n\n\n\n\nModel.plot(ax=None)\nPlot the nodes and edges of the model.\n\n\n\n\n\n\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nax\nmatplotlib.pyplot.Artist\nAxes on which to draw the plot.\nNone\n\n\n\n\n\n\n\n\n\nType\nDescription\n\n\n\n\nmatplotlib.pyplot.Artist\n\n\n\n\n\n\n\n\nModel.read(filepath)\nRead model from TOML file.\n\n\n\nModel.validate_model()\nValidate the model.\nChecks: - Whether the node IDs of the node_type fields are valid - Whether the node IDs in the node field correspond to the node IDs on the node type fields\n\n\n\nModel.validate_model_node_field_ids()\nCheck whether the node IDs of the node_type fields are valid.\n\n\n\nModel.validate_model_node_ids()\nCheck whether the node IDs in the data tables correspond to the node IDs in the network.\n\n\n\nModel.write(filepath)\nWrite the contents of the model to disk and save it as a TOML configuration file.\nIf filepath.parent does not exist, it is created before writing.\n\n\n\n\n\nName\nType\nDescription\nDefault\n\n\n\n\nfilepath\nPath | str\n\nrequired"
},
{
- "objectID": "core/equations.html#precipitation",
- "href": "core/equations.html#precipitation",
- "title": "Equations",
- "section": "2.2 Precipitation",
- "text": "2.2 Precipitation\nThe precipitation term is given by\n\\[\n Q_P = P \\cdot A(S).\n\\tag{2}\\]\nHere \\(P = P(t)\\) is the precipitation rate and \\(A\\) is the wetted area. \\(A\\) is a function of the storage \\(S = S(t)\\): as the volume of water changes, the area of the free water surface may change as well, depending on the slopes of the surface waters."
+ "objectID": "python/reference/LinearResistance.html",
+ "href": "python/reference/LinearResistance.html",
+ "title": "1 LinearResistance",
+ "section": "",
+ "text": "1 LinearResistance\nLinearResistance()"
},
{
- "objectID": "core/equations.html#evaporation",
- "href": "core/equations.html#evaporation",
- "title": "Equations",
- "section": "2.3 Evaporation",
- "text": "2.3 Evaporation\nThe evaporation term is given by\n\\[\n Q_E = E_\\text{pot} \\cdot A(S) \\cdot \\phi(d;0.1).\n\\tag{3}\\]\nHere \\(E_\\text{pot} = E_\\text{pot}(t)\\) is the potential evaporation rate and \\(A\\) is the wetted area. \\(\\phi\\) is the reduction factor which depends on the depth \\(d\\). It provides a smooth gradient as \\(S \\rightarrow 0\\).\nA straightforward formulation \\(Q_E = \\mathrm{max}(E_\\text{pot} A(S), 0)\\) is unsuitable, as \\(\\frac{\\mathrm{d}Q_E}{\\mathrm{d}S}(S=0)\\) is then not well-defined.\n\nA non-smooth derivative results in extremely small timesteps and long computation time: ModelingToolkit identifies the singular behavior and adjusts its timestepping. In a physical interpretation, evaporation is switched on or off per individual droplet of water. In general, the effect of the reduction term is negligible, or not even necessary. As a surface water dries, its wetted area decreases and so does the evaporative flux. However, for (simplified) cases with constant wetted surface (a rectangular profile), evaporation only stops at \\(S = 0\\)."
+ "objectID": "python/reference/TabulatedRatingCurve.html",
+ "href": "python/reference/TabulatedRatingCurve.html",
+ "title": "1 TabulatedRatingCurve",
+ "section": "",
+ "text": "1 TabulatedRatingCurve\nTabulatedRatingCurve()"
},
{
- "objectID": "core/equations.html#infiltration-and-drainage",
- "href": "core/equations.html#infiltration-and-drainage",
- "title": "Equations",
- "section": "2.4 Infiltration and Drainage",
- "text": "2.4 Infiltration and Drainage\nInfiltration is provided as a lump sum for the basin. If Ribasim is coupled with MODFLOW 6, the infiltration is computed as the sum of all positive flows of the MODFLOW 6 boundary conditions in the basin:\n\\[\n Q_\\text{inf} = \\sum_{i=1}^{n} \\sum_{j=1}^{m} \\max(Q_{\\mathrm{mf6}_{i,j}}, 0.0)\n\\] {#eq-inf}.\nWhere \\(i\\) is the index of the boundary condition, \\(j\\) the MODFLOW 6 cell index, \\(n\\) the number of boundary conditions, and \\(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\\).\nDrainage is a lump sump 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.\n\\[\n Q_\\text{drn} = \\sum_{i=1}^{n} \\sum_{j=1}^{m} \\left| \\min(Q_{\\mathrm{mf6}_{i,j}}, 0.0) \\right|\n\\tag{4}\\]\nThe interaction with MODFLOW 6 boundary conditions is explained in greater detail in the the MODFLOW coupling section of the documentation."
+ "objectID": "python/reference/LevelBoundary.html",
+ "href": "python/reference/LevelBoundary.html",
+ "title": "1 LevelBoundary",
+ "section": "",
+ "text": "1 LevelBoundary\nLevelBoundary()"
},
{
- "objectID": "core/equations.html#upstream-and-downstream-flow",
- "href": "core/equations.html#upstream-and-downstream-flow",
- "title": "Equations",
- "section": "2.5 Upstream and downstream flow",
- "text": "2.5 Upstream and downstream flow\nRibasim’s basins can be connected to each other, and each basin expects an explicit connection. These connections are currently available for inter-basin flows:\n\n\nPump\nTabulatedRatingCurve\nLinearResistance\nManningResistance\n\nThe flow direction of the basin is not pre-determined: flow directions may freely reverse, provided the connection allows it. Currently, a LinearResistance allows bidirectional flow, but the\nAdditionally, three additional “connections” area available for the “outmost” basins (external nodes) in a network.\n\nTerminal\nLevelBoundary\nFlowBoundary\n\n\n2.5.1 Pump\nThe behaviour of pumps is very straight forward if these nodes are not PID controlled. Their flow is given by a fixed flow rate \\(q\\), multiplied by a reduction factor: \\[\nQ_\\text{pump} = \\phi(u; 10.0)q\n\\]\nHere \\(u\\) is the storage of the upstream basin. The reduction factor \\(\\phi\\) makes sure that the flow of the pump goes smootly to \\(0\\) as the upstream basin dries out.\n\n\n2.5.2 Outlet\nThe outlet is very similar to the pump, but it has a few extra reduction factors for physical constraints: \\[\nQ_\\text{outlet} = \\phi(u_a; 10.0)\\phi(\\Delta h; 0.1) \\phi(h_a-h_\\text{min};0.1)q.\n\\] The subscript \\(a\\) denotes the upstream node and \\(b\\) the downstream node. The first reduction factor is equivalent to the one for the pump. The second one makes sure that the outlet flow goes to zero as the head difference \\(\\Delta h = h_a - h_b\\) goes to zero. The last one makes sure that the outlet only produces flow when the upstream level is above the minimum chrest level \\(h_\\text{min}\\).\nNot all node types upstream or downstream of the outlet have a defined level. If this is the case, and therefore the reduction factor cannot be computed, it is defined to be \\(1.0\\).\n\n\n2.5.3 TabulatedRatingCurve\nThe Tabulated Rating Curve is a tabulation of a basin’s discharge behavior. It describes a piecewise linear relationship between the basin’s level and its discharge. It can be understood as an empirical description of a basin’s properties. This can include an outlet, but also the lumped hydraulic behavior of the upstream channels.\n\nThe Tabulated Rating Curve should indicate at which volume no discharge occurs (the dead storage volume).\n\n\n\n\n\n\nNote\n\n\n\nCurrently, the discharge relies only on the basin’s level; it could also use the volume of both connected basins to simulate backwater effects, submersion of outlets, or even reversal of flows for high precipitation events.\n\n\n\n\n2.5.4 LinearResistance\nA LinearResistance connects two basins together. The flow between the two basins is determined by a linear relationship:\n\\[\n Q = \\frac{h_a - h_b}{R}\n\\tag{5}\\]\nHere \\(h_a\\) is the water level in the first basin, \\(h_b\\) is the water level in the second basin, and \\(R\\) is the resistance of the link. A LinearResistance makes no assumptions about the direction of the flow: water flows from high to low.\n\n\n2.5.5 Terminal\nThis only allows outflow from a basin into a terminal node.\n\n\n2.5.6 LevelBoundary\nThis can be connected to a basin via a LinearResistance. This boundary node will then exchange water with the basin based on the difference in water level between the two.\n\n\n2.5.7 FlowBoundary\nThis can be connected directly to a basin and prescribes the flow to or from that basin. We require that the edge connecting the flow boundary to the basin should point towards the basin, so that positive flow corresponds to water being added to the model.\n\n\n2.5.8 Manning connection\nRibasim is capable of simulating steady flow between basins through a reach described by a trapezoidal profile and a Manning roughness coefficient.\nWe describe the discharge from basin \\(a\\) to basin \\(b\\) solely as a function of the water levels in \\(a\\) and \\(b\\).\n\\[\nQ = f(h_a, h_b)\n\\]\nwhere:\n\nThe subscripts \\(a,b\\) denote basins\n\\(h\\) is the hydraulic head, or water level\n\nThe energy equation for open channel flow is:\n\\[\nH = h + \\frac{v^2}{2g}\n\\]\nWhere\n\n\\(H\\) is total head\n\\(v\\) is average water velocity\n\\(g\\) is gravitational acceleration\n\nThe discharge \\(Q\\) is defined as:\n\\[\nQ = Av\n\\]\nwhere \\(A\\) is cross-sectional area.\nWe use conservation of energy to relate the total head at \\(a\\) to \\(b\\), with \\(H_a > H_b\\) as follows:\n\\[\nH_a = H_b + h_{\\text{loss}}\n\\]\nOr:\n\\[\nh_a + \\frac{v_a^2}{2g} = h_b + \\frac{v_b^2}{2g} + h_{\\text{loss}}\n\\]\nWhere \\(v\\) is the average water velocity. \\(h_{\\text{loss}}\\) is a combination of friction and contraction/expansion losses:\n\\[\nh_{\\text{loss}} = S_f L + \\frac{C}{2g} \\left(v_b^2 - v_a^2\\right)\n\\]\nWhere:\n\n\\(L\\) is the reach length\n\\(S_f\\) is the representative friction slope\n\\(C\\) is the expansion or contraction coefficient, \\(0 \\le C \\le1\\)\n\nWe assume velocity differences in a connection are negligible (\\(v_a = v_b\\)):\n\\[\nh_a = h_b + S_f L\n\\]\nFriction losses are computed with the Gauckler-Manning formula:\n\\[\nQ = \\frac{A}{n} R_h^\\frac{2}{3} \\sqrt{S_f}\n\\]\nWhere:\n\n\\(A\\) is the representative area.\n\\(R_h\\) is the representative wetted radius.\n\\(S_f\\) is the representative friction slope.\n\\(n\\) is Manning’s roughness coefficient.\n\nWe can rewrite to express \\(S_f\\) in terms of Q:\n\\[\nS_f = Q^2 \\frac{n^2}{A^2 R_h^{4/3}}\n\\]\nNo water is added or removed in a connection:\n\\[\nQ_a = Q_b = Q\n\\]\nSubstituting:\n\\[\nh_a = h_b + Q^2 \\frac{n^2}{A^2 R_h^{4/3}} L\n\\]\nWe can then express \\(Q\\) as a function of head difference \\(\\Delta h\\):\n\\[\nQ = \\textrm{sign}(\\Delta h) \\frac{A}{n} R_h^{2/3}\\sqrt{\\frac{|\\Delta h|}{L} }\n\\]\nThe \\(\\textrm{sign}(\\Delta h)\\) term causes the direction of the flow to reverse if the head in basin \\(b\\) is larger than in basin \\(a\\).\nThis expression however leads to problems in simulation since the derivative of \\(Q\\) with respect to \\(\\Delta h\\) tends to \\(\\pm \\infty\\) as \\(\\Delta h\\) tends to 0. Therefore we use the slightly modified expression\n\\[\nQ = \\textrm{sign}(\\Delta h) \\frac{A}{n} R_h^{2/3}\\sqrt{\\frac{\\Delta h}{L} s(\\Delta h)}\n\\]\nto smooth out this problem. Here \\(s(x) = \\frac{2}{\\pi}\\arctan{1000x}\\) can be thought of as a smooth approximation of the sign function.\n\n\n\n\n\n\nNote\n\n\n\nThe computation of \\(S_f\\) is not exact: we base it on a representative area and hydraulic radius, rather than integrating \\(S_f\\) along the length of a reach. Direct analytic solutions exist for e.g. parabolic profiles (Tolkmitt), but other profiles requires relatively complicated approaches (such as approximating the profile with a polynomial).\nWe use the average value of the cross-sectional area, the average value of the water depth, and the average value of the hydraulic radius to compute a friction slope. The size of the resulting error will depend on the water depth difference between the upstream and downstream basin.\n\n\nThe cross sectional area for a trapezoidal or rectangular profile:\n\\[\nA = w d + \\frac{\\Delta y}{\\Delta z} d^2\n\\]\nWhere\n\n\\(w\\) is the width at \\(d = 0\\) (A triangular profile has \\(w = 0\\))\n\\(\\frac{\\Delta y}{\\Delta z}\\) is the slope of the profile expressed as the horizontal length for one unit in the vertical (A slope of 45 degrees has \\(\\frac{\\Delta y}{\\Delta z} = 1\\); a rectangular profile 0).\n\nAccordingly, the wetted perimeter is:\n\\[\nB = w + 2 d \\sqrt{\\left(\\frac{\\Delta y}{\\Delta z}\\right)^2 + 1}\n\\]"
+ "objectID": "python/reference/User.html",
+ "href": "python/reference/User.html",
+ "title": "1 User",
+ "section": "",
+ "text": "1 User\nUser()"
},
{
- "objectID": "core/equations.html#the-derivative-term",
- "href": "core/equations.html#the-derivative-term",
- "title": "Equations",
- "section": "4.1 The derivative term",
- "text": "4.1 The derivative term\nWhen \\(K_d \\ne 0\\) this adds a level of complexity. We can see this by looking at the error derivative more closely: \\[\n\\frac{\\text{d}e}{\\text{d}t} = \\frac{\\text{d}\\text{SP}}{\\text{d}t} - \\frac{1}{A(u_\\text{PID})}\\frac{\\text{d}u_\\text{PID}}{\\text{d}t},\n\\] where \\(A(u_\\text{PID})\\) is the area of the controlled basin as a function of the storage of the controlled basin \\(u_\\text{PID}\\). The complexity arises from the fact that \\(Q_\\text{PID}\\) is a contribution to \\(\\frac{\\text{d}u_\\text{PID}}{\\text{d}t} = f_\\text{PID}\\), which makes Equation 7 an implicit equation for \\(Q_\\text{PID}\\). We define\n\\[\nf_\\text{PID} = \\hat{f}_\\text{PID} \\pm Q_\\text{pump/outlet},\n\\]\nthat is, \\(\\hat{f}_\\text{PID}\\) is the right hand side of the ODE for the controlled basin storage state without the contribution of the PID controlled pump. The plus sign holds for an outlet and the minus sign for a pump, dictated by the way the pump and outlet connectivity to the controlled basin is enforced.\nUsing this, solving Equation 7 for \\(Q_\\text{PID}\\) yields \\[\nQ_\\text{pump/outlet} = \\text{clip}\\left(\\phi(u_\\text{us})\\frac{K_pe + K_iI + K_d \\left(\\frac{\\text{d}\\text{SP}}{\\text{d}t}-\\frac{\\hat{f}_\\text{PID}}{A(u_\\text{PID})}\\right)}{1\\pm\\phi(u_\\text{us})\\frac{K_d}{A(u_\\text{PID})}};Q_{\\min},Q_{\\max}\\right),\n\\] where the clipping is again done last. Note that to compute this, \\(\\hat{f}_\\text{PID}\\) has to be known first, meaning that the PID controlled pump/outlet flow rate has to be computed after all other contributions to the PID controlled basin’s storage are known."
+ "objectID": "python/reference/Terminal.html",
+ "href": "python/reference/Terminal.html",
+ "title": "1 Terminal",
+ "section": "",
+ "text": "1 Terminal\nTerminal()"
},
{
- "objectID": "core/equations.html#the-sign-of-the-parameters",
- "href": "core/equations.html#the-sign-of-the-parameters",
- "title": "Equations",
- "section": "4.2 The sign of the parameters",
- "text": "4.2 The sign of the parameters\nNote by Equation 6 that the error is positive if the setpoint is larger than the basin level and negative if the setpoint is smaller than the basin level.\nWe enforce the convention that when a pump is controlled, its edge points away from the basin, and when an outlet is controlled, its edge points towards the basin, so that the main flow direction along these edges is positive. Therefore, positive flows of the pump and outlet have opposite effects on the basin, and thus the parameters \\(K_p,K_i,K_d\\) of the pump and outlet must have oppositive signs to achieve the same goal."
+ "objectID": "couple/modflow-demo.html",
+ "href": "couple/modflow-demo.html",
+ "title": "MODFLOW 6 Demonstration",
+ "section": "",
+ "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system."
},
{
- "objectID": "core/numerics.html",
- "href": "core/numerics.html",
- "title": "Numerical considerations",
+ "objectID": "couple/modflow-demo.html#example-parametrization",
+ "href": "couple/modflow-demo.html#example-parametrization",
+ "title": "MODFLOW 6 Demonstration",
"section": "",
- "text": "We want to solve the following initial value problem: \\[\n\\begin{cases}\n \\frac{\\text{d}\\mathbf{u}}{\\text{d}t} = \\mathbf{f}(\\mathbf{u},t) \\quad t_0 < t < t_\\text{end} \\\\\n \\mathbf{u}(t_0) = \\mathbf{u}_0\n\\end{cases},\n\\tag{1}\\]\nwhere \\(\\mathbf{f}\\) denotes water_balance! and \\(\\mathbf{u_0}\\) the initial storages (and the PID integrals which start out at \\(0\\)).\nIn general \\(\\mathbf{f}\\) is a non-linear function in \\(\\mathbf{u}\\). These non-linearities are introduced by:\nThe problem Equation 1 can be solved by various numerical time-integration methods. To do this the time interval \\([t_0,t_\\text{end}]\\) is discretized into a finite number of time points \\(t_0 < t_1 < \\ldots < t_N = t_\\text{end}\\) for which approximate solutions \\(\\mathbf{w}_n \\approx \\mathbf{u}(t_n)\\) are computed. In general we do not assume a fixed timestep (the interval between successive points in time). Rather, the solver attempts to make as large a step as possible while keeping error tolerances within requirements. The solver settings section details the available configuration options."
+ "text": "In our test cases, we will work with the Dutch national groundwater model (LHM). For testing, we simplify the top boundary conditions to just three surface water “systems”, from large to small:\n\nPrimary\nSecondary\nTertiary\n\nThese systems are separated in the groundwater model schematization due to the relatively small size of the surface waters (several meters in width) in comparison with relatively coarse cells of the groundwater model (250 m): most cells in the LHM contain more than one surface water, and are included via representative parameters which take the physical scaling into account. In case of sufficiently small cell sizes, no overlap occurs and all surface waters can be represented in a single grid.\nRibasim has no knowledge of these systems unless explicitly separated into different (sub-)basins. In the examples below, these three systems are represented by one basin with a single volume. For the purposes of testing, we have not yet created empirical volume-level relationships for the surface water of every cell extracted from a hydraulic model. Instead, we have chosen an (over)simplified parametrization of the hydraulic properties, which makes it easy to verify the behavior of Ribasim and the coupling procedures:\n\nPrimary, secondary, and tertiary systems have a rectangular profile: the area of the surface water does not change with water level or volume.\nWater depth is constant for a system within a basin.\nWater depth increases linearly with volume. (This follows from 1.)\nWater depth is distributed across the systems in a geometric progression: when the tertiary system contains 0.1 m of water, the secondary system contains 0.2 m, and the primary system contains 0.4 m.\nWater depth is 0.0 m for all systems when basin volume is 0.0. (This follows from 4.)\nThe distribution of water occurs according to the surface water area (width times length) used for the LHM parametrization of river bed conductance.\n\nFor freely draining basins, a simplified storage-discharge relationship has been derived as follows:\n\nThe area of the basin polygon has been multiplied by 1 mm/d (roughly the average precipitation excess in the Netherlands). This yields a “normative discharge”.\nA corresponding normative volume has been chosen, corresponding with water depths of 0.4, 0.2, and 0.1 m for the primary, secondary, and tertiary surface waters respectively.\nA dead storage volume has been chosen, corresponding with water depths of 0.2, 0.1, and 0.05 m for the primary, secondary, and tertiary surface waters respectively. The basin only starts discharging when the storage volume exceeds this value.\n\n\n\n\n\n\n\nNote\n\n\n\nRibasim is not limited to such oversimplified parametrization! Ribasim uses tabulations and therefore supports arbitrary (piecewise linear) volume-depth and volume-discharge relationships.\n\n\nA visual representation of this simplified conceptual schematization is given in Figure 1 and Figure 2.\n\n\n\nFigure 1: Distribution of water depths over the primary, secondary, and tertiary system.\n\n\n\n\n\nFigure 2: Discharge as a function of basin storage volume.\n\n\nAn example of the resulting parameters for a single cell is shown in Table 1. The first row shows the water levels when the basin is empty. The level for primary, secondary, and tertiary are equal to the bottom elevation of the surface waters. The second row shows the volume and levels for water depths of 0.4, 0.2, and 0.1 m. The third row shows the volume and levels for a tenfold larger volume. This results in implausible water levels with depths of 4.0, 2.0 and 1.0 m; the water level in the primary system is over two meters higher than in the tertiary system. In reality, the surface waters would overflow and surface ponding would occur; this mechanism is ignored in the test cases for the sake of simplicity.\n\n\nTable 1: Volume-level table for a single cell in the Hupsel basin.\n\n\nVolume (m3)\nPrimary (m NAP)\nSecondary (m NAP)\nTertiary (m NAP)\n\n\n\n\n0.0\n25.65\n25.83\n26.60\n\n\n6843.1\n26.05\n26.03\n26.70\n\n\n68431.0\n29.65\n27.83\n27.60\n\n\n\n\nFigure 3 shows the volume of the first row of the cell based input for the primary system. Symbology is set to unique values. While water levels differ per cell in this parametrization, the “normative volume” defined above is shared by all cells in a basin.\n\n\n\nFigure 3: Basin normative volume of the primary system.\n\n\nFigure 3 shows the water level corresponding to the normative storage volume based input for the primary system (it corresponds to the value shown in the first row of the primary column in Table 1). We see a clear gradient from west to east: as our simplified parametrization assumes a constant water depth for all cells in a single system, water levels spatially fall and rise with the bottom elevation.\n\n\n\nFigure 4: Water level corresponding to the normative basin volume of the primary system."
},
{
- "objectID": "core/numerics.html#euler-forward",
- "href": "core/numerics.html#euler-forward",
- "title": "Numerical considerations",
- "section": "1.1 Euler forward",
- "text": "1.1 Euler forward\nThe simplest numerical method is Euler forward: \\[\n\\mathbf{w}_{n+1} = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{w}_n, t_n).\n\\tag{2}\\]\nHere \\(\\mathbf{w}_{n+1}\\) is given as a simple explicit function of \\(\\mathbf{w}_n\\)."
+ "objectID": "couple/modflow-demo.html#example-configuration",
+ "href": "couple/modflow-demo.html#example-configuration",
+ "title": "MODFLOW 6 Demonstration",
+ "section": "2 Example: Configuration",
+ "text": "2 Example: Configuration\nAn example of the MODFLOW 6 section of TOML configuration required for a coupled run can be seen below:\n[modflow]\nsimulation = \"../data/hupsel/mfsim.nam\"\nmode = \"sequential\"\ntimestep = 86400.0\n\n[modflow.models]\n\n[modflow.models.gwf]\ntype = \"gwf\"\ndataset = \"../data/volume_level_profile-hupsel.nc\"\nbasins = \"basin_id\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_P\"\ndrain = \"DRN_P\"\nprofile = \"profile_primary\"\n\n[[modflow.models.gwf.bounds]]\nriver = \"RIV_S\"\ndrain = \"DRN_S\"\nprofile = \"profile_secondary\"\n\n[[modflow.models.gwf.bounds]]\ndrain = \"DRN_T\"\nprofile = \"profile_tertiary\"\nThe section starts by stating the path to the MODFLOW 6 simulation name file (simulation). The next section contains the information regarding the MODFLOW 6 model(s) to couple to Ribasim’s basins. Per model, a path to the coupling parameter dataset is provided (dataset), along with the variable to use as the basin identification number (basins). Next, for every boundary condition that should be coupled to Ribasim, the package names (river, drain) used by MODFLOW 6 must be specified (as we look for these names in the MODFLOW 6 memory), along with the name of the variable in the coupling dataset which provides the volume-level relationship (profile).\nAs can be seen in the example, the coupling mechanism supports coupling of:\n\nA single drainage package (drain entry).\nA single river package (river entry).\nA combination of river and drainage package (when infiltration conductance does not equal drainage conductance) both (river and drain entry)."
},
{
- "objectID": "core/numerics.html#euler-backward",
- "href": "core/numerics.html#euler-backward",
- "title": "Numerical considerations",
- "section": "1.2 Euler backward",
- "text": "1.2 Euler backward\nEuler backward is formulated as follows: \\[\n\\mathbf{w}_{n+1} = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{w}_{n+1},t_{n+1}).\n\\tag{3}\\]\nNote that this is an implicit equation for \\(\\mathbf{w}_{n+1}\\), which is non-linear because of the non-linearity of \\(\\mathbf{f}\\).\nGenerally one of the following iterative methods is used for finding solutions to non-linear equations like this:\n\nPicard iteration for fixed points. This method aims to approximate \\(\\mathbf{w}_{n+1}\\) as a fixed point of the function \\[\n\\mathbf{g}(\\mathbf{x}) = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{x},t_{n+1})\n\\] by iterating \\(\\mathbf{g}\\) on an initial guess of \\(\\mathbf{w}_{n+1}\\);\nNewton iterations: approximate \\(\\mathbf{w}_{n+1}\\) as a root of the function \\[\n\\mathbf{h}(\\mathbf{x}) = \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{x},t_{n+1}) - \\mathbf{x},\n\\] by iteratively finding the root of its linearized form:\n\n\\[\\begin{align}\n\\mathbf{0} =& \\mathbf{h}(\\mathbf{w}_{n+1}^k) + \\mathbf{J}(\\mathbf{h})(\\mathbf{w}_{n+1}^k)(\\mathbf{w}_{n+1}^{k+1}-\\mathbf{w}_{n+1}^k) \\\\\n=& \\mathbf{w}_n + (t_{n+1}-t_n)\\mathbf{f}(\\mathbf{w}_{n+1}^k,t_{n+1}) - \\mathbf{w}_{n+1}^k \\\\ +&\\left[(t_{n+1}-t_n)\\mathbf{J}(\\mathbf{f})(\\mathbf{w}_{n+1}^k)-\\mathbf{I}\\right](\\mathbf{w}_{n+1}^{k+1}-\\mathbf{w}_{n+1}^k).\n\\end{align}\\] Note that this thus requires an evaluation of the Jacobian of \\(\\mathbf{f}\\) and solving a linear system per iteration."
+ "objectID": "couple/modflow-demo.html#test-case-hupsel",
+ "href": "couple/modflow-demo.html#test-case-hupsel",
+ "title": "MODFLOW 6 Demonstration",
+ "section": "3 Test case: Hupsel",
+ "text": "3 Test case: Hupsel\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\nFrom these tests, we expect the following behavior:\n\nDrainage terms should remain largely the same when the water level is lowered in a steady-state: the amount of recharge is fixed, and this is locally drained.\nIn case of negative recharge (evapotranspiration), infiltration occurs in the surface waters. Infiltration should be zero when the basin volume is 0.\n\n\n\n\nFigure 5: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 5 shows the water balance of steady-state for submodel of the LHM that has been by selecting the cells belonging to the district containing the Hupsel catch, the Berkel.\nThe Hupsel basin show the expected behavior: for a net groundwater recharge of 1.0 mm/d, most is precipitation with a minor part lateral inflow from higher areas. In terms of outgoing flows, most of the precipitation ends up in the surface water, primarily in the many ditches of the tertiary system. Only a relatively small part of the water leaves the basin via the groundwater. Interbasin flows through the groundwater form such a minor role, as the aquifer is thin and transmissivity is limited.\nReducing groundwater recharge to 0.5 mm/d reduces all flows, with the tertiary system playing a less dominant role, relatively speaking: as its elevation is the highest compared to the primary and secondary system, the head difference is reduced strongest for the tertiary system.\nWith evapotranspiration (ET) excess (-0.05 and 0.1 mm/d; low values are chosen here since most ET would be drawn from storage, which is not available in a steady-state model), the surface waters provide mostly inflow, and recharge is a negative term. In this case, the secondary system provides a small amount of infiltration; most of the water is drawn from the surroundings instead.\n\n\n\nFigure 6: Water balance of the MODFLOW 6 boundary conditions for the Hupsel basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 6 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. Consequently, primary and secondary outflow terms are larger for positive groundwater recharge as they drain at a lower level and intercept the water before the tertiary system does. Secondly, with negative groundwater recharge, no infiltration occurs and the water is drawn from the surroundings instead. This shows the coupling mechanism adjusting MODFLOW 6 water levels successfully."
},
{
- "objectID": "core/numerics.html#basin-profiles",
- "href": "core/numerics.html#basin-profiles",
- "title": "Numerical considerations",
- "section": "4.1 Basin profiles",
- "text": "4.1 Basin profiles\nThe basin profiles affect \\(\\mathbf{f}\\) in many ways, anywhere where a basin level or area is required.\n\n\n\n\n\n\nNote\n\n\n\nThis section needs to be updated and extended after once this issue is resolved."
+ "objectID": "couple/modflow-demo.html#test-case-de-tol",
+ "href": "couple/modflow-demo.html#test-case-de-tol",
+ "title": "MODFLOW 6 Demonstration",
+ "section": "4 Test case: de Tol",
+ "text": "4 Test case: de Tol\nTo test the coupling, the following simulations have been setup and run:\n\nA standalone MODFLOW 6 run of sequential steady-states (i.e. no storage) with differing groundwater recharge values.\nA coupled run where the MODFLOW 6 stages are updated by the Ribasim coupler process, but without Ribasim. This results in volumes of 0.0, so all MODFLOW 6 water levels are set equal to bed elevation.\nA coupled run where the water levels are updated by Ribasim.\n\n\n\n\nFigure 7: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a standalone MODFLOW 6 run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 7 shows the water balance of steady-state for a submodel of the LHM for the Polder de Tol and its surroundings. While groundwater recharge is the dominant ingoing flow, lateral groundwater flow (over the entire depth of the groundwater model) is a sizable inflow for the area; the larger lateral inflow shows that De Tol is a net receiver of groundwater which is mostly discharged through the secondary system. In contrast to the Hupsel, the tertiary system is almost entirely absent: drainage occurs not through ephemeral tertiary ditches, but by the permanently water-bearing ditches of the primary and secondary system. Unlike the Hupsel, the water balance does not shrink to very small discharges, as there is sizable regional groundwater flow.\n\n\n\nFigure 8: Water balance of the MODFLOW 6 boundary conditions for De Tol basin for a zero volume run. The four sequential steady states (01, 02, 03, 04) use net groundwater recharge values of 1.0, 0.5, -0.05 and -0.1 mm/d.\n\n\nFigure 8 shows the same model, with 0-basin volume which causes water levels to be set equal to bed elevation. The total discharge is larger: the primary and secondary systems are set to lower levels, and so the head difference is larger. While De Tol’s evapotranspiration excess can be fed by the regional groundwater, the primary and secondary ditches also provide some part; as expected, they do not feed when the coupling mechanism adjusts MODFLOW 6’s water levels."
},
{
- "objectID": "core/numerics.html#qh-relations",
- "href": "core/numerics.html#qh-relations",
- "title": "Numerical considerations",
- "section": "4.2 Q(h) relations",
- "text": "4.2 Q(h) relations\nTabulatedRatingCurve nodes contribute to \\(\\mathbf{f}\\) with terms of the following form:\n\\[\n Q(h(S))\n\\]\nwhere the continuity of this term is given by the least continuous of \\(Q\\) and \\(h\\)."
+ "objectID": "couple/modflow.html",
+ "href": "couple/modflow.html",
+ "title": "MODFLOW 6",
+ "section": "",
+ "text": "Ribasim has been designed to provide a computationally efficient representation of surface water for MODFLOW 6. It does so by connecting to basic MODFLOW 6 boundary conditions: the river and drainage packages.\nRibasim connects to MODFLOW 6 via the Basic Model Interface (BMI) and Extended Model Interface (XMI) (Hughes et al. 2022). BMI describes the interface to initialize a model, get values from its memory, run a timestep, etc. XMI extends this interface to allow much finer control into MODFLOW 6’s solution procedures. We have written a Julia package (Deltares 2022) which implements this interface for the Julia programming language. In coupling, Ribasim uses this interface to get the head values, the boundary condition water levels, and the budgets term of the MODFLOW 6 groundwater model while MODFLOW 6 is running.\nAdditionally, links can be made with other (BMI/XMI-compliant) processes and models. One example of such a link is using the surface runoff and the agricultural irrigation demand calculated by an unsaturated zone model; in the Netherlands Hydrological Instrument MetaSWAP provides this demand."
},
{
- "objectID": "core/numerics.html#empty-basins",
- "href": "core/numerics.html#empty-basins",
- "title": "Numerical considerations",
- "section": "4.3 Empty basins",
- "text": "4.3 Empty basins\nReduction factors are introduced at several points in the definition of \\(\\mathbf{f}\\) to smooth out otherwise discontinuous transitions (e.g. the flow rate of a pump going to zero when the source basin dries out). If flows are not too large with respect to basin storage, this will prevent basins from reaching 0. Rather, the basin gets a very small storage. The reduction factors help with performance, but are also an important tool to avoid getting negative storage in basins. Negative storage needs to be avoided since it is not a real solution, and would introduce water into the model that doesn’t exist. Another tool used to avoid negative storage is the isoutoutofdomain option, which Ribasim makes use of. This reject timesteps that lead to negative storage, instead retrying with a smaller timestep."
+ "objectID": "couple/modflow.html#drainage",
+ "href": "couple/modflow.html#drainage",
+ "title": "MODFLOW 6",
+ "section": "1.1 Drainage",
+ "text": "1.1 Drainage\nThe drainage package can be simulated to agricultural drains, ditches, or draining streams. The amount of water removed from the aquifer is proportional to the difference between the groundwater head and the drainage elevation. Drainage only occurs when the head is larger than the elevation; this boundary condition does not allow infiltration into the groundwater.\n\\[\nQ_{drain} = \\left\\{\n \\begin{array}{ c l }\n C_{drain} (\\phi - h_{drain}) & \\quad \\textrm{if } \\phi > h_{drain} \\\\\n 0 & \\quad \\textrm{otherwise}\n \\end{array}\n\\right.\n\\]"
},
{
- "objectID": "core/allocation.html",
- "href": "core/allocation.html",
- "title": "Allocation",
- "section": "",
- "text": "Allocation is the process of assigning an allocated abstraction flow rate to user nodes in the model based on information about sources, user demands over various priorities, constraints introduced by nodes, local water availability and graph topology. The allocation procedure implemented in Ribasim is heavily inspired by the maximum flow problem.\nThe allocation problem is solved per subnetwork of the Ribasim model. The subnetwork is used to formulate an optimization problem with the JuMP package, which is solved using the HiGHS solver. For more information see also the example of solving the maximum flow problem with JuMP.jl here."
+ "objectID": "couple/modflow.html#river",
+ "href": "couple/modflow.html#river",
+ "title": "MODFLOW 6",
+ "section": "1.2 River",
+ "text": "1.2 River\nThe river package can both drain the groundwater, or infiltrate surface water to the groundwater. It limits the amount of water that can infiltrate when the groundwater head falls below the river bottom, in which cases it assumes atmospheric pressure conditions underneath the surface water.\n\\[\nQ_{river} = \\left\\{\n \\begin{array}{ c l }\n C_{river} (\\phi - h_{river}) & \\quad \\textrm{if } \\phi > b_{river} \\\\\n C_{river} (h_{river} - b_{river}) & \\quad \\textrm{if } \\phi <= b_{river}\n \\end{array}\n\\right.\n\\]\nIn the Netherlands, it is somewhat common to make a distinction between the drainage and infiltration conductance of surface waters. Drainage conductance is often larger than the infiltration conductance due to clogging processes, seepage through sides of the ditches, less contracted flow lines, etc.\n\\[\nQ_{river} = \\left\\{\n \\begin{array}{ c l }\n C_{river,drn} (\\phi - h_{river}) & \\quad \\textrm{if } \\phi > h_{river} \\\\\n C_{river,inf} (\\phi - h_{river}) & \\quad \\textrm{if } \\phi <= h_{river} \\\\\n C_{river,inf} (h_{river} - b_{river}) & \\quad \\textrm{if } \\phi <= b_{river}\n \\end{array}\n\\right.\n\\]\nMODFLOW 6 does not support this (currently), but an identical effect may be achieved by “stacking” a drainage package on top of a river package with these values:\n\\[\n\\begin{array}{ c l }\n h_{drain} = h_{river} \\\\\n C_{drain} = C_{river,drn} - C_{river,inf}\n\\end{array}\n\\]"
},
{
- "objectID": "core/allocation.html#allocation-problem-input",
- "href": "core/allocation.html#allocation-problem-input",
- "title": "Allocation",
- "section": "1.1 Allocation problem input",
- "text": "1.1 Allocation problem input\n\n1.1.1 The subnetwork\nThe allocation problem is solved per subnetwork, which is given by a subset \\(S \\subset V\\) of node ids. Different subnetworks are disjoint from eachother.\n\n\n1.1.2 Source flows\nSources are indicated by a set of edges in the subnetwork \\[\nE_S^\\text{source} \\subset \\left(S \\times S\\right) \\cap E.\n\\] That is, if \\((i,j) \\in E_S^\\text{source}\\), then \\(Q_{ij}\\) (see the formal model description) is treated as a source flow in the allocation problem.\n\n\n1.1.3 User demands\nThe subnetwork contains a subset of user nodes \\(U_S \\subset S\\), who all have time varying demands over various priorities \\(p\\): \\[\n d^p_i(t), \\quad i \\in U_S, p = 1,2,\\ldots, p_{\\max}.\n\\]\n\n\n\n\n\n\nNote\n\n\n\nOn this page we assume that the priorities are given by all integers from \\(1\\) to some \\(p_{\\max} \\in \\mathbb{N}\\). However, in the Ribasim input this is not a requirement; some of these in between priority values can be missing, only the ordering of the given priorities is taken into account.\n\n\n\n\n1.1.4 Vertical fluxes and local storage\nApart from the source flows denoted by edges, there are other sources of water in the subnetwork, associated with the basins in the subnetwork \\(B_S = B \\cap S\\). Firstly there is the sum of the vertical fluxes (precipitation, evaporation, infiltration and drainage) for each basin \\[\n \\phi_i(t), \\quad \\forall i \\in B_S.\n\\]\nSecondly, there is the available water in each basin above the minimum level \\(l_{\\min,i}\\) corresponding to a minimum storage \\(s_{\\min,i}\\): \\[\n u_i(t)-s_{\\min,i}, \\quad \\forall i \\in B_S.\n\\] Note that this value can be negative, which we interpret as a demand from the basin.\n\n\n1.1.5 Flow magnitude and direction constraints\nNodes in the Ribasim model that have a max_flow_rate, i.e. pumps and outlets, put a constraint on the flow through that node. Some nodes only allow flow in one direction, like pumps, outlets and tabulated rating curves.\n\n\n1.1.6 Fractional flows and user return flows\nBoth fractional flow nodes and user nodes dictate proportional relationships between flows over edges in the subnetwork. Users have a return factor \\(0 \\le r_i \\le 1, i \\in U_S\\)."
+ "objectID": "couple/modflow.html#numerical-solution-in-modflow",
+ "href": "couple/modflow.html#numerical-solution-in-modflow",
+ "title": "MODFLOW 6",
+ "section": "2.1 Numerical solution in MODFLOW",
+ "text": "2.1 Numerical solution in MODFLOW\nMODFLOW uses a backward-in-time implicit solution scheme. This creates a large system of equations (a water balance for every cell), which it solves by repeatedly solving a linearized system of equations instead. In matrix form, this system of equations is expressed by:\n\\[\n\\mathbf{Ax} = \\mathbf{b}\n\\]\nWhere \\(\\mathbf{x}\\) is a vector containing the head of every cell.\nFor the boundary conditions, this requires linearization of the flow equations. Flow from outside of the aquifer (cell) may be represented by:\n\\[\na = p\\phi + q\n\\]\n(Equation 2-6 in the MODFLOW 6 documentation (Langevin et al. 2017).)\nFor e.g. a draining boundary condition, the flow is head dependent:\n\\[\na = C(h - \\phi) = -C\\phi + Ch\n\\]\nWith \\(C\\) the conductance, \\(h\\) the boundary head or elevation, and \\(\\phi\\) the groundwater head.\nIn MODFLOW’s internal formulation, the term in \\(\\mathbf{A}\\) is called “coefficient of head” or hcof. Terms in \\(\\mathbf{b}\\) are called “right hand side” or rhs. We can separate the equation above:\n\\[\n\\begin{aligned}\np = \\text{hcof} = -C \\\\\nq = \\textrm{rhs} = -Ch \\\\\na = -C\\phi + Ch = \\text{hcof} * \\phi - \\text{rhs}\n\\end{aligned}\n\\]\nFor every boundary condition, MODFLOW 6 stores the hcof and rhs values. This makes it quite convenient for us to compute the water budget for every boundary condition: we simply multiply the hcof value by the head of the cell and subtract the rhs.\nNote that hcof may have a value of 0! For example, when for a river boundary the \\(\\phi <= b_{river}\\) condition occurs, the flow into the cell is controlled only by \\(h_{river}\\) and \\(b_{river}\\) (equal to recharge for the linear solution)."
},
{
- "objectID": "core/allocation.html#the-allocation-optimization-problem",
- "href": "core/allocation.html#the-allocation-optimization-problem",
- "title": "Allocation",
- "section": "1.2 The allocation optimization problem",
- "text": "1.2 The allocation optimization problem\n\n1.2.1 The allocation subgraph\nA new graph is created from the subnetwork, which we call an allocation graph. The allocation graph is almost a subgraph of the main (flow) model, apart from the fact that an allocation graph can contain edges which are not in the main model.\nThe allocation graph consists of:\n\nNodes \\(V'_S \\subset V_S\\), where each basin, source and user in the subnetwork get a node in the allocation graph. Also nodes that have fractional flow outneighbors get a node in the allocation graph.\nEdges \\(E_S\\), which are either edges that also appear between nodes in the subnetwork or represent a sequence of those, creating a shortcut.\n\nFor notational convenience, we use the notation\n\\[\\begin{align}\n V^{\\text{out}}_S(i) = \\left\\{j \\in V'_S : (i,j) \\in E_S\\right\\} \\\\\n V^{\\text{in}}_S(j) = \\left\\{i \\in V'_S : (i,j) \\in E_S\\right\\}\n\\end{align}\\]\nfor the set of in-neighbors and out-neighbors of a node in the allocation graph respectively\n\n\n1.2.2 The allocation graph capacities\nThe capacities of the edges of the allocation graph are collected in the sparse capacity matrix \\(C_S \\in \\overline{\\mathbb{R}}_{\\ge 0}^{n'\\times n'}\\) where \\(n' = \\#V'_S\\) is the number of nodes in the allocation graph. The capacities can be infinite.\nThe capacities are determined in 4 different ways:\n\nIf an edge does not exist in the allocation graph, i.e. \\((i,j) \\notin E_S\\) for certain \\(1 \\le i,j\\le n'\\), then \\((C_S)_{i,j} = 0\\);\nThe capacity of the edge \\(e \\in E_S\\) is given by the smallest max_flow_rate of the nodes along the equivalent edges in the subnetwork. If there are no nodes with a max_flow_rate, the edge capacity is infinite;\nIf the edge is a source, the capacity of the edge is given by the flow rate of that source.\n\n\n\n1.2.3 The optimization variables\nThere are 2 types of variables whose value has to be determined to solve the allocation problem:\n\nThe flows \\(F \\in \\mathbb{R}_{\\ge 0}^{n'\\times n'}\\) over the edges in the allocation graph;\nThe allocations to the basins \\[\n A^\\text{basin}_{i} \\ge 0, \\quad B_S.\n\\]\n\n\n\n\n\n\n\nNote\n\n\n\nCurrently the basin allocations are not taken into account in the implementation.\n\n\n\n\n1.2.4 The optimization objective\nThe goal of allocation is to get the flow to the users as close as possible to their demand. To achieve this, the following objectives are supported:\n\nquadratic_absolute: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left( F_{ij} - d_j^p(t)\\right)^2\n\\]\nquadratic_relative: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left( 1 - \\frac{F_{ij}}{d_j^p(t)}\\right)^2\n\\]\nlinear_absolute: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left| F_{ij} - d_j^p(t)\\right|\n\\]\nlinear_relative: \\[\n \\min \\sum_{(i,j)\\in E_S\\;:\\; i\\in U_S} \\left|1 - \\frac{F_{ij}}{d_j^p(t)}\\right|\n\\]\n\nTo avoid division by \\(0\\) errors, if a *_relative objective is used and a demand is \\(0\\), the coefficient of the flow \\(F_{ij}\\) is set to \\(0\\).\nFor *_absolute objectives the optimizer cares about the actual amount of water allocated to a user, for *_relative objectives it cares about the fraction of the demand allocated to the user. For quadratic_* objectives the optimizer cares about avoiding large shortages, for linear_* objectives it treats all deviations equally.\n\n\n\n\n\n\nNote\n\n\n\nThese options for objectives for allocation to users have not been tested thoroughly, and might change in the future.\n\n\nThe absolute value applied here is not supported in a linear programming context directly; this requires introduction of new variables and constraints. For more details see here.\n\n\n\n\n\n\nNote\n\n\n\nIn the future new optimization objectives will be introduced, for demands of basins and priorities over sources. These will be used in combination with the above, in the form of goal programming.\n\n\n\n\n1.2.5 The optimization variable constraints\n\nFlow conservation: For the basins in the allocation graph we have that \\[\n \\sum_{j=1}^{n'} F_{kj} \\le \\sum_{i=1}^{n'} F_{ik}, \\quad \\forall k \\in B_S.\n\\tag{1}\\] Note that we do not require equality here; in the allocation we do not mind that excess flow is ‘forgotten’ if it cannot contribute to the allocation to the users.\nCapacity: the flows over the edges are positive and bounded by the edge capacity: \\[\n F_{ij} \\le \\left(C_S\\right)_{ij}, \\quad \\forall(i,j) \\in E_S.\n\\tag{2}\\] By the definition of \\(C_S\\) this also includes the source flows.\nUser outflow: The outflow of the user is dictated by the inflow and the return factor: \\[\n F_{ik} = r_k \\cdot F_{kj} \\quad\n \\quad \\forall k \\in U_S, \\quad\n V^{\\text{in}}_S(k) = \\{i\\},\\;\n V^{\\text{out}}_S(k) = \\{j\\}.\n\\tag{3}\\] Here we use that each user node in the allocation graph has a unique in-edge and out-edge.\nUser demand: user demand constraints are discussed in the next section.\nFractional flow: Let \\(L_S \\subset V_S\\) be the set of nodes in the max flow graph with fractional flow outneighbors, and \\(f_j\\) the flow fraction associated with fractional flow node \\(j \\in V_S\\). Then \\[\n F_{ij} \\le f_j \\sum_{k\\in V^\\text{in}_S(i)} F_{ki} \\qquad\n \\forall i \\in L_S, \\;\n j \\in V_S^\\text{out}(i).\n\\tag{4}\\]\nFlow sign: Furthermore there are the non-negativity constraints for the flows and allocations, see The optimization variables.\n\n\n\n\n\n\n\nNote\n\n\n\nCurrently the fractional flow constraints are not taken into account in the implementation."
+ "objectID": "couple/modflow.html#sequential-coupled-solution",
+ "href": "couple/modflow.html#sequential-coupled-solution",
+ "title": "MODFLOW 6",
+ "section": "2.2 Sequential coupled solution",
+ "text": "2.2 Sequential coupled solution\nA coupled run stars by initializing both models and creating the exchange information: which MODFLOW 6 boundary condition is connected to which Ribasim basin. While the model is running, the model proceeds through time as follows:\n\nRibasim solves the equations on a basin level; this occurs with adaptive timestepping via ModelingToolkit.\nAt a specified time (in accordance with the MODFLOW 6 time discretization), the volumes are converted to MODFLOW 6 boundary condition levels using a Callback function.\nMODFLOW 6 runs a timestep.\nThe drainage and infiltration budgets are computed from MODFLOW 6 using the equations described above and aggregated per basin.\nThe aggregated values are set as Ribasim boundary conditions, and Ribasim solves until the next preset exchange time.\n\nThese steps run until the final timestep is finished."
},
{
- "objectID": "core/allocation.html#final-notes-on-the-allocation-problem",
- "href": "core/allocation.html#final-notes-on-the-allocation-problem",
- "title": "Allocation",
- "section": "1.3 Final notes on the allocation problem",
- "text": "1.3 Final notes on the allocation problem\n\n1.3.1 Users using their own return flow\nIf not explicitly avoided, users can use their own return flow in this allocation problem formulation. Therefore, return flow of users is only taken into account by allocation if that return flow is downstream of the user where it comes from. That is, if there is no path in the directed allocation graph from the user outflow node back to the user."
+ "objectID": "couple/modflow.html#iterative-coupled-solution",
+ "href": "couple/modflow.html#iterative-coupled-solution",
+ "title": "MODFLOW 6",
+ "section": "2.3 Iterative coupled solution",
+ "text": "2.3 Iterative coupled solution\n\n\n\n\n\n\nNote\n\n\n\nWe have not implemented an iterative coupled solution yet. The section below describes an approach.\n\n\nThe simplest form of a iterative solution occurs as follows:\n\nMODFLOW 6 computes drainage and infiltration flows.\nRibasim uses these flows to compute a storage volume for the basin.\nThe volume is translated to a water level for every MODFLOW 6 boundary condition in the basins.\nMODFLOW 6 recomputes drainage and infiltration flows with the updated water levels, etc.\n\nSuch a scheme is not maximally efficient: the discharge and infiltration terms are not constant, but are driven by a head difference. This head difference depends on the level of the boundary conditions and head of every cell of the groundwater model. If the surface waters of a basin empty, the water level will decrease and drainage and infiltration flows will change. Ideally, we can provide Ribasim with more information, so that it may estimate drainage and infiltration terms better.\nAs groundwater flow is often (approximately) linear, we can use linearization to more efficiently compute the flow from Ribasim’s side as well. In the iterative coupled solution, we are solving both MODFLOW 6 and Ribasim repeatedly, until they produce same drainage or infiltration (approximately). One of Ribasim’s basins contains many MODFLOW cells with boundary conditions. We could add every boundary condition to Ribasim’s equations, but this is costly and cumbersome. Fortunately, linearization allows us to “stack” (superpose) all the different boundary conditions into a single, simple equation. In linear form, every equation takes the form of:\n\\[\na = ph + q\n\\]\nNote the \\(h\\) rather than \\(\\phi\\), we are formulating from Ribasim’s perspective! We can sum all coefficients for p and q to provide a linear groundwater response to Ribasim.\n\n2.3.1 Drainage\nFrom Ribasim’s perspective, the groundwater head is constant given a timestep, so that:\n\\[\\begin{align}\np = -C \\\\\nq = -C\\phi\n\\end{align}\\]\nWhen the head falls below the drainage elevation, the coefficients are 0.\n\n\n2.3.2 River\nFrom Ribasim’s perspective, infiltration is never limited when the head falls below the bottom:\n\\[\\begin{align}\np = -C \\\\\nq = -Cb\n\\end{align}\\]\nOtherwise, infiltration and drainage occur with the same equation as for the drainage package:\n\\[\\begin{align}\np = -C \\\\\nq = -C\\phi\n\\end{align}\\]"
},
{
- "objectID": "core/allocation.html#example",
- "href": "core/allocation.html#example",
- "title": "Allocation",
- "section": "2.1 Example",
- "text": "2.1 Example\n\n\n\n\n\n\nNote\n\n\n\nAn example with figures and data will be added here after addition of allocation output files."
+ "objectID": "couple/modflow.html#parametrization",
+ "href": "couple/modflow.html#parametrization",
+ "title": "MODFLOW 6",
+ "section": "3.1 Parametrization",
+ "text": "3.1 Parametrization\nIn coupling Ribasim to MODFLOW 6, relations translating the Ribasim volume must be given for every every cell of every boundary condition. These consist of piecewise linear relationships between the basin volume and its associated water level for the boundary condition in the cell.\nThese values are stored in a netCDF dataset. This dataset must meet the following requirements:\n\nIt must contain a x and y coordinate. The extent and cell size of these coordinates must match the domain of the coupled MODFLOW 6 model exactly.\nIt must contain a variable (x, y) denoting the basin IDs.\nIt must contain a volume-level variable (x, y, row, column) for every coupled MODFLOW 6 boundary condition, describing the volume-level lookup table per cell.\n\n\n\n\n\n\n\nNote\n\n\n\nThe x and y coordinates are valid for structured MODFLOW 6 models (DIS). Discretized-by-vertices (DISV) and fully unstructured discretization (DISU). are not yet supported, but require no fundamental changes: one basin is connected to multiple MODFLOW 6 cells, and the coupling parameters must match the (structured, unstructured) grid of the MODFLOW 6 model exactly.\n\n\nThe MODFLOW 6 coupling example cases show examples of such a parametrization."
}
]
\ No newline at end of file