epyt-flow 0.2.0__py3-none-any.whl → 0.4.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_macos.sh +4 -0
- epyt_flow/VERSION +1 -1
- epyt_flow/__init__.py +6 -2
- 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/handlers.py +42 -0
- epyt_flow/rest_api/server.py +3 -1
- epyt_flow/simulation/events/leakages.py +28 -18
- epyt_flow/simulation/parallel_simulation.py +7 -7
- epyt_flow/simulation/scada/scada_data.py +543 -12
- epyt_flow/simulation/scada/scada_data_export.py +38 -5
- epyt_flow/simulation/scenario_config.py +7 -5
- epyt_flow/simulation/scenario_simulator.py +81 -48
- epyt_flow/simulation/sensor_config.py +342 -47
- epyt_flow/topology.py +313 -11
- epyt_flow/uncertainty/model_uncertainty.py +26 -19
- epyt_flow/utils.py +1 -1
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/METADATA +18 -6
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/RECORD +23 -22
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/WHEEL +0 -0
- {epyt_flow-0.2.0.dist-info → epyt_flow-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -11,6 +11,20 @@ class BaseHandler():
|
|
|
11
11
|
"""
|
|
12
12
|
Base class for all REST API handlers.
|
|
13
13
|
"""
|
|
14
|
+
def send_error(self, resp: falcon.Response, error_msg: str) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Sends an error message back.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
resp : `falcon.Response`
|
|
21
|
+
Response instance.
|
|
22
|
+
error_msg : `str`
|
|
23
|
+
Error message.
|
|
24
|
+
"""
|
|
25
|
+
resp.status = falcon.HTTP_BAD_REQUEST
|
|
26
|
+
resp.data = error_msg.encode()
|
|
27
|
+
|
|
14
28
|
def send_invalid_resource_id_error(self, resp: falcon.Response) -> None:
|
|
15
29
|
"""
|
|
16
30
|
Sends an error that th given resource ID (e.g. scenario ID, or SCADA data ID) is invalid.
|
|
@@ -165,3 +165,45 @@ class ScadaDataSensorFaultsHandler(ScadaDataBaseHandler):
|
|
|
165
165
|
except Exception as ex:
|
|
166
166
|
warnings.warn(str(ex))
|
|
167
167
|
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ScadaDataConvertUnitsHandler(ScadaDataBaseHandler):
|
|
171
|
+
"""
|
|
172
|
+
Class for handling POST requests concerning unit conversion of a
|
|
173
|
+
given SCADA data instance.
|
|
174
|
+
"""
|
|
175
|
+
def on_post(self, req: falcon.Request, resp: falcon.Response, data_id: str) -> None:
|
|
176
|
+
"""
|
|
177
|
+
Converts the units of a given SCADA data instance and returns a new SCADA data instance.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
req : `falcon.Request`
|
|
182
|
+
Request instance.
|
|
183
|
+
resp : `falcon.Response`
|
|
184
|
+
Response instance.
|
|
185
|
+
data_id : `str`
|
|
186
|
+
UUID of the SCADA data.
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
if self.scada_data_mgr.validate_uuid(data_id) is False:
|
|
190
|
+
self.send_invalid_resource_id_error(resp)
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
new_units = self.load_json_data_from_request(req)
|
|
194
|
+
if not isinstance(new_units, dict):
|
|
195
|
+
self.send_json_parsing_error(resp)
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
my_scada_data = self.scada_data_mgr.get(data_id)
|
|
199
|
+
try:
|
|
200
|
+
scada_data_new = my_scada_data.convert_units(**new_units)
|
|
201
|
+
except Exception as ex:
|
|
202
|
+
self.send_error(resp, str(ex))
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
new_scada_data_id = self.scada_data_mgr.create_new_item(scada_data_new)
|
|
206
|
+
self.send_json_response(resp, {"data_id": new_scada_data_id})
|
|
207
|
+
except Exception as ex:
|
|
208
|
+
warnings.warn(str(ex))
|
|
209
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
epyt_flow/rest_api/server.py
CHANGED
|
@@ -14,7 +14,7 @@ from .scenario.event_handlers import ScenarioLeakageHandler, ScenarioSensorFault
|
|
|
14
14
|
from .scenario.simulation_handlers import ScenarioSimulationHandler, \
|
|
15
15
|
ScenarioBasicQualitySimulationHandler, ScenarioAdvancedQualitySimulationHandler
|
|
16
16
|
from .scada_data.handlers import ScadaDataManager, ScadaDataSensorConfigHandler, \
|
|
17
|
-
ScadaDataRemoveHandler, ScadaDataSensorFaultsHandler
|
|
17
|
+
ScadaDataRemoveHandler, ScadaDataSensorFaultsHandler, ScadaDataConvertUnitsHandler
|
|
18
18
|
from .scada_data.data_handlers import ScadaDataPressuresHandler, ScadaDataDemandsHandler, \
|
|
19
19
|
ScadaDataFlowsHandler, ScadaDataLinksQualityHandler, ScadaDataNodesQualityHandler, \
|
|
20
20
|
ScadaDataNodeBulkSpeciesHandler, ScadaDataLinkBulkSpeciesHandler, \
|
|
@@ -114,6 +114,8 @@ class RestApiService():
|
|
|
114
114
|
ScadaDataNumpyExportHandler(scada_data_mgr=self.scada_data_mgr))
|
|
115
115
|
self.app.add_route("/scada_data/{data_id}/export",
|
|
116
116
|
ScadaDataExportHandler(scada_data_mgr=self.scada_data_mgr))
|
|
117
|
+
self.app.add_route("/scada_data/{data_id}/convert_units",
|
|
118
|
+
ScadaDataConvertUnitsHandler(scada_data_mgr=self.scada_data_mgr))
|
|
117
119
|
|
|
118
120
|
@property
|
|
119
121
|
def port(self) -> int:
|
|
@@ -5,6 +5,7 @@ from copy import deepcopy
|
|
|
5
5
|
import math
|
|
6
6
|
import numpy as np
|
|
7
7
|
import epyt
|
|
8
|
+
from epyt.epanet import ToolkitConstants
|
|
8
9
|
|
|
9
10
|
from .system_event import SystemEvent
|
|
10
11
|
from ...serialization import serializable, JsonSerializable, \
|
|
@@ -23,14 +24,14 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
23
24
|
Note that if the leak is placed at a node, then 'link_id' must be None and the
|
|
24
25
|
ID of the node must be set in 'node_id'
|
|
25
26
|
diameter : `float`, optional
|
|
26
|
-
Diameter of this leak.
|
|
27
|
+
Diameter of this leak in either *foot* or *meter* (depending on the used flow units).
|
|
27
28
|
|
|
28
29
|
Alternatively, 'area' can be used for specifying the size of this leakage --
|
|
29
30
|
in this case, 'diameter' must be set to 'None'.
|
|
30
31
|
|
|
31
32
|
The default is None.
|
|
32
33
|
area : `float`, optional
|
|
33
|
-
Area of this leak.
|
|
34
|
+
Area of this leak in either *foot^2* or *meter^2* (depending on the used flow units).
|
|
34
35
|
|
|
35
36
|
Alternatively, 'diameter' can be used for specifying the size of this leakage --
|
|
36
37
|
in this case, 'area' must be set to 'None'.
|
|
@@ -124,19 +125,21 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
124
125
|
@property
|
|
125
126
|
def diameter(self) -> float:
|
|
126
127
|
"""
|
|
127
|
-
Gets the diameter of the leak
|
|
128
|
+
Gets the diameter of the leak in either *foot* or *meter*
|
|
129
|
+
(depending on the sued flow units).
|
|
128
130
|
|
|
129
131
|
Returns
|
|
130
132
|
-------
|
|
131
133
|
`float`
|
|
132
|
-
Diameter of the leak.
|
|
134
|
+
Diameter (*foot* or *meter*) of the leak.
|
|
133
135
|
"""
|
|
134
136
|
return self.__diameter
|
|
135
137
|
|
|
136
138
|
@property
|
|
137
139
|
def area(self) -> float:
|
|
138
140
|
"""
|
|
139
|
-
Gets the area of the leak
|
|
141
|
+
Gets the area of the leak in either *foot^2* or *meter^2*
|
|
142
|
+
(depending on the sued flow units).
|
|
140
143
|
|
|
141
144
|
Returns
|
|
142
145
|
-------
|
|
@@ -179,7 +182,7 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
179
182
|
raise TypeError(f"Can not compare 'Leakage' instance with '{type(other)}' instance")
|
|
180
183
|
|
|
181
184
|
return super().__eq__(other) and self.__link_id == other.link_id \
|
|
182
|
-
and self.__diameter == other.diameter and self.__profile == other.profile \
|
|
185
|
+
and self.__diameter == other.diameter and np.all(self.__profile == other.profile) \
|
|
183
186
|
and self.__node_id == other.node_id and self.area == other.area
|
|
184
187
|
|
|
185
188
|
def __str__(self) -> str:
|
|
@@ -190,46 +193,53 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
190
193
|
"""
|
|
191
194
|
Computes the leak area given the diameter.
|
|
192
195
|
|
|
196
|
+
leak_area = pi * (diameter * .5)^2
|
|
197
|
+
|
|
193
198
|
Parameters
|
|
194
199
|
----------
|
|
195
200
|
diameter : `float`
|
|
196
|
-
Diameter (
|
|
201
|
+
Diameter (*foot* or *meter*) of the leak.
|
|
197
202
|
|
|
198
203
|
Returns
|
|
199
204
|
-------
|
|
200
205
|
`float`
|
|
201
|
-
Leak area in
|
|
206
|
+
Leak area in *foot^2* or *meter^2*.
|
|
202
207
|
"""
|
|
203
|
-
return
|
|
208
|
+
return np.pi * (diameter / 2) ** 2
|
|
204
209
|
|
|
205
|
-
def compute_leak_emitter_coefficient(self, area: float, discharge_coef: float = .75
|
|
206
|
-
g: float = 9.80665) -> float:
|
|
210
|
+
def compute_leak_emitter_coefficient(self, area: float, discharge_coef: float = .75) -> float:
|
|
207
211
|
"""
|
|
208
212
|
Computes the leak emitter coefficient.
|
|
209
213
|
|
|
210
|
-
emitter_coef = discharge_coef * area * sqrt(2*g)
|
|
214
|
+
emitter_coef = discharge_coef * area * sqrt(2*g)
|
|
215
|
+
where g is the gravitational constant, and discharge_coef = .75
|
|
211
216
|
|
|
212
217
|
leak_demand = emitter_coef * pressure^alpha where alpha = .5
|
|
213
218
|
|
|
214
219
|
Parameters
|
|
215
220
|
----------
|
|
216
221
|
area : `float`
|
|
217
|
-
Leak area (
|
|
218
|
-
:func
|
|
222
|
+
Leak area (foot^2 or meter^2) as computed in
|
|
223
|
+
:func:`~epyt_flow.simulation.events.leakages.Leakage.compute_leak_area`.
|
|
219
224
|
discharge_coef : `float`, optional
|
|
220
225
|
Discharge coefficient.
|
|
221
226
|
|
|
222
227
|
The default is set to 0.75
|
|
223
|
-
g : `float`, optional
|
|
224
|
-
Gravitational constant. Do not change this!
|
|
225
|
-
|
|
226
|
-
The default is 9.8
|
|
227
228
|
|
|
228
229
|
Returns
|
|
229
230
|
-------
|
|
230
231
|
`float`
|
|
231
232
|
Leak emitter coefficient.
|
|
232
233
|
"""
|
|
234
|
+
flow_unit = self._epanet_api.api.ENgetflowunits()
|
|
235
|
+
if flow_unit == ToolkitConstants.EN_CMH:
|
|
236
|
+
g = 127137600 # m/h^2
|
|
237
|
+
elif flow_unit == ToolkitConstants.EN_CFS:
|
|
238
|
+
g = 32.17405 # feet/s^2
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError("Leakages are only implemented for the following flow units:\n" +
|
|
241
|
+
" EN_CMH (cubic foot/sec)\n EN_CFS (cubic meter/hr)")
|
|
242
|
+
|
|
233
243
|
return discharge_coef * area * np.sqrt(2. * g)
|
|
234
244
|
|
|
235
245
|
def init(self, epanet_api: epyt.epanet) -> None:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Module provides functions for simulating several scenarios in parallel.
|
|
3
3
|
"""
|
|
4
|
-
from typing import Callable
|
|
4
|
+
from typing import Callable, Any
|
|
5
5
|
import os
|
|
6
6
|
import warnings
|
|
7
7
|
from multiprocess import Pool, cpu_count
|
|
@@ -38,9 +38,9 @@ def callback_save_to_file(folder_out: str = "") -> Callable[[ScadaData, Scenario
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def _run_scenario_simulation(scenario_config: ScenarioConfig, scenario_idx: int,
|
|
41
|
-
callback: Callable[[ScadaData, ScenarioConfig, int],
|
|
41
|
+
callback: Callable[[ScadaData, ScenarioConfig, int], Any]) -> Any:
|
|
42
42
|
with ScenarioSimulator(scenario_config=scenario_config) as sim:
|
|
43
|
-
callback(sim.run_simulation(), scenario_config, scenario_idx)
|
|
43
|
+
return callback(sim.run_simulation(), scenario_config, scenario_idx)
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class ParallelScenarioSimulation():
|
|
@@ -50,8 +50,8 @@ class ParallelScenarioSimulation():
|
|
|
50
50
|
@staticmethod
|
|
51
51
|
def run(scenarios: list[ScenarioConfig], n_jobs: int = -1,
|
|
52
52
|
max_working_memory_consumption: int = None,
|
|
53
|
-
callback: Callable[[ScadaData, ScenarioConfig, int],
|
|
54
|
-
) ->
|
|
53
|
+
callback: Callable[[ScadaData, ScenarioConfig, int], Any] = callback_save_to_file()
|
|
54
|
+
) -> Any:
|
|
55
55
|
"""
|
|
56
56
|
Simulates multiple scenarios in parallel.
|
|
57
57
|
|
|
@@ -76,7 +76,7 @@ class ParallelScenarioSimulation():
|
|
|
76
76
|
|
|
77
77
|
The callback gets the simulation results as a
|
|
78
78
|
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData` instance, the scenario
|
|
79
|
-
configuration as a :class
|
|
79
|
+
configuration as a :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig`
|
|
80
80
|
instance, and the index of the scenario in 'scenarios' as arguments.
|
|
81
81
|
|
|
82
82
|
The default is :func:`~epyt_flow.simulation.parallel_simulation.callback_save_to_file`.
|
|
@@ -144,4 +144,4 @@ class ParallelScenarioSimulation():
|
|
|
144
144
|
scenarios_task.append((scenario, scenario_idx, callback))
|
|
145
145
|
|
|
146
146
|
with Pool(processes=n_parallel_scenarios, maxtasksperchild=1) as pool:
|
|
147
|
-
pool.starmap(_run_scenario_simulation, scenarios_task)
|
|
147
|
+
return pool.starmap(_run_scenario_simulation, scenarios_task)
|