epyt-flow 0.1.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epyt_flow/EPANET/compile_linux.sh +4 -0
- epyt_flow/EPANET/compile_macos.sh +4 -0
- epyt_flow/VERSION +1 -1
- epyt_flow/__init__.py +29 -18
- epyt_flow/data/benchmarks/leakdb.py +7 -12
- epyt_flow/data/networks.py +404 -40
- epyt_flow/rest_api/base_handler.py +14 -0
- epyt_flow/rest_api/scada_data/__init__.py +0 -0
- epyt_flow/rest_api/{scada_data_handler.py → scada_data/data_handlers.py} +3 -162
- epyt_flow/rest_api/scada_data/export_handlers.py +140 -0
- epyt_flow/rest_api/scada_data/handlers.py +209 -0
- epyt_flow/rest_api/scenario/__init__.py +0 -0
- epyt_flow/rest_api/scenario/event_handlers.py +118 -0
- epyt_flow/rest_api/{scenario_handler.py → scenario/handlers.py} +86 -67
- epyt_flow/rest_api/scenario/simulation_handlers.py +174 -0
- epyt_flow/rest_api/scenario/uncertainty_handlers.py +118 -0
- epyt_flow/rest_api/server.py +61 -24
- epyt_flow/simulation/events/leakages.py +27 -17
- epyt_flow/simulation/scada/scada_data.py +545 -14
- epyt_flow/simulation/scada/scada_data_export.py +39 -12
- epyt_flow/simulation/scenario_config.py +14 -20
- epyt_flow/simulation/scenario_simulator.py +358 -114
- epyt_flow/simulation/sensor_config.py +693 -37
- epyt_flow/topology.py +149 -8
- epyt_flow/utils.py +75 -18
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/METADATA +33 -5
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/RECORD +30 -22
- epyt_flow/EPANET/compile.sh +0 -4
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/WHEEL +0 -0
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/top_level.txt +0 -0
epyt_flow/topology.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Module provides a class for representing the topology of WDN.
|
|
3
3
|
"""
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import Any
|
|
4
7
|
import numpy as np
|
|
5
8
|
import networkx as nx
|
|
6
9
|
from scipy.sparse import bsr_array
|
|
@@ -8,6 +11,39 @@ from scipy.sparse import bsr_array
|
|
|
8
11
|
from .serialization import serializable, JsonSerializable, NETWORK_TOPOLOGY_ID
|
|
9
12
|
|
|
10
13
|
|
|
14
|
+
UNITS_USCUSTOM = 0
|
|
15
|
+
UNITS_SIMETRIC = 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def unitscategoryid_to_str(unit_category_id: int) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Converts a given units category ID to the corresponding description.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
unit_category_id : `int`
|
|
25
|
+
ID of the units category.
|
|
26
|
+
|
|
27
|
+
Must be one of the following constants:
|
|
28
|
+
|
|
29
|
+
- UNITS_USCUSTOM = 0
|
|
30
|
+
- UNITS_SIMETRIC = 1
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
`str`
|
|
35
|
+
Units category description.
|
|
36
|
+
"""
|
|
37
|
+
if unit_category_id is None:
|
|
38
|
+
return ""
|
|
39
|
+
elif unit_category_id == UNITS_USCUSTOM:
|
|
40
|
+
return "US CUSTOMARY"
|
|
41
|
+
elif unit_category_id == UNITS_SIMETRIC:
|
|
42
|
+
return "SI METRIC"
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError(f"Unknown units category ID '{unit_category_id}'")
|
|
45
|
+
|
|
46
|
+
|
|
11
47
|
@serializable(NETWORK_TOPOLOGY_ID, ".epytflow_topology")
|
|
12
48
|
class NetworkTopology(nx.Graph, JsonSerializable):
|
|
13
49
|
"""
|
|
@@ -22,13 +58,28 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
22
58
|
links : `list[tuple[tuple[str, str], dict]]`
|
|
23
59
|
List of all links/pipes -- i.e. link ID, ID of connecting nodes, and link information
|
|
24
60
|
such as pipe diameter, length, etc.
|
|
61
|
+
units : `int`
|
|
62
|
+
Measurement units category -- i.e. US Customary or SI Metric.
|
|
63
|
+
|
|
64
|
+
Must be one of the following constants:
|
|
65
|
+
|
|
66
|
+
- UNITS_USCUSTOM = 0 (US Customary)
|
|
67
|
+
- UNITS_SIMETRIC = 1 (SI Metric)
|
|
25
68
|
"""
|
|
26
69
|
def __init__(self, f_inp: str, nodes: list[tuple[str, dict]],
|
|
27
|
-
links: list[tuple[str, tuple[str, str], dict]],
|
|
70
|
+
links: list[tuple[str, tuple[str, str], dict]],
|
|
71
|
+
units: int = None,
|
|
72
|
+
**kwds):
|
|
28
73
|
super().__init__(name=f_inp, **kwds)
|
|
29
74
|
|
|
30
75
|
self.__nodes = nodes
|
|
31
76
|
self.__links = links
|
|
77
|
+
self.__units = units
|
|
78
|
+
|
|
79
|
+
if units is None:
|
|
80
|
+
warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
|
|
81
|
+
" -- support of such old files will be removed in the next release!",
|
|
82
|
+
DeprecationWarning)
|
|
32
83
|
|
|
33
84
|
for node_id, node_info in nodes:
|
|
34
85
|
node_elevation = node_info["elevation"]
|
|
@@ -42,6 +93,76 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
42
93
|
info={"id": link_id, "nodes": link, "diameter": link_diameter,
|
|
43
94
|
"length": link_length})
|
|
44
95
|
|
|
96
|
+
def convert_units(self, units: int) -> Any:
|
|
97
|
+
"""
|
|
98
|
+
Converts this instance to a :class:`epyt_flow.topology.NetworkTopology` instance
|
|
99
|
+
where everything is measured in given measurement units category
|
|
100
|
+
(US Customary or SI Metric).
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
units : `int`
|
|
105
|
+
Measurement units category.
|
|
106
|
+
|
|
107
|
+
Must be one of the following constants:
|
|
108
|
+
|
|
109
|
+
- UNITS_USCUSTOM = 0 (US Customary)
|
|
110
|
+
- UNITS_SIMETRIC = 1 (SI Metric)
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
:class:`epyt_flow.topology.NetworkTopology`
|
|
115
|
+
Network topology with the new measurements units.
|
|
116
|
+
"""
|
|
117
|
+
if self.__units is None:
|
|
118
|
+
raise ValueError("This instance does not contain any units!")
|
|
119
|
+
|
|
120
|
+
if not isinstance(units, int):
|
|
121
|
+
raise TypeError(f"'units' must be an instance of 'int' but not of '{type(units)}'")
|
|
122
|
+
if units not in [UNITS_SIMETRIC, UNITS_USCUSTOM]:
|
|
123
|
+
raise ValueError(f"Invalid units '{units}'")
|
|
124
|
+
|
|
125
|
+
if units == self.__units:
|
|
126
|
+
warnings.warn("Units already set in this NetworkTopology instance -- nothing to do!")
|
|
127
|
+
return deepcopy(self)
|
|
128
|
+
|
|
129
|
+
# Get all data and convert units
|
|
130
|
+
inch_to_millimeter = 25.4
|
|
131
|
+
feet_to_meter = 0.3048
|
|
132
|
+
|
|
133
|
+
nodes = []
|
|
134
|
+
for node_id in self.get_all_nodes():
|
|
135
|
+
node_info = self.get_node_info(node_id)
|
|
136
|
+
if units == UNITS_USCUSTOM:
|
|
137
|
+
conv_factor = 1. / feet_to_meter
|
|
138
|
+
else:
|
|
139
|
+
conv_factor = feet_to_meter
|
|
140
|
+
node_info["elevation"] *= conv_factor
|
|
141
|
+
if "diameter" in node_info:
|
|
142
|
+
node_info["diameter"] *= conv_factor
|
|
143
|
+
|
|
144
|
+
nodes.append((node_id, node_info))
|
|
145
|
+
|
|
146
|
+
links = []
|
|
147
|
+
for link_id, link_nodes in self.get_all_links():
|
|
148
|
+
link_info = self.get_link_info(link_id)
|
|
149
|
+
|
|
150
|
+
if units == UNITS_USCUSTOM:
|
|
151
|
+
conv_factor = 1. / feet_to_meter
|
|
152
|
+
else:
|
|
153
|
+
conv_factor = feet_to_meter
|
|
154
|
+
link_info["length"] *= conv_factor
|
|
155
|
+
|
|
156
|
+
if units == UNITS_USCUSTOM:
|
|
157
|
+
conv_factor = 1. / inch_to_millimeter
|
|
158
|
+
else:
|
|
159
|
+
conv_factor = inch_to_millimeter
|
|
160
|
+
link_info["diameter"] *= conv_factor
|
|
161
|
+
|
|
162
|
+
links.append((link_id, link_nodes, link_info))
|
|
163
|
+
|
|
164
|
+
return NetworkTopology(f_inp=self.name, nodes=nodes, links=links, units=units)
|
|
165
|
+
|
|
45
166
|
def get_all_nodes(self) -> list[str]:
|
|
46
167
|
"""
|
|
47
168
|
Gets a list of all nodes.
|
|
@@ -64,7 +185,7 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
64
185
|
"""
|
|
65
186
|
return [(link_id, end_points) for link_id, end_points, _ in self.__links]
|
|
66
187
|
|
|
67
|
-
def get_node_info(self, node_id) -> dict:
|
|
188
|
+
def get_node_info(self, node_id: str) -> dict:
|
|
68
189
|
"""
|
|
69
190
|
Gets all information (e.g. elevation, type, etc.) associated with a given node.
|
|
70
191
|
|
|
@@ -84,7 +205,7 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
84
205
|
|
|
85
206
|
raise ValueError(f"Unknown node '{node_id}'")
|
|
86
207
|
|
|
87
|
-
def get_link_info(self, link_id) -> dict:
|
|
208
|
+
def get_link_info(self, link_id: str) -> dict:
|
|
88
209
|
"""
|
|
89
210
|
Gets all information (e.g. diameter, length, etc.) associated with a given link/pipe.
|
|
90
211
|
|
|
@@ -104,23 +225,43 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
104
225
|
|
|
105
226
|
raise ValueError(f"Unknown link '{link_id}'")
|
|
106
227
|
|
|
228
|
+
@property
|
|
229
|
+
def units(self) -> int:
|
|
230
|
+
"""
|
|
231
|
+
Gets the used measurement units category.
|
|
232
|
+
|
|
233
|
+
Will be one of the following constants:
|
|
234
|
+
|
|
235
|
+
- UNITS_USCUSTOM = 0 (US Customary)
|
|
236
|
+
- UNITS_SIMETRIC = 1 (SI Metric)
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
`int`
|
|
241
|
+
Measurement units category.
|
|
242
|
+
"""
|
|
243
|
+
return self.__units
|
|
244
|
+
|
|
107
245
|
def __eq__(self, other) -> bool:
|
|
108
246
|
if not isinstance(other, NetworkTopology):
|
|
109
247
|
raise TypeError("Can not compare 'NetworkTopology' instance to " +
|
|
110
248
|
f"'{type(other)}' instance")
|
|
111
249
|
|
|
112
250
|
return super().__eq__(other) and \
|
|
113
|
-
self.get_all_nodes() == other.get_all_nodes()
|
|
114
|
-
all(link_a[0] == link_b[0] and all(link_a[1] == link_b[1])
|
|
115
|
-
|
|
251
|
+
self.get_all_nodes() == other.get_all_nodes() \
|
|
252
|
+
and all(link_a[0] == link_b[0] and all(link_a[1] == link_b[1])
|
|
253
|
+
for link_a, link_b in zip(self.get_all_links(), other.get_all_links())) \
|
|
254
|
+
and self.__units == other.units
|
|
116
255
|
|
|
117
256
|
def __str__(self) -> str:
|
|
118
|
-
return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links}"
|
|
257
|
+
return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links} " +\
|
|
258
|
+
f"units: {unitscategoryid_to_str(self.__units)}"
|
|
119
259
|
|
|
120
260
|
def get_attributes(self) -> dict:
|
|
121
261
|
return super().get_attributes() | {"f_inp": self.name,
|
|
122
262
|
"nodes": self.__nodes,
|
|
123
|
-
"links": self.__links
|
|
263
|
+
"links": self.__links,
|
|
264
|
+
"units": self.__units}
|
|
124
265
|
|
|
125
266
|
def get_adj_matrix(self) -> bsr_array:
|
|
126
267
|
"""
|
epyt_flow/utils.py
CHANGED
|
@@ -9,6 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
import requests
|
|
10
10
|
from tqdm import tqdm
|
|
11
11
|
import numpy as np
|
|
12
|
+
import matplotlib
|
|
12
13
|
import matplotlib.pyplot as plt
|
|
13
14
|
|
|
14
15
|
|
|
@@ -67,7 +68,8 @@ def volume_to_level(tank_volume: float, tank_diameter: float) -> float:
|
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_label: str = None,
|
|
70
|
-
y_axis_label: str = None, show: bool = True
|
|
71
|
+
y_axis_label: str = None, show: bool = True,
|
|
72
|
+
ax: matplotlib.axes.Axes = None) -> matplotlib.axes.Axes:
|
|
71
73
|
"""
|
|
72
74
|
Plots a single or multiple time series.
|
|
73
75
|
|
|
@@ -91,7 +93,18 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
91
93
|
show : `bool`, optional
|
|
92
94
|
If True, the plot/figure is shown in a window.
|
|
93
95
|
|
|
96
|
+
Only considered when 'ax' is None.
|
|
97
|
+
|
|
94
98
|
The default is True.
|
|
99
|
+
ax : `matplotlib.axes.Axes`, optional
|
|
100
|
+
If not None, 'ax' is used for plotting.
|
|
101
|
+
|
|
102
|
+
The default is None.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
`matplotlib.axes.Axes`
|
|
107
|
+
Plot.
|
|
95
108
|
"""
|
|
96
109
|
if not isinstance(data, np.ndarray):
|
|
97
110
|
raise TypeError(f"'data' must be an instance of 'numpy.ndarray' but not of '{type(data)}'")
|
|
@@ -111,28 +124,37 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
111
124
|
f"but not of '{type(y_axis_label)}'")
|
|
112
125
|
if not isinstance(show, bool):
|
|
113
126
|
raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
|
|
127
|
+
if ax is not None:
|
|
128
|
+
if not isinstance(ax, matplotlib.axes.Axes):
|
|
129
|
+
raise TypeError("ax' must be an instance of 'matplotlib.axes.Axes'" +
|
|
130
|
+
f"but not of '{type(ax)}'")
|
|
114
131
|
|
|
115
|
-
|
|
132
|
+
fig = None
|
|
133
|
+
if ax is None:
|
|
134
|
+
fig, ax = plt.subplots()
|
|
116
135
|
|
|
117
136
|
labels = labels if labels is not None else [None] * data.shape[0]
|
|
118
137
|
|
|
119
138
|
for i in range(data.shape[0]):
|
|
120
|
-
|
|
139
|
+
ax.plot(data[i, :], ".-", label=labels[i])
|
|
121
140
|
|
|
122
141
|
if not any(label is None for label in labels):
|
|
123
|
-
|
|
142
|
+
ax.legend()
|
|
124
143
|
|
|
125
144
|
if x_axis_label is not None:
|
|
126
|
-
|
|
145
|
+
ax.set_xlabel(x_axis_label)
|
|
127
146
|
if y_axis_label is not None:
|
|
128
|
-
|
|
147
|
+
ax.set_ylabel(y_axis_label)
|
|
129
148
|
|
|
130
|
-
if show is True:
|
|
149
|
+
if show is True and fig is not None:
|
|
131
150
|
plt.show()
|
|
132
151
|
|
|
152
|
+
return ax
|
|
153
|
+
|
|
133
154
|
|
|
134
155
|
def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
135
|
-
confidence_interval: np.ndarray = None, show: bool = True
|
|
156
|
+
confidence_interval: np.ndarray = None, show: bool = True,
|
|
157
|
+
ax: matplotlib.axes.Axes = None) -> matplotlib.axes.Axes:
|
|
136
158
|
"""
|
|
137
159
|
Plots the prediction (e.g. forecast) of *single* time series together with the
|
|
138
160
|
ground truth time series. In addition, confidence intervals can be plotted as well.
|
|
@@ -151,7 +173,18 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
151
173
|
show : `bool`, optional
|
|
152
174
|
If True, the plot/figure is shown in a window.
|
|
153
175
|
|
|
176
|
+
Only considered when 'ax' is None.
|
|
177
|
+
|
|
154
178
|
The default is True.
|
|
179
|
+
ax : `matplotlib.axes.Axes`, optional
|
|
180
|
+
If not None, 'axes' is used for plotting.
|
|
181
|
+
|
|
182
|
+
The default is None.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
`matplotlib.axes.Axes`
|
|
187
|
+
Plot.
|
|
155
188
|
"""
|
|
156
189
|
if not isinstance(y_pred, np.ndarray):
|
|
157
190
|
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
@@ -167,21 +200,29 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
167
200
|
raise ValueError("'y' must be a 1d array")
|
|
168
201
|
if not isinstance(show, bool):
|
|
169
202
|
raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
|
|
203
|
+
if ax is not None:
|
|
204
|
+
if not isinstance(ax, matplotlib.axes.Axes):
|
|
205
|
+
raise TypeError("ax' must be an instance of 'matplotlib.axes.Axes'" +
|
|
206
|
+
f"but not of '{type(ax)}'")
|
|
170
207
|
|
|
171
|
-
|
|
208
|
+
fig = None
|
|
209
|
+
if ax is None:
|
|
210
|
+
fig, ax = plt.subplots()
|
|
172
211
|
|
|
173
212
|
if confidence_interval is not None:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if show is True:
|
|
213
|
+
ax.fill_between(range(len(y_pred)),
|
|
214
|
+
y_pred - confidence_interval[0],
|
|
215
|
+
y_pred + confidence_interval[1],
|
|
216
|
+
alpha=0.5)
|
|
217
|
+
ax.plot(y_pred, ".-", label="Prediction")
|
|
218
|
+
ax.plot(y, ".-", label="Ground truth")
|
|
219
|
+
ax.legend()
|
|
220
|
+
|
|
221
|
+
if show is True and fig is not None:
|
|
183
222
|
plt.show()
|
|
184
223
|
|
|
224
|
+
return ax
|
|
225
|
+
|
|
185
226
|
|
|
186
227
|
def download_if_necessary(download_path: str, url: str, verbose: bool = True) -> None:
|
|
187
228
|
"""
|
|
@@ -234,6 +275,22 @@ def create_path_if_not_exist(path_in: str) -> None:
|
|
|
234
275
|
Path(path_in).mkdir(parents=True, exist_ok=True)
|
|
235
276
|
|
|
236
277
|
|
|
278
|
+
def pack_zip_archive(f_in: list[str], f_out: str) -> None:
|
|
279
|
+
"""
|
|
280
|
+
Compresses a given list of files into a .zip archive.
|
|
281
|
+
|
|
282
|
+
Parameters
|
|
283
|
+
----------
|
|
284
|
+
f_in : `list[str]`
|
|
285
|
+
List of files to be compressed into the .zip archive.
|
|
286
|
+
f_out : `str`
|
|
287
|
+
Path to the final .zip file.
|
|
288
|
+
"""
|
|
289
|
+
with zipfile.ZipFile(f_out, "w") as f_zip_out:
|
|
290
|
+
for f_cur_in in f_in:
|
|
291
|
+
f_zip_out.write(f_cur_in, compress_type=zipfile.ZIP_DEFLATED)
|
|
292
|
+
|
|
293
|
+
|
|
237
294
|
def unpack_zip_archive(f_in: str, folder_out: str) -> None:
|
|
238
295
|
"""
|
|
239
296
|
Unpacks a .zip archive.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: epyt-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: EPyT-Flow -- EPANET Python Toolkit - Flow
|
|
5
5
|
Author-email: André Artelt <aartelt@techfak.uni-bielefeld.de>, "Marios S. Kyriakou" <kiriakou.marios@ucy.ac.cy>, "Stelios G. Vrachimis" <vrachimis.stelios@ucy.ac.cy>
|
|
6
6
|
License: MIT License
|
|
@@ -32,8 +32,9 @@ Requires-Dist: falcon >=3.1.3
|
|
|
32
32
|
Requires-Dist: multiprocess >=0.70.16
|
|
33
33
|
Requires-Dist: psutil
|
|
34
34
|
|
|
35
|
-
[](https://opensource.org/licenses/MIT)
|
|
36
35
|
[](https://pypi.org/project/epyt-flow/)
|
|
36
|
+
[](https://opensource.org/licenses/MIT)
|
|
37
|
+

|
|
37
38
|
[](https://github.com/WaterFutures/EPyT-Flow/actions/workflows/build_tests.yml)
|
|
38
39
|
[](https://epyt-flow.readthedocs.io/en/stable/?badge=stable)
|
|
39
40
|
[](https://pepy.tech/project/epyt-flow)
|
|
@@ -41,6 +42,8 @@ Requires-Dist: psutil
|
|
|
41
42
|
|
|
42
43
|
# EPyT-Flow -- EPANET Python Toolkit - Flow
|
|
43
44
|
|
|
45
|
+
<img src="https://github.com/WaterFutures/EPyT-Flow/blob/main/docs/_static/net1_plot.png?raw=true" align="right" height="230px"/>
|
|
46
|
+
|
|
44
47
|
EPyT-Flow is a Python package building on top of [EPyT](https://github.com/OpenWaterAnalytics/EPyT)
|
|
45
48
|
for providing easy access to water distribution network simulations.
|
|
46
49
|
It aims to provide a high-level interface for the easy generation of hydraulic and water quality scenario data.
|
|
@@ -50,15 +53,40 @@ and [EPANET-MSX](https://github.com/USEPA/EPANETMSX/).
|
|
|
50
53
|
EPyT-Flow provides easy access to popular benchmark data sets for event detection and localization.
|
|
51
54
|
Furthermore, it also provides an environment for developing and testing control algorithms.
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
|
|
57
|
+
## Unique Features
|
|
58
|
+
|
|
59
|
+
Unique features of EPyT-Flow that make it superior to other (Python) toolboxes are the following:
|
|
60
|
+
|
|
61
|
+
- High-performance hydraulic and (advanced) water quality simulation
|
|
62
|
+
- High- and low-level interface
|
|
63
|
+
- Object-orientated design that is easy to extend and customize
|
|
64
|
+
- Sensor configurations
|
|
65
|
+
- Wide variety of pre-defined events (e.g. leakages, sensor faults, actuator events, cyber-attacks, etc.)
|
|
66
|
+
- Wide variety of pre-defined types of uncertainties (e.g. model uncertainties)
|
|
67
|
+
- Step-wise simulation and environment for training and evaluating control strategies
|
|
68
|
+
- Serialization module for easy exchange of data and (scenario) configurations
|
|
69
|
+
- REST API to make EPyT-Flow accessible in other applications
|
|
70
|
+
- Access to many WDNs and popular benchmarks (incl. their evaluation)
|
|
71
|
+
|
|
54
72
|
|
|
55
73
|
## Installation
|
|
56
74
|
|
|
57
75
|
EPyT-Flow supports Python 3.9 - 3.12
|
|
58
76
|
|
|
59
77
|
Note that [EPANET and EPANET-MSX sources](epyt_flow/EPANET/) are compiled and overwrite the binaries
|
|
60
|
-
shipped by EPyT IF EPyT-Flow is installed on a
|
|
61
|
-
a better performance of the simulations but also avoid any
|
|
78
|
+
shipped by EPyT **IF** EPyT-Flow is installed on a Unix system and the *gcc* compiler is available.
|
|
79
|
+
By this, we not only aim to achieve a better performance of the simulations but also avoid any
|
|
80
|
+
compatibility issues of pre-compiled binaries.
|
|
81
|
+
|
|
82
|
+
#### Prerequisites for macOS users
|
|
83
|
+
The "true" *gcc* compiler (version 12) is needed which is not the
|
|
84
|
+
*clang* compiler that is shipped with Xcode and is linked to gcc!
|
|
85
|
+
|
|
86
|
+
The correct version of the "true" *gcc* can be installed via [brew](https://brew.sh/):
|
|
87
|
+
```
|
|
88
|
+
brew install gcc@12
|
|
89
|
+
```
|
|
62
90
|
|
|
63
91
|
### PyPI
|
|
64
92
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
epyt_flow/VERSION,sha256=
|
|
2
|
-
epyt_flow/__init__.py,sha256=
|
|
1
|
+
epyt_flow/VERSION,sha256=2RXMldbKj0euKXcT7UbU5cXZnd0p_Dxh4mO98wXytbA,6
|
|
2
|
+
epyt_flow/__init__.py,sha256=KNDiPWiHdB9a5ZF1ipjA1uoq61TwU2ThjaStpvSLBtY,1742
|
|
3
3
|
epyt_flow/metrics.py,sha256=kvt42pzZrUR9PSlCyK4uq5kj6UlYHkt7OcCjLnI1RQE,12883
|
|
4
4
|
epyt_flow/serialization.py,sha256=nBcwc3aMUbHF0zW8Nvpc49kKeLPh75blc3gzjKDR1ok,12893
|
|
5
|
-
epyt_flow/topology.py,sha256=
|
|
6
|
-
epyt_flow/utils.py,sha256
|
|
7
|
-
epyt_flow/EPANET/
|
|
5
|
+
epyt_flow/topology.py,sha256=LTWqKwXVRfg3zYN16x5_ZH-wUj8U5hB2spLWzlEnsqM,14221
|
|
6
|
+
epyt_flow/utils.py,sha256=-1DFDpST_J86AB6pd2ufe_QQ06_tvQqB-5wD8DXacaU,11507
|
|
7
|
+
epyt_flow/EPANET/compile_linux.sh,sha256=wcrDyiB8NkivmaC-X9FI2WxhY3IJqDLiyIbVTv2XEPY,489
|
|
8
|
+
epyt_flow/EPANET/compile_macos.sh,sha256=1K33-bPdgr01EIf87YUvmOFHXyOkBWI6mKXQ8x1Hzmo,504
|
|
8
9
|
epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS,sha256=yie5yAsEEPY0984PmkSRUdqEU9rVvRSGGWmjxdwCYMU,925
|
|
9
10
|
epyt_flow/EPANET/EPANET/SRC_engines/LICENSE,sha256=8SIIcPPO-ga2HotvptcK3uRccZOEGCeUOIU0Asiq7CU,1070
|
|
10
11
|
epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt,sha256=7LWHGbghkYJb18wkIskUzYswRq0ZTMu_m6nV0IfvCOs,1005
|
|
@@ -79,14 +80,14 @@ epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h,sha256=heHTNgQNNDTs_Jx0YBts7_B7dPg8VUF
|
|
|
79
80
|
epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h,sha256=L9y0VKHk5Fg1JZxID9uBzcvLZWOb1xuQP-MkmHH_LD4,3429
|
|
80
81
|
epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h,sha256=h5UMaf6pH_0asRJOmhWUGAZhyA180ui2Cz8_y5h1FKw,1054
|
|
81
82
|
epyt_flow/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
|
-
epyt_flow/data/networks.py,sha256=
|
|
83
|
+
epyt_flow/data/networks.py,sha256=Ct-3tNEJPq6EooGWHr_Hz4vkkNYjv-QgiSwNKOTZ-oM,38526
|
|
83
84
|
epyt_flow/data/benchmarks/__init__.py,sha256=nJ6hqPaPNp8YMizniev3fwOWpzvvNUBMoRF16wACUkE,754
|
|
84
85
|
epyt_flow/data/benchmarks/batadal.py,sha256=sa_OZwO5XIbJONgGMwgok-KCGyHq07WpIQagVq-a-gw,11175
|
|
85
86
|
epyt_flow/data/benchmarks/batadal_data.py,sha256=oIzcysGivMPAgrfzrk5l8i-j6Ii96DPcFa6sL4TSaw8,880
|
|
86
87
|
epyt_flow/data/benchmarks/battledim.py,sha256=K0j9tbfxuXJmqBQY_e5XBJZGDYavaOZ7efkCT96Fb8A,20167
|
|
87
88
|
epyt_flow/data/benchmarks/battledim_data.py,sha256=0vHm-2eAiLv6U-n5dqUUWS1o_szFRy9mVJ3eqDRp4PE,3373
|
|
88
89
|
epyt_flow/data/benchmarks/gecco_water_quality.py,sha256=1buZRJiNf4jsqWYg4Ud90GhqaiLVo4yij3RAZJkzsqE,10985
|
|
89
|
-
epyt_flow/data/benchmarks/leakdb.py,sha256=
|
|
90
|
+
epyt_flow/data/benchmarks/leakdb.py,sha256=jMMSxJ6XrjOo5b91sGwplT5mTcv3k9Xr-TnWz3BhMbw,24952
|
|
90
91
|
epyt_flow/data/benchmarks/leakdb_data.py,sha256=FNssgMkC1wqWVlaOrrihr4Od9trEZY7KeK5KuBeRMvM,507058
|
|
91
92
|
epyt_flow/data/benchmarks/water_usage.py,sha256=FLqjff3pha33oEU9ZM3UGPXn9eJJumsJH8Gdj7YFX3A,4778
|
|
92
93
|
epyt_flow/gym/__init__.py,sha256=KNTDtPTEtHwZ4ehHfj9qGw81Z9acFqPIgMzYUzH5_uM,115
|
|
@@ -96,36 +97,43 @@ epyt_flow/models/__init__.py,sha256=be5s08y1Tg66SuNYKGz5GnNMHThnQJo8SWJdT9493Kc,
|
|
|
96
97
|
epyt_flow/models/event_detector.py,sha256=idR7byBgloo07XEJEyMIwi49VW4wxJErLQtI-tJXWPs,789
|
|
97
98
|
epyt_flow/models/sensor_interpolation_detector.py,sha256=5MBK9WlliGPonrNApf0j9lp-NjwF0iTwPDXx4yv7Fa0,3624
|
|
98
99
|
epyt_flow/rest_api/__init__.py,sha256=4HilmXhdh6H56UHJBB2WUSULlEBUDnI1FPTP11ft3HE,126
|
|
99
|
-
epyt_flow/rest_api/base_handler.py,sha256=
|
|
100
|
+
epyt_flow/rest_api/base_handler.py,sha256=I5ZcSiCJOH9sY5Ai_CdvSZ8PMVblyxCd82beE_2Sjlo,2262
|
|
100
101
|
epyt_flow/rest_api/res_manager.py,sha256=j6-3FUBZNLKM9bCsIDZzSytfDYJbDLRwjn1mIPstTqI,2342
|
|
101
|
-
epyt_flow/rest_api/
|
|
102
|
-
epyt_flow/rest_api/
|
|
103
|
-
epyt_flow/rest_api/
|
|
102
|
+
epyt_flow/rest_api/server.py,sha256=kZlprvRw7op39s7KhtEt1TgAsqs94KTQEdzIW9NMMJc,7778
|
|
103
|
+
epyt_flow/rest_api/scada_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
|
+
epyt_flow/rest_api/scada_data/data_handlers.py,sha256=VpOQq_jb2d7CuZbPQ1lKfb5dd5UAkBp7H0mEtlDJMzo,10938
|
|
105
|
+
epyt_flow/rest_api/scada_data/export_handlers.py,sha256=m5gM1u7z-KFZ5SCSS0f2Fs6PpAT8FX3FvCS2mp7DmTA,4450
|
|
106
|
+
epyt_flow/rest_api/scada_data/handlers.py,sha256=tFVY11C3tolTkNVbXXBb0_KnHv38UE4fOUtnqkvec8Y,6893
|
|
107
|
+
epyt_flow/rest_api/scenario/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
108
|
+
epyt_flow/rest_api/scenario/event_handlers.py,sha256=oIqbXWQVqJp0MzE9V4UhGF4fXPF03oWbKEyLhC6g_Ko,3842
|
|
109
|
+
epyt_flow/rest_api/scenario/handlers.py,sha256=bDqMa-jM4hOMVqs0XvQJtHbQDZx9FP40GiEGwW38fGk,12353
|
|
110
|
+
epyt_flow/rest_api/scenario/simulation_handlers.py,sha256=oY1Ch6ZQgYT_5WeE1I7tvGqpDKlT664GHLdmcVKP7ek,5905
|
|
111
|
+
epyt_flow/rest_api/scenario/uncertainty_handlers.py,sha256=uuu6AP11ZZUp2P3Dnukjg5ZTjyYKljlubg1xLN1GnXY,4048
|
|
104
112
|
epyt_flow/simulation/__init__.py,sha256=VGGJqJRUoXZjKJ0-m6KPp3JQqD_1TFW0pofLgkwZJ8M,164
|
|
105
113
|
epyt_flow/simulation/parallel_simulation.py,sha256=mMevycgtnMjV2FDq50WS4HjAdcOlI72Aj6FBX4HZDtc,6508
|
|
106
|
-
epyt_flow/simulation/scenario_config.py,sha256=
|
|
107
|
-
epyt_flow/simulation/scenario_simulator.py,sha256=
|
|
114
|
+
epyt_flow/simulation/scenario_config.py,sha256=6xZD3Gwje-isiSaoKCXLkX0R81SVDH8LojsXdDlH8to,26540
|
|
115
|
+
epyt_flow/simulation/scenario_simulator.py,sha256=Yf2oo3fMfvxrrW3nX8d6y2plrNcGZ-DBK-5mpLtplms,92070
|
|
108
116
|
epyt_flow/simulation/scenario_visualizer.py,sha256=2gr1o731VLlhA8wQKgrLT94M43FsBNKjG_eucZPHy9A,2186
|
|
109
|
-
epyt_flow/simulation/sensor_config.py,sha256=
|
|
117
|
+
epyt_flow/simulation/sensor_config.py,sha256=9pUzvRm4HSF8zZ-Hu4pdpxzTjpNSdNmDvcPhRaRFNAI,80175
|
|
110
118
|
epyt_flow/simulation/events/__init__.py,sha256=tIdqzs7_Cus4X2kbZG4Jl2zs-zsk_4rnajFOCvL0zlI,185
|
|
111
119
|
epyt_flow/simulation/events/actuator_events.py,sha256=2_MPYbYO9As6fMkm5Oy9pjSB9kCvFuKpGu8ykYDAydg,7903
|
|
112
120
|
epyt_flow/simulation/events/event.py,sha256=kARPV20XCAl6zxnJwI9U7ICtZUPACO_rgAmtHm1mGCs,2603
|
|
113
|
-
epyt_flow/simulation/events/leakages.py,sha256=
|
|
121
|
+
epyt_flow/simulation/events/leakages.py,sha256=lDZNKOtVtQsKYhAuJ5Mt7tG6IfHX1436eSkjYTkQiZk,15267
|
|
114
122
|
epyt_flow/simulation/events/sensor_faults.py,sha256=XX6k-GJh9RWZ4x54eGj9um-Ir9Eq41tY_9pRSCeYeqc,8447
|
|
115
123
|
epyt_flow/simulation/events/sensor_reading_attack.py,sha256=bo5VavArN0wD5AHbIXJC9NFGZ7KR1uyWE6tBtwj0k9I,7538
|
|
116
124
|
epyt_flow/simulation/events/sensor_reading_event.py,sha256=rQ-CmdpSUyZzDFYwNUGH2jGoj0oyU-aAb-7E8Oshhqw,6785
|
|
117
125
|
epyt_flow/simulation/events/system_event.py,sha256=0KI2iaAaOyC9Y-FIfFVazeKT_4ORQRp26gWyMBUu_3c,2396
|
|
118
126
|
epyt_flow/simulation/scada/__init__.py,sha256=ZFAxJVqwEVsgiyFilFetnb13gPhZg1JEOPWYvKIJT4c,90
|
|
119
127
|
epyt_flow/simulation/scada/advanced_control.py,sha256=Enox02ggt36HdFLX7ZNxgxuqsTEeu9AACHrzU8CXGrg,4489
|
|
120
|
-
epyt_flow/simulation/scada/scada_data.py,sha256=
|
|
121
|
-
epyt_flow/simulation/scada/scada_data_export.py,sha256=
|
|
128
|
+
epyt_flow/simulation/scada/scada_data.py,sha256=NfGDGrQ7HIXTcShUaGeCALTvGmM5LAJW0Bgmk0H4UQg,104867
|
|
129
|
+
epyt_flow/simulation/scada/scada_data_export.py,sha256=YlA-6R6XpQ28POyKsw1eEe23gMAuDFvwBuDUN-ciu2Y,11213
|
|
122
130
|
epyt_flow/uncertainty/__init__.py,sha256=ZRjuJL9rDpWVSdPwObPxFpEmMTcgAl3VmPOsS6cIyGg,89
|
|
123
131
|
epyt_flow/uncertainty/model_uncertainty.py,sha256=DAgyfvdTQvOVMyduuPIpPB_8e3GIMNpehu6kh0jFg0s,13592
|
|
124
132
|
epyt_flow/uncertainty/sensor_noise.py,sha256=zJVULxnxVPSSqc6UW0iwZ9O-HGf9dn4CwScPqf4yCY0,2324
|
|
125
133
|
epyt_flow/uncertainty/uncertainties.py,sha256=X-o7GZUC0HELtzpoXIAJaAeYOw35N05TuRoSmStcCpI,17669
|
|
126
134
|
epyt_flow/uncertainty/utils.py,sha256=gq66c9-QMOxOqI6wgWLyFxjVV0fbG0_8Yzd6mQjNYNo,5315
|
|
127
|
-
epyt_flow-0.
|
|
128
|
-
epyt_flow-0.
|
|
129
|
-
epyt_flow-0.
|
|
130
|
-
epyt_flow-0.
|
|
131
|
-
epyt_flow-0.
|
|
135
|
+
epyt_flow-0.3.0.dist-info/LICENSE,sha256=-4hYIY2BLmCkdOv2_PehEwlnMKTCes8_oyIUXjKtkug,1076
|
|
136
|
+
epyt_flow-0.3.0.dist-info/METADATA,sha256=tnq12BhftSSxemSI93ONtLRs4K4eT8R_I71NENxUuX4,7053
|
|
137
|
+
epyt_flow-0.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
138
|
+
epyt_flow-0.3.0.dist-info/top_level.txt,sha256=Wh_kd7TRL8ownCw3Y3dxx-9C0iTSk6wNauv_NX9JcrY,10
|
|
139
|
+
epyt_flow-0.3.0.dist-info/RECORD,,
|
epyt_flow/EPANET/compile.sh
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
mkdir -p "../customlibs/"
|
|
3
|
-
gcc -w -shared -Wl,-soname,libepanet2_2.so -fPIC -o "../customlibs/libepanet2_2.so" EPANET/SRC_engines/*.c -IEPANET/SRC_engines/include -lc -lm -pthread
|
|
4
|
-
gcc -w -fPIC -shared -Wl,-soname,libepanetmsx2_2_0.so -o "../customlibs/libepanetmsx2_2_0.so" -fopenmp -Depanetmsx_EXPORTS -IEPANET-MSX/Src/include -IEPANET/SRC_engines/include EPANET-MSX/Src/*.c -Wl,-rpath=. "../customlibs/libepanet2_2.so" -lm -lgomp -lpthread
|
|
File without changes
|
|
File without changes
|
|
File without changes
|