Skip to content

Commit

Permalink
Document parsing Delwaq results.
Browse files Browse the repository at this point in the history
  • Loading branch information
evetion committed Sep 24, 2024
1 parent c9948a2 commit a310f19
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 13 deletions.
93 changes: 89 additions & 4 deletions docs/guide/delwaq.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@
"model.plot(); # for later comparison"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's add another tracer to the model, to setup a fraction calculation."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ribasim.delwaq import add_tracer\n",
"\n",
"add_tracer(model, 11, \"Foo\")\n",
"add_tracer(model, 15, \"Bar\")\n",
"display(model.flow_boundary.concentration) # flow boundaries\n",
"display(model.level_boundary.concentration) # flow boundaries\n",
"\n",
"model.write(toml_path)"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -89,9 +112,7 @@
"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",
"output_path = Path(\"../../generated_testmodels/basic/delwaq\")\n",
"graph, substances = generate(toml_path, output_path)"
]
},
Expand Down Expand Up @@ -182,6 +203,70 @@
"- 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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Parsing the results\n",
"With Delwaq having run, we can now parse the results using `ribasim.delwaq.parse`. This function requires the `graph` and `substances` variables that were output by `ribasim.delwaq.generate`, as well as the path to the results folder of the Delwaq simulation."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ribasim.delwaq import parse\n",
"\n",
"nmodel = parse(toml_path, graph, substances, output_folder=output_path)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The parsed model is identical to the Ribasim model, with the exception of the added concentration_external table that contains all tracer results from Delwaq."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"display(nmodel.basin.concentration_external)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can use this table to plot the results of the Delwaq model, both spatially as over time."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ribasim.delwaq import plot_fraction\n",
"\n",
"plot_fraction(nmodel, 9, [\"Foo\", \"Bar\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ribasim.delwaq import plot_spatial\n",
"\n",
"plot_spatial(nmodel, \"FlowBoundary\")"
]
}
],
"metadata": {
Expand All @@ -200,7 +285,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.4"
"version": "3.12.5"
}
},
"nbformat": 4,
Expand Down
14 changes: 11 additions & 3 deletions python/ribasim/ribasim/delwaq/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from .generate import generate
from .generate import add_tracer, generate
from .parse import parse
from .plot import plot
from .plot import plot_fraction, plot_spatial
from .util import run_delwaq

__all__ = ["generate", "parse", "run_delwaq", "plot"]
__all__ = [
"generate",
"parse",
"run_delwaq",
"plot",
"add_tracer",
"plot_fraction",
"plot_spatial",
]
37 changes: 34 additions & 3 deletions python/ribasim/ribasim/delwaq/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from datetime import timedelta
from pathlib import Path

from ribasim.utils import MissingOptionalModule
from ribasim import nodes
from ribasim.utils import MissingOptionalModule, _pascal_to_snake

try:
import networkx as nx
Expand Down Expand Up @@ -276,13 +277,14 @@ def generate(
toml_path: Path,
output_folder=output_folder,
use_evaporation=USE_EVAP,
results_folder="results",
) -> 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")
basins = pd.read_feather(toml_path.parent / results_folder / "basin.arrow")
flows = pd.read_feather(toml_path.parent / results_folder / "flow.arrow")

Check warning on line 287 in python/ribasim/ribasim/delwaq/generate.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/generate.py#L286-L287

Added lines #L286 - L287 were not covered by tests

output_folder.mkdir(exist_ok=True)

Expand Down Expand Up @@ -409,10 +411,12 @@ def generate(
# Setup initial basin concentrations
defaults = {
"Continuity": 1.0,
"Initial": 1.0,
"Basin": 0.0,
"LevelBoundary": 0.0,
"FlowBoundary": 0.0,
"Terminal": 0.0,
"UserDemand": 0.0,
}
substances.update(defaults.keys())

Expand Down Expand Up @@ -486,6 +490,33 @@ def generate(
return G, substances


def add_tracer(model, node_id, tracer_name):
"""Add a tracer to the Delwaq model."""
n = model.node_table().df.loc[node_id]
node_type = n.node_type
if node_type not in [

Check warning on line 497 in python/ribasim/ribasim/delwaq/generate.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/generate.py#L495-L497

Added lines #L495 - L497 were not covered by tests
"Basin",
"LevelBoundary",
"FlowBoundary",
"UserDemand",
]:
raise ValueError("Can only trace Basins and boundaries")
snake_node_type = _pascal_to_snake(node_type)
nt = getattr(model, snake_node_type)

Check warning on line 505 in python/ribasim/ribasim/delwaq/generate.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/generate.py#L503-L505

Added lines #L503 - L505 were not covered by tests

ct = getattr(nodes, snake_node_type)
table = ct.Concentration(

Check warning on line 508 in python/ribasim/ribasim/delwaq/generate.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/generate.py#L507-L508

Added lines #L507 - L508 were not covered by tests
node_id=[node_id],
time=[model.starttime],
substance=[tracer_name],
concentration=[1.0],
)
if nt.concentration is None:
nt.concentration = table

Check warning on line 515 in python/ribasim/ribasim/delwaq/generate.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/generate.py#L514-L515

Added lines #L514 - L515 were not covered by tests
else:
nt.concentration = pd.concat([nt.concentration.df, table.df], ignore_index=True)

Check warning on line 517 in python/ribasim/ribasim/delwaq/generate.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/generate.py#L517

Added line #L517 was not covered by tests


if __name__ == "__main__":
# Generate a Delwaq model from the default Ribasim model
repo_dir = delwaq_dir.parents[1]
Expand Down
59 changes: 57 additions & 2 deletions python/ribasim/ribasim/delwaq/plot.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
def plot():
pass
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable


def plot_fraction(
model,
node_id,
tracers=["Basin", "LevelBoundary", "FlowBoundary", "UserDemand", "Initial"],
):
table = model.basin.concentration_external.df
table = table[table["node_id"] == node_id]
table = table[table["substance"].isin(tracers)]

Check warning on line 12 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L10-L12

Added lines #L10 - L12 were not covered by tests

groups = table.groupby("substance")
stack = {k: v["concentration"].to_numpy() for (k, v) in groups}

Check warning on line 15 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L14-L15

Added lines #L14 - L15 were not covered by tests

fig, ax = plt.subplots()
ax.stackplot(

Check warning on line 18 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L17-L18

Added lines #L17 - L18 were not covered by tests
groups.get_group(tracers[0])["time"],
stack.values(),
labels=stack.keys(),
)
ax.legend()
ax.set_title(f"Fraction plot for node {node_id}")
ax.set_xlabel("Time")
ax.set_ylabel("Fraction")

Check warning on line 26 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L23-L26

Added lines #L23 - L26 were not covered by tests

plt.show(fig)

Check warning on line 28 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L28

Added line #L28 was not covered by tests


def plot_spatial(model, tracer="Basin"):
table = model.basin.concentration_external.df
table = table[table["substance"] == tracer]
table = table[table["time"] == table["time"].max()]
table.set_index("node_id", inplace=True)

Check warning on line 35 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L32-L35

Added lines #L32 - L35 were not covered by tests

nodes = model.node_table().df
nodes = nodes[nodes.index.isin(table.index)]

Check warning on line 38 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L37-L38

Added lines #L37 - L38 were not covered by tests

fig, ax = plt.subplots()
s = ax.scatter(

Check warning on line 41 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L40-L41

Added lines #L40 - L41 were not covered by tests
nodes.geometry.x,
nodes.geometry.y,
c=table["concentration"][nodes.index],
clim=(0, 1),
)
ax.legend()
ax.set_title(f"Scatter plot for {tracer} tracer at {table["time"].iloc[0]}")

Check warning on line 48 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L47-L48

Added lines #L47 - L48 were not covered by tests

divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)

Check warning on line 51 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L50-L51

Added lines #L50 - L51 were not covered by tests

fig.colorbar(s, cax=cax, orientation="vertical")
ax.set_xlabel("x")
ax.set_ylabel("y")

Check warning on line 55 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L53-L55

Added lines #L53 - L55 were not covered by tests

plt.show(fig)

Check warning on line 57 in python/ribasim/ribasim/delwaq/plot.py

View check run for this annotation

Codecov / codecov/patch

python/ribasim/ribasim/delwaq/plot.py#L57

Added line #L57 was not covered by tests
8 changes: 7 additions & 1 deletion python/ribasim/tests/test_delwaq.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from pathlib import Path

import pytest
from ribasim.delwaq import generate, parse, run_delwaq
from ribasim import Model
from ribasim.delwaq import add_tracer, generate, parse, run_delwaq

delwaq_dir = Path(__file__).parent

Expand All @@ -14,6 +15,10 @@ def test_offline_delwaq_coupling():
repo_dir = delwaq_dir.parents[2]
toml_path = repo_dir / "generated_testmodels/basic/ribasim.toml"

model = Model.read(toml_path)
add_tracer(model, 17, "Foo")
model.write(toml_path)

graph, substances = generate(toml_path)
run_delwaq()
model = parse(toml_path, graph, substances)
Expand All @@ -27,6 +32,7 @@ def test_offline_delwaq_coupling():
"Cl",
"Continuity",
"FlowBoundary",
"Foo",
"LevelBoundary",
"Terminal",
"Tracer",
Expand Down

0 comments on commit a310f19

Please sign in to comment.