From a00276bf5be0d01468568470e4f4d57e68a249cb Mon Sep 17 00:00:00 2001 From: Maarten Pronk Date: Tue, 9 Jul 2024 16:51:01 +0200 Subject: [PATCH 1/4] Add Delwaq coupling guide. --- docs/guide/delwaq.ipynb | 161 ++++++++++++++++++++++ python/ribasim/ribasim/delwaq/generate.py | 10 +- python/ribasim/ribasim/delwaq/parse.py | 4 +- 3 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 docs/guide/delwaq.ipynb diff --git a/docs/guide/delwaq.ipynb b/docs/guide/delwaq.ipynb new file mode 100644 index 000000000..ea1a6fac3 --- /dev/null +++ b/docs/guide/delwaq.ipynb @@ -0,0 +1,161 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: \"Ribasim Delwaq coupling\"\n", + "---" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", + "from ribasim import Model\n", + "from ribasim.delwaq import generate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to generate the Delwaq input files, we need a completed Ribasim simulation (typically one with a results folder). Let's take a complete HWS model for example. Please adjust the paths and filenames to your own case.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "toml_path = Path(\"../../hws_2024_6_0/hws.toml\")\n", + "output_path = Path(\n", + " \"../../hws_2024_6_0/delwaq\"\n", + ") # also set a path where we store the Delwaq input files\n", + "assert toml_path.is_file()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can call `generate`, generating the required input files for Delwaq." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph, substances = generate(toml_path, output_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's inspect the generated files.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "list(output_path.iterdir())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The two output variables `graph` and `substances` are required for parsing the results of the Delwaq model later on. Nonetheless, we can also inspect them here, and inspect the graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "substances # list of substances, as will be present in the Delwaq netcdf output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's draw the graph\n", + "# plt.clf()\n", + "plt.figure(figsize=(15, 15))\n", + "nx.draw(\n", + " graph,\n", + " pos={k: v[\"pos\"] for k, v in graph.nodes(data=True)},\n", + " with_labels=True,\n", + " labels={\n", + " k: v[\"id\"] for k, v in graph.nodes(data=True)\n", + " }, # uncomment for original node id\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create a geopackage that contains our node mapping between Ribasim and Delwaq." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = Model.read(toml_path)\n", + "nodes = model.node_table()\n", + "\n", + "mapping = {k: v[\"id\"] for k, v in graph.nodes(data=True)}\n", + "\n", + "df = nodes.df[nodes.df[\"node_id\"].isin(mapping.values())]\n", + "df.set_index(\"node_id\", inplace=True)\n", + "df = df.loc[list(mapping.values())]\n", + "df[\"meta_delwaq_id\"] = mapping.keys()\n", + "\n", + "df.to_file(output_path / \"mapping.gpkg\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "default", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/python/ribasim/ribasim/delwaq/generate.py b/python/ribasim/ribasim/delwaq/generate.py index fdfbd0c6e..6f2f72b41 100644 --- a/python/ribasim/ribasim/delwaq/generate.py +++ b/python/ribasim/ribasim/delwaq/generate.py @@ -31,6 +31,7 @@ ) delwaq_dir = Path(__file__).parent +output_folder = delwaq_dir / "model" env = jinja2.Environment( autoescape=True, loader=jinja2.FileSystemLoader(delwaq_dir / "template") @@ -41,7 +42,11 @@ USE_EVAP = True -def generate(toml_path: Path) -> tuple[nx.DiGraph, set[str]]: +def generate( + toml_path: Path, + output_folder=output_folder, + use_evaporation=USE_EVAP, +) -> tuple[nx.DiGraph, set[str]]: """Generate a Delwaq model from a Ribasim model and results.""" # Read in model and results @@ -49,7 +54,6 @@ def generate(toml_path: Path) -> tuple[nx.DiGraph, set[str]]: basins = pd.read_feather(toml_path.parent / "results" / "basin.arrow") flows = pd.read_feather(toml_path.parent / "results" / "flow.arrow") - output_folder = delwaq_dir / "model" output_folder.mkdir(exist_ok=True) # Setup flow network @@ -184,7 +188,7 @@ def generate(toml_path: Path) -> tuple[nx.DiGraph, set[str]]: boundary=(node["id"], "precipitation"), ) - if USE_EVAP: + if use_evaporation: boundary_id -= 1 G.add_node( boundary_id, diff --git a/python/ribasim/ribasim/delwaq/parse.py b/python/ribasim/ribasim/delwaq/parse.py index 7c0001e8c..3c527d02a 100644 --- a/python/ribasim/ribasim/delwaq/parse.py +++ b/python/ribasim/ribasim/delwaq/parse.py @@ -17,7 +17,9 @@ output_folder = delwaq_dir / "model" -def parse(toml_path: Path, graph, substances) -> ribasim.Model: +def parse( + toml_path: Path, graph, substances, output_folder=output_folder +) -> ribasim.Model: model = ribasim.Model.read(toml_path) # Output of Delwaq From 080367b266a05ef8364085bccfd66cd7c88bf012 Mon Sep 17 00:00:00 2001 From: Maarten Pronk Date: Fri, 9 Aug 2024 23:16:01 +0200 Subject: [PATCH 2/4] Flesh out tutorial and run actual Ribasim model in the background. --- docs/_quarto.yml | 3 + docs/guide/coupling.qmd | 5 +- docs/guide/delwaq.ipynb | 123 +++++++++++++++++++++++++++------------- 3 files changed, 89 insertions(+), 42 deletions(-) diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 0a69bdbe5..ec52e0725 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -43,6 +43,9 @@ website: - guide/index.qmd - guide/examples.ipynb - guide/coupling.qmd + - section: "Coupling guides" + contents: + - guide/delwaq.ipynb - title: "Concepts" contents: diff --git a/docs/guide/coupling.qmd b/docs/guide/coupling.qmd index 95a5019b9..c1ac43ecb 100644 --- a/docs/guide/coupling.qmd +++ b/docs/guide/coupling.qmd @@ -10,7 +10,7 @@ Ribasim can also be (online) coupled to other kernels with the help of iMOD Coup ## Water quality -Ribasim can be offline coupled to Delwaq, the Deltares Water Quality model. Note that this functionality is still in active development. +Ribasim can be offline coupled to Delwaq, the Deltares Water Quality model. ```{mermaid} flowchart LR @@ -21,5 +21,4 @@ flowchart LR Delwaq can calculate the concentration of substances in [Basin](/reference/node/basin.qmd) nodes over time, based on initial concentrations, and of [FlowBoundary](/reference/node/flow-boundary.qmd) nodes. Ribasim exposes the `Basin / concentration`, `Basin / concentration_state`, `FlowBoundary / concentration`, and `LevelBoundary / concentration` tables to setup these substances and concentrations. -When a Ribasim model ran with the above tables, one can use the utilities in the `coupling/delwaq` folder to generate the input required for Delwaq to run, as well as to parse the output from Delwaq into a Ribasim compatible format. -For more information see the `README.md` in the same folder. +When a Ribasim model ran with the above tables, one can use the utilities in the `delwaq` namespace of the Ribasim Python API to generate the input required for Delwaq to run, as well as to parse the output from Delwaq into a Ribasim compatible format. For more information see the [guide](/guide/delwaq.ipynb). diff --git a/docs/guide/delwaq.ipynb b/docs/guide/delwaq.ipynb index ea1a6fac3..f5d76f924 100644 --- a/docs/guide/delwaq.ipynb +++ b/docs/guide/delwaq.ipynb @@ -9,6 +9,13 @@ "---" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to generate the Delwaq input files, we need a completed Ribasim simulation (typically one with a results folder) that ideally also includes some substances and initial concentrations. Let's take the basic test model for example, which already has set some initial concentrations.\n" + ] + }, { "cell_type": "code", "execution_count": null, @@ -17,17 +24,15 @@ "source": [ "from pathlib import Path\n", "\n", - "import matplotlib.pyplot as plt\n", - "import networkx as nx\n", - "from ribasim import Model\n", - "from ribasim.delwaq import generate" + "toml_path = Path(\"../../generated_testmodels/basic/ribasim.toml\")\n", + "assert toml_path.is_file()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In order to generate the Delwaq input files, we need a completed Ribasim simulation (typically one with a results folder). Let's take a complete HWS model for example. Please adjust the paths and filenames to your own case.\n" + "This Ribasim model already has substance concentrations for `Cl` and `Tracer` in the input tables, and we will use these to generate the Delwaq input files." ] }, { @@ -36,18 +41,42 @@ "metadata": {}, "outputs": [], "source": [ - "toml_path = Path(\"../../hws_2024_6_0/hws.toml\")\n", - "output_path = Path(\n", - " \"../../hws_2024_6_0/delwaq\"\n", - ") # also set a path where we store the Delwaq input files\n", - "assert toml_path.is_file()" + "from ribasim import Model\n", + "\n", + "model = Model.read(toml_path)\n", + "\n", + "display(model.basin.concentration_state) # basin initial state\n", + "display(model.basin.concentration) # basin boundaries\n", + "display(model.flow_boundary.concentration) # flow boundaries\n", + "display(model.level_boundary.concentration) # level boundaries\n", + "model.plot(); # for later comparison" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# | include: false\n", + "from subprocess import run\n", + "\n", + "run(\n", + " [\n", + " \"julia\",\n", + " \"--project=../../core\",\n", + " \"--eval\",\n", + " f'using Ribasim; Ribasim.main(\"{toml_path.as_posix()}\")',\n", + " ],\n", + " check=True,\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now we can call `generate`, generating the required input files for Delwaq." + "Given the path to a completed Ribasim simulation, we can call `ribasim.delwaq.generate` for generating the required input files for Delwaq from scratch." ] }, { @@ -56,6 +85,11 @@ "metadata": {}, "outputs": [], "source": [ + "from ribasim.delwaq import generate\n", + "\n", + "output_path = Path(\n", + " \"../../generated_testmodels/basic/delwaq\"\n", + ") # set a path where we store the Delwaq input files\n", "graph, substances = generate(toml_path, output_path)" ] }, @@ -63,7 +97,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's inspect the generated files.\n" + "This call produces a handful of files in the user defined folder. Let's take a look at them:\n" ] }, { @@ -79,16 +113,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The two output variables `graph` and `substances` are required for parsing the results of the Delwaq model later on. Nonetheless, we can also inspect them here, and inspect the graph." + "These files form a complete Delwaq simulation, and can be run by either pointing DIMR to the `dimr_config.xml` file or pointing Delwaq to the `delwaq.inp` file." ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "substances # list of substances, as will be present in the Delwaq netcdf output" + "Note that the call to `generate` produces two output variables; `graph` and `substances` that are required for parsing the results of the Delwaq model later on. Nonetheless, we can also inspect them here, and inspect the created Delwaq network." ] }, { @@ -97,24 +129,14 @@ "metadata": {}, "outputs": [], "source": [ - "# Let's draw the graph\n", - "# plt.clf()\n", - "plt.figure(figsize=(15, 15))\n", - "nx.draw(\n", - " graph,\n", - " pos={k: v[\"pos\"] for k, v in graph.nodes(data=True)},\n", - " with_labels=True,\n", - " labels={\n", - " k: v[\"id\"] for k, v in graph.nodes(data=True)\n", - " }, # uncomment for original node id\n", - ")" + "substances # list of substances, as will be present in the Delwaq netcdf output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's create a geopackage that contains our node mapping between Ribasim and Delwaq." + "As you can see, the complete substances list is a combination of user input (`Cl` and `Tracer` in the input tables), a `Continuity` tracer, and tracers for all nodetypes in the Ribasim model. The latter tracers allow for deeper inspection of the Ribasim model, such as debugging the mass balance by plotting fraction graphs. Let's inspect the `graph` next, which is the Delwaq network that was created from the Ribasim model:" ] }, { @@ -123,17 +145,40 @@ "metadata": {}, "outputs": [], "source": [ - "model = Model.read(toml_path)\n", - "nodes = model.node_table()\n", - "\n", - "mapping = {k: v[\"id\"] for k, v in graph.nodes(data=True)}\n", + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", "\n", - "df = nodes.df[nodes.df[\"node_id\"].isin(mapping.values())]\n", - "df.set_index(\"node_id\", inplace=True)\n", - "df = df.loc[list(mapping.values())]\n", - "df[\"meta_delwaq_id\"] = mapping.keys()\n", + "# Let's draw the graph\n", + "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n", + "nx.draw(\n", + " graph,\n", + " pos={k: v[\"pos\"] for k, v in graph.nodes(data=True)},\n", + " with_labels=True,\n", + " labels={k: k for k, v in graph.nodes(data=True)},\n", + " ax=ax[0],\n", + ")\n", + "ax[0].set_title(\"Delwaq node IDs\")\n", + "nx.draw(\n", + " graph,\n", + " pos={k: v[\"pos\"] for k, v in graph.nodes(data=True)},\n", + " with_labels=True,\n", + " labels={k: v[\"id\"] for k, v in graph.nodes(data=True)},\n", + " ax=ax[1],\n", + ")\n", + "ax[1].set_title(\"Ribasim node IDs\")\n", + "fig.suptitle(\"Delwaq network\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we plotted the Delwaq network twice, with the node ids as used by Delwaq on the left hand side, and the corresponding Ribasim node ids on the right hand side.\n", + "As you can see, the Delwaq network is very similar to the Ribasim network, with some notable changes:\n", "\n", - "df.to_file(output_path / \"mapping.gpkg\")" + "- All non-Basin or non-boundary types are removed (e.g. no more Pumps or TabulatedRatingCurves)\n", + "- Basin boundaries are split into separate nodes and links (drainage, precipitation, and evaporation, as indicated by the duplicated basin ids on the right hand side)\n", + "- All node ids have been renumbered, with boundaries being negative, and basins being positive." ] } ], @@ -153,7 +198,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.12.4" } }, "nbformat": 4, From 0600f67e0d330ae430d7f350f2424fab6a339140 Mon Sep 17 00:00:00 2001 From: Maarten Pronk Date: Fri, 9 Aug 2024 23:55:45 +0200 Subject: [PATCH 3/4] Split out the complex generate function. --- python/ribasim/ribasim/delwaq/generate.py | 181 ++++++++++++---------- 1 file changed, 100 insertions(+), 81 deletions(-) diff --git a/python/ribasim/ribasim/delwaq/generate.py b/python/ribasim/ribasim/delwaq/generate.py index 6f2f72b41..e3ae67f92 100644 --- a/python/ribasim/ribasim/delwaq/generate.py +++ b/python/ribasim/ribasim/delwaq/generate.py @@ -42,23 +42,53 @@ USE_EVAP = True -def generate( - toml_path: Path, - output_folder=output_folder, - use_evaporation=USE_EVAP, -) -> tuple[nx.DiGraph, set[str]]: - """Generate a Delwaq model from a Ribasim model and results.""" - - # Read in model and results - model = ribasim.Model.read(toml_path) - basins = pd.read_feather(toml_path.parent / "results" / "basin.arrow") - flows = pd.read_feather(toml_path.parent / "results" / "flow.arrow") +def _boundary_name(id, type): + # Delwaq has a limit of 12 characters for the boundary name + return type[:9] + "_" + str(id) + + +def _quote(value): + return f"'{value}'" + + +def _make_boundary(data, boundary_type): + """ + Create a Delwaq boundary definition with the given data and boundary type. + Pivot our data from long to wide format, and convert the time to a string. + + Specifically, we go from a table: + `node_id, substance, time, concentration` + to + ``` + ITEM 'Drainage_6' + CONCENTRATIONS 'Cl' 'Tracer' + ABSOLUTE TIME + LINEAR DATA 'Cl' 'Tracer' + '2020/01/01-00:00:00' 0.0 1.0 + '2020/01/02-00:00:00' 1.0 -999 + ``` + """ + bid = _boundary_name(data.node_id.iloc[0], boundary_type) + piv = ( + data.pivot_table(index="time", columns="substance", values="concentration") + .reset_index() + .reset_index(drop=True) + ) + piv.time = piv.time.dt.strftime("%Y/%m/%d-%H:%M:%S") + boundary = { + "name": bid, + "substances": list(map(_quote, piv.columns[1:])), + "df": piv.to_string( + formatters={"time": _quote}, header=False, index=False, na_rep=-999 + ), + } + substances = data.substance.unique() + return boundary, substances - output_folder.mkdir(exist_ok=True) - # Setup flow network +def _setup_graph(nodes, edge, use_evaporation=True): G = nx.DiGraph() - nodes = model.node_table() + assert nodes.df is not None for row in nodes.df.itertuples(): if row.node_type not in ribasim.geometry.edge.SPATIALCONTROLNODETYPES: @@ -70,8 +100,8 @@ def generate( y=row.geometry.y, pos=(row.geometry.x, row.geometry.y), ) - assert model.edge.df is not None - for row in model.edge.df.itertuples(): + assert edge.df is not None + for row in edge.df.itertuples(): if row.edge_type == "flow": G.add_edge( f"{row.from_node_type} #{row.from_node_id}", @@ -210,6 +240,57 @@ def generate( for edge_id in d["id"]: edge_mapping[edge_id] = i + assert len(basin_mapping) == basin_id + + return G, merge_edges, node_mapping, edge_mapping, basin_mapping + + +def _setup_boundaries(model): + boundaries = [] + substances = set() + + if model.level_boundary.concentration.df is not None: + for _, rows in model.level_boundary.concentration.df.groupby(["node_id"]): + boundary, substance = _make_boundary(rows, "LevelBoundary") + boundaries.append(boundary) + substances.update(substance) + + if model.flow_boundary.concentration.df is not None: + for _, rows in model.flow_boundary.concentration.df.groupby("node_id"): + boundary, substance = _make_boundary(rows, "FlowBoundary") + boundaries.append(boundary) + substances.update(substance) + + if model.basin.concentration.df is not None: + for _, rows in model.basin.concentration.df.groupby(["node_id"]): + for boundary_type in ("Drainage", "Precipitation"): + nrows = rows.rename(columns={boundary_type.lower(): "concentration"}) + boundary, substance = _make_boundary(nrows, boundary_type) + boundaries.append(boundary) + substances.update(substance) + + return boundaries, substances + + +def generate( + toml_path: Path, + output_folder=output_folder, + use_evaporation=USE_EVAP, +) -> tuple[nx.DiGraph, set[str]]: + """Generate a Delwaq model from a Ribasim model and results.""" + + # Read in model and results + model = ribasim.Model.read(toml_path) + basins = pd.read_feather(toml_path.parent / "results" / "basin.arrow") + flows = pd.read_feather(toml_path.parent / "results" / "flow.arrow") + + output_folder.mkdir(exist_ok=True) + + # Setup flow network + G, merge_edges, node_mapping, edge_mapping, basin_mapping = _setup_graph( + model.node_table(), model.edge, use_evaporation=use_evaporation + ) + # Plot # plt.figure(figsize=(18, 18)) # nx.draw( @@ -231,7 +312,7 @@ def generate( pointer.to_csv(output_folder / "network.csv", index=False) # not needed write_pointer(output_folder / "ribasim.poi", pointer) - total_segments = basin_id + total_segments = len(basin_mapping) total_exchanges = len(pointer) # Write attributes template @@ -313,69 +394,7 @@ def generate( write_flows(output_folder / "ribasim.len", lengths, timestep) # Find all boundary substances and concentrations - boundaries = [] - substances = set() - - def boundary_name(id, type): - # Delwaq has a limit of 12 characters for the boundary name - return type[:9] + "_" + str(id) - - def quote(value): - return f"'{value}'" - - def make_boundary(data, boundary_type): - """ - Create a Delwaq boundary definition with the given data and boundary type. - Pivot our data from long to wide format, and convert the time to a string. - - Specifically, we go from a table: - `node_id, substance, time, concentration` - to - ``` - ITEM 'Drainage_6' - CONCENTRATIONS 'Cl' 'Tracer' - ABSOLUTE TIME - LINEAR DATA 'Cl' 'Tracer' - '2020/01/01-00:00:00' 0.0 1.0 - '2020/01/02-00:00:00' 1.0 -999 - ``` - """ - bid = boundary_name(data.node_id.iloc[0], boundary_type) - piv = ( - data.pivot_table(index="time", columns="substance", values="concentration") - .reset_index() - .reset_index(drop=True) - ) - piv.time = piv.time.dt.strftime("%Y/%m/%d-%H:%M:%S") - boundary = { - "name": bid, - "substances": list(map(quote, piv.columns[1:])), - "df": piv.to_string( - formatters={"time": quote}, header=False, index=False, na_rep=-999 - ), - } - substances = data.substance.unique() - return boundary, substances - - if model.level_boundary.concentration.df is not None: - for _, rows in model.level_boundary.concentration.df.groupby(["node_id"]): - boundary, substance = make_boundary(rows, "LevelBoundary") - boundaries.append(boundary) - substances.update(substance) - - if model.flow_boundary.concentration.df is not None: - for _, rows in model.flow_boundary.concentration.df.groupby("node_id"): - boundary, substance = make_boundary(rows, "FlowBoundary") - boundaries.append(boundary) - substances.update(substance) - - if model.basin.concentration.df is not None: - for _, rows in model.basin.concentration.df.groupby(["node_id"]): - for boundary_type in ("Drainage", "Precipitation"): - nrows = rows.rename(columns={boundary_type.lower(): "concentration"}) - boundary, substance = make_boundary(nrows, boundary_type) - boundaries.append(boundary) - substances.update(substance) + boundaries, substances = _setup_boundaries(model) # Write boundary data with substances and concentrations template = env.get_template("B5_bounddata.inc.j2") @@ -431,7 +450,7 @@ def make_boundary(data, boundary_type): bnd.sort_values(by="bid", ascending=False, inplace=True) bnd["node_type"] = [G.nodes(data="type")[bid] for bid in bnd["bid"]] bnd["node_id"] = [G.nodes(data="id")[bid] for bid in bnd["bid"]] - bnd["fid"] = list(map(boundary_name, bnd["node_id"], bnd["node_type"])) + bnd["fid"] = list(map(_boundary_name, bnd["node_id"], bnd["node_type"])) bnd["comment"] = "" bnd = bnd[["fid", "comment", "node_type"]] bnd.to_csv( From d89d8de8abf3073fdffdd1ed03278bfd3485080e Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Tue, 13 Aug 2024 10:19:42 +0200 Subject: [PATCH 4/4] Add link to generated testmodels, and capitalization --- docs/guide/delwaq.ipynb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/guide/delwaq.ipynb b/docs/guide/delwaq.ipynb index f5d76f924..e81472512 100644 --- a/docs/guide/delwaq.ipynb +++ b/docs/guide/delwaq.ipynb @@ -13,7 +13,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In order to generate the Delwaq input files, we need a completed Ribasim simulation (typically one with a results folder) that ideally also includes some substances and initial concentrations. Let's take the basic test model for example, which already has set some initial concentrations.\n" + "In order to generate the Delwaq input files, we need a completed Ribasim simulation (typically one with a results folder) that ideally also includes some substances and initial concentrations. Let's take the basic test model for example, which already has set some initial concentrations.\n", + "\n", + "All testmodels can be [downloaded from here](/install.qmd#sec-download)." ] }, { @@ -173,12 +175,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here we plotted the Delwaq network twice, with the node ids as used by Delwaq on the left hand side, and the corresponding Ribasim node ids on the right hand side.\n", + "Here we plotted the Delwaq network twice, with the node IDs as used by Delwaq on the left hand side, and the corresponding Ribasim node IDs on the right hand side.\n", "As you can see, the Delwaq network is very similar to the Ribasim network, with some notable changes:\n", "\n", "- All non-Basin or non-boundary types are removed (e.g. no more Pumps or TabulatedRatingCurves)\n", - "- Basin boundaries are split into separate nodes and links (drainage, precipitation, and evaporation, as indicated by the duplicated basin ids on the right hand side)\n", - "- All node ids have been renumbered, with boundaries being negative, and basins being positive." + "- Basin boundaries are split into separate nodes and links (drainage, precipitation, and evaporation, as indicated by the duplicated Basin IDs on the right hand side)\n", + "- All node IDs have been renumbered, with boundaries being negative, and Basins being positive." ] } ],