User Guide
WaCo is built on top of WNTR, that is a wrapper of EPANET.
import waco
import wntr
Read the water network
WaCo uses the WaterNetworkModel
provided by WNTR to manage EPANET networks (with .inp
extension).
Some example networks are provided in the examples/networks
directory.
# Read and plot the water network using wntr
wn = wntr.network.WaterNetworkModel("examples/networks/Anytown.inp")
wntr.graphics.plot_network(wn);
Run simulations to analyze the diffusion of a contaminant
WaCo allows to simply analyze the diffusion of a contaminant in the network, considering different injection points, by using the waco.sim.contamination
function. This function runs a water quality simulation for each injection points specified by the parameter inj_nodes
; by default, all the junctions of the network are considered as injection points. For each simulation, the varying contaminant concentration (percentage) is tracked across the entire network.
trace = waco.sim.contamination(wn=wn)
This results in a dataframe in which, each column represents an injection node (except for the "time" and "node" columns that are row ids) and each row represents the contaminant concentration in a node of the network at a specific simulation time for all the injection nodes.
time | node | 1 | 2 | ... | 21 | 22 |
---|---|---|---|---|---|---|
25200 | 6 | 32.408066 | 14.922180 | ... | 34.149334 | 25.908562 |
25200 | 7 | 32.408066 | 14.922180 | ... | 34.149334 | 25.908562 |
25200 | 8 | 32.408066 | 14.922180 | ... | 34.149334 | 25.908562 |
25200 | 9 | 8.921225 | 0.914897 | ... | 8.870847 | 65.627129 |
25200 | 10 | 12.547630 | 0.000000 | ... | 0.000000 | 84.482864 |
Let's print some details:
print(f"Columns: {list(trace.columns)}")
print(f"Simulation timesteps: {trace['time'].unique().tolist()}")
print(f"Water network nodes: {trace['node'].unique().tolist()}")
The output is:
Columns: ['time', 'node', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22']
Simulation timesteps: [0, 3600, 7200, 10800, 14400, 18000, 21600, 25200, 28800, 32400, 36000, 39600, 43200, 46800, 50400, 54000, 57600, 61200, 64800, 68400, 72000, 75600, 79200, 82800, 86400]
Water network nodes: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '40', '41', '42']
It is possible to change the injection nodes as well as the simulation duration and timestep (in seconds).
In the following example, the injection points are limited to the nodes with ids 1
, 2
, 21
and 22
, the simulation duration is set to 5 hours and data are registered every 15 minutes.
trace = waco.sim.contamination(wn=wn,
inj_nodes=["1", "2", "21", "22"],
duration=5 * 3600, # 5 hours
timestep=15 * 60) # 15 minutes
The result looks like the following:
time | node | 1 | 2 | 21 | 22 |
---|---|---|---|---|---|
0 | 1 | 100.0 | 0.0 | 0.0 | 0.0 |
0 | 2 | 0.0 | 100.0 | 0.0 | 0.0 |
... | ... | ... | ... | ... | ... |
18000 | 16 | 55.391529 | 0.0 | 9.2 | 21.9 |
18000 | 17 | 12.067455 | 0.0 | 0.0 | 85.5 |
Compute the Detection Times
A useful insight is how long it takes to detect the contaminant at a specific location of the network (e.g., node). Considering that the contaminant is detected at a node when it exceeds a concentration percentage (sensibility
), the function waco.analyzer.detection_time
can be used to compute the detection times.
det_time = waco.analyzer.detection_time(trace, sensibility=5)
The result is a Dataframe with the time the contaminant concentration reach the specified threshold (i.e., sensibility
) in node
when injected in inj_node
.
node | inj_node | time |
---|---|---|
1 | 1 | 0 |
1 | 2 | 18900 |
... | ... | ... |
9 | 21 | 18900 |
9 | 22 | 5400 |
Compute the Volumes of Contaminated Water
Another information that can be extracted from the simulations is the volume of contaminated water consumed prior to detection. First, the water demands at each node have to be computed using the waco.sim.demand
function. Then, the waco.analyzer.contaminated_volume
function can be used to compute the volume of contaminated water.
Note
The same time granularity (duration
and timestep
) is required for both the demand
and trace
to be able to compute the volume of contaminated water.
demand = waco.sim.water_demand(wn,
duration=5 * 3600, # 5 hours
timestep=15 * 60) # 15 minutes
contam_vol = waco.analyzer.contaminated_volume(trace=trace,
det_time=det_time,
demand=demand)
The demands are returned as a Dataframe with the water demand at each node
and time
.
time | node | demand |
---|---|---|
0 | 1 | 0.031545 |
0 | 2 | 0.012618 |
... | ... | ... |
18000 | 18 | 0.031545 |
18000 | 19 | 0.063090 |
Then, the volume of contaminated water is returned as a Dataframe with a structure similar to the detection times.
node | inj_node | volume |
---|---|---|
1 | 1 | 0.031545 |
1 | 2 | 0.116008 |
... | ... | ... |
9 | 21 | 0.118453 |
9 | 22 | 0.102254 |