Quickstart (.py)¶
Note - this script can also be opened in interactive Python if you wanted to play around. On the GitHub it is in docs/demo/scripts
Introduction¶
WSIMOD is a Python package that lets you create nodes that represent physical things in the water cycle and enables them talk to each other. In this demo, we will create some nodes, some arcs (the things that link nodes), and make a model that lets us simulate the flow of water through the water cycle.
We will create a simple catchment model that contains a hydrological node, a sewer node, a groundwater node, and a river node. Using these nodes, we will simulate a mixed urban-rural catchment.
Imports and forcing data¶
Import packages
import os
import pandas as pd
from matplotlib import pyplot as plt
from wsimod.core import constants
from wsimod.orchestration.model import Model
Load input data, in this example we use precipitation, temperature, and evapotranspiration (et0).
# Select the root path for the data folder. Use the appropriate value for your case.
data_folder = os.path.join(os.path.abspath(""), "docs", "demo", "data")
input_fid = os.path.join(data_folder, "processed", "timeseries_data.csv")
input_data = pd.read_csv(input_fid)
input_data.loc[input_data.variable == "precipitation", "value"] *= constants.MM_TO_M
input_data.date = pd.to_datetime(input_data.date)
input_data = input_data.loc[input_data.site == "oxford_land"]
dates = input_data.date.drop_duplicates()
print(input_data.sample(10))
site date variable value 78325 oxford_land 2012-05-03 et0 0.002000 75773 oxford_land 2009-05-03 precipitation 0.000000 76948 oxford_land 2012-07-21 precipitation 0.000000 78342 oxford_land 2012-05-20 et0 0.002000 79450 oxford_land 2011-06-07 temperature 15.400000 79443 oxford_land 2011-05-31 temperature 13.700000 75872 oxford_land 2009-08-10 precipitation 0.000400 79782 oxford_land 2012-05-04 temperature 11.017857 77757 oxford_land 2010-10-13 et0 0.002000 77872 oxford_land 2011-02-05 et0 0.002000
Input data is stored in dicts, where a key is a variable on a given day.
land_inputs = input_data.set_index(["variable", "date"]).value.to_dict()
example_date = pd.to_datetime("2009-03-03")
print(land_inputs[("precipitation", example_date)])
print(land_inputs[("et0", example_date)])
print(land_inputs[("temperature", example_date)])
0.015 0.002 7.625
Create nodes¶
Nodes can be defined as dictionaries of parameters. Different nodes require different parameters, you can see the documentation in the API to understand what parameters can be set. Although every node should have a type (type_) and a name.
sewer = {"type_": "Sewer", "capacity": 0.04, "name": "my_sewer"}
surface1 = {
"type_": "ImperviousSurface",
"surface": "urban",
"area": 10,
"pollutant_load": {"phosphate": 1e-7},
}
surface2 = {
"type_": "PerviousSurface",
"surface": "rural",
"area": 100,
"depth": 0.5,
"pollutant_load": {"phosphate": 1e-7},
}
land = {
"type_": "Land",
"data_input_dict": land_inputs,
"surfaces": [surface1, surface2],
"name": "my_land",
}
gw = {"type_": "Groundwater", "area": 100, "capacity": 100, "name": "my_groundwater"}
node = {"type_": "Node", "name": "my_river"}
waste = {"type_": "Waste", "name": "my_outlet"}
Create arcs¶
Arcs can also be created as dictionaries, they don't typically need any numerical parameters (although there are some exceptions in the case study demo). Though they do need to specify the in_port (where the arc starts), and out_port (where it finishes). It's also handy to give each arc a name.
urban_drainage = {
"type_": "Arc",
"in_port": "my_land",
"out_port": "my_sewer",
"name": "urban_drainage",
}
percolation = {
"type_": "Arc",
"in_port": "my_land",
"out_port": "my_groundwater",
"name": "percolation",
}
runoff = {
"type_": "Arc",
"in_port": "my_land",
"out_port": "my_river",
"name": "runoff",
}
storm_outflow = {
"type_": "Arc",
"in_port": "my_sewer",
"out_port": "my_river",
"name": "storm_outflow",
}
baseflow = {
"type_": "Arc",
"in_port": "my_groundwater",
"out_port": "my_river",
"name": "baseflow",
}
catchment_outflow = {
"type_": "Arc",
"in_port": "my_river",
"out_port": "my_outlet",
"name": "catchment_outflow",
}
Create model¶
We can create a model object and add dates
my_model = Model()
my_model.dates = dates
Add nodes in a list. The Model object will create the nodes from the dict entries.
my_model.add_nodes([sewer, land, gw, node, waste])
Add arcs in a list.
my_model.add_arcs(
[urban_drainage, percolation, runoff, storm_outflow, baseflow, catchment_outflow]
)
Run model¶
Run the model
flows, _, _, _ = my_model.run()
flows = pd.DataFrame(flows)
0%| | 0/1456 [00:00<?, ?it/s]
10%|▉ | 140/1456 [00:00<00:00, 1399.86it/s]
19%|█▉ | 280/1456 [00:00<00:00, 1265.10it/s]
28%|██▊ | 408/1456 [00:00<00:00, 1216.85it/s]
36%|███▋ | 531/1456 [00:00<00:00, 1213.63it/s]
45%|████▍ | 653/1456 [00:00<00:00, 1205.92it/s]
53%|█████▎ | 774/1456 [00:00<00:00, 1202.45it/s]
61%|██████▏ | 895/1456 [00:00<00:00, 1201.02it/s]
70%|██████▉ | 1016/1456 [00:00<00:00, 1202.01it/s]
78%|███████▊ | 1137/1456 [00:00<00:00, 1203.71it/s]
86%|████████▋ | 1258/1456 [00:01<00:00, 1188.30it/s]
95%|█████████▍| 1377/1456 [00:01<00:00, 1182.07it/s]
100%|██████████| 1456/1456 [00:01<00:00, 1203.16it/s]
Plot results
f, axs_ = plt.subplots(3, 2, figsize=(10, 10))
for axs, variable in zip(axs_.T, ["flow", "phosphate"]):
flows_plot = flows.pivot(index="time", columns="arc", values=variable)
input_data.pivot(index="date", columns="variable", values="value")[
["precipitation"]
].plot(ax=axs[0], xlabel="", xticks=[], sharex=True)
flows_plot[["runoff", "urban_drainage", "percolation"]].plot(
ax=axs[1], xlabel="", xticks=[], sharex=True
)
flows_plot[["catchment_outflow", "storm_outflow", "baseflow"]].plot(ax=axs[2])
axs_[1, 0].set_title("Flows (m3/d)")
axs_[1, 1].set_title("Phosphate (kg/d)")
f.tight_layout()
What next?¶
If you are hydrologically minded then you might be interested in a more detailed tutorial for our Land node. If you want an overview to see how many different nodes in WSIMOD work, then the Oxford case study might be more interesting.