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.
@@ -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
@@ -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 (m) of the leak.
201
+ Diameter (*foot* or *meter*) of the leak.
197
202
 
198
203
  Returns
199
204
  -------
200
205
  `float`
201
- Leak area in mm^2.
206
+ Leak area in *foot^2* or *meter^2*.
202
207
  """
203
- return (np.pi * (diameter / 2) ** 2) * 10000.
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) where g = 9.8, discharge_coef = .75
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 (mm^2) as computed in
218
- :func:`epyt_flow.simulation.events.leakages.Leakage.compute_leak_area`.
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], None]) -> None:
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], None] = callback_save_to_file()
54
- ) -> None:
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: `epyt_flow.simulation.scenario_config.ScenarioConfig`
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)