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
|
@@ -4,6 +4,7 @@ Module provides a class for specifying scenario configurations.
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
import json
|
|
7
|
+
import numpy as np
|
|
7
8
|
|
|
8
9
|
from ..uncertainty import AbsoluteGaussianUncertainty, RelativeGaussianUncertainty, \
|
|
9
10
|
AbsoluteUniformUncertainty, RelativeUniformUncertainty, ModelUncertainty, \
|
|
@@ -350,10 +351,11 @@ class ScenarioConfig(Serializable):
|
|
|
350
351
|
return self.__f_inp_in == other.f_inp_in and self.__f_msx_in == other.f_msx_in \
|
|
351
352
|
and self.__general_params == other.general_params \
|
|
352
353
|
and self.__memory_consumption_estimate == other.memory_consumption_estimate \
|
|
353
|
-
and self.__sensor_config == other.sensor_config
|
|
354
|
+
and self.__sensor_config == other.sensor_config \
|
|
355
|
+
and np.all(self.__controls == other.controls) \
|
|
354
356
|
and self.__model_uncertainty == other.model_uncertainty \
|
|
355
|
-
and self.__system_events == other.system_events \
|
|
356
|
-
and self.__sensor_reading_events == other.sensor_reading_events
|
|
357
|
+
and np.all(self.__system_events == other.system_events) \
|
|
358
|
+
and np.all(self.__sensor_reading_events == other.sensor_reading_events)
|
|
357
359
|
|
|
358
360
|
def __str__(self) -> str:
|
|
359
361
|
return f"f_inp_in: {self.f_inp_in} f_msx_in: {self.f_msx_in} " + \
|
|
@@ -415,8 +417,8 @@ class ScenarioConfig(Serializable):
|
|
|
415
417
|
general_params["demand_model"] = general_settings["demand_model"]
|
|
416
418
|
if "quality_model" in general_settings.keys():
|
|
417
419
|
general_params["quality_model"] = general_settings["quality_model"]
|
|
418
|
-
if "
|
|
419
|
-
general_params["
|
|
420
|
+
if "flow_units_id" in general_settings.keys():
|
|
421
|
+
general_params["flow_units_id"] = general_settings["flow_units_id"]
|
|
420
422
|
|
|
421
423
|
sensor_config = data["sensors"]
|
|
422
424
|
|
|
@@ -17,8 +17,8 @@ from epyt.epanet import ToolkitConstants
|
|
|
17
17
|
from tqdm import tqdm
|
|
18
18
|
|
|
19
19
|
from .scenario_config import ScenarioConfig
|
|
20
|
-
from .sensor_config import SensorConfig, areaunit_to_id, massunit_to_id,
|
|
21
|
-
|
|
20
|
+
from .sensor_config import SensorConfig, areaunit_to_id, massunit_to_id, qualityunit_to_id, \
|
|
21
|
+
qualityunit_to_str, MASS_UNIT_MG, \
|
|
22
22
|
SENSOR_TYPE_LINK_FLOW, SENSOR_TYPE_LINK_QUALITY, SENSOR_TYPE_NODE_DEMAND, \
|
|
23
23
|
SENSOR_TYPE_NODE_PRESSURE, SENSOR_TYPE_NODE_QUALITY, \
|
|
24
24
|
SENSOR_TYPE_PUMP_STATE, SENSOR_TYPE_TANK_VOLUME, SENSOR_TYPE_VALVE_STATE, \
|
|
@@ -137,7 +137,7 @@ class ScenarioSimulator():
|
|
|
137
137
|
tank_id_to_idx: dict = None, bulkspecies_id_to_idx: dict = None,
|
|
138
138
|
surfacespecies_id_to_idx: dict = None) -> SensorConfig:
|
|
139
139
|
flow_unit = self.epanet_api.api.ENgetflowunits()
|
|
140
|
-
quality_unit =
|
|
140
|
+
quality_unit = qualityunit_to_id(self.epanet_api.getQualityInfo().QualityChemUnits)
|
|
141
141
|
bulk_species = []
|
|
142
142
|
surface_species = []
|
|
143
143
|
bulk_species_mass_unit = []
|
|
@@ -298,6 +298,7 @@ class ScenarioSimulator():
|
|
|
298
298
|
|
|
299
299
|
return deepcopy(list(filter(lambda e: isinstance(e, Leakage), self.__system_events)))
|
|
300
300
|
|
|
301
|
+
@property
|
|
301
302
|
def actuator_events(self) -> list[ActuatorEvent]:
|
|
302
303
|
"""
|
|
303
304
|
Gets all actuator events.
|
|
@@ -483,6 +484,7 @@ class ScenarioSimulator():
|
|
|
483
484
|
|
|
484
485
|
if inp_file_path is not None:
|
|
485
486
|
self.epanet_api.saveInputFile(inp_file_path)
|
|
487
|
+
self.__f_inp_in = inp_file_path
|
|
486
488
|
|
|
487
489
|
if export_sensor_config is True:
|
|
488
490
|
report_desc = "\n\n[REPORT]\n"
|
|
@@ -537,6 +539,7 @@ class ScenarioSimulator():
|
|
|
537
539
|
|
|
538
540
|
if self.__f_msx_in is not None and msx_file_path is not None:
|
|
539
541
|
self.epanet_api.saveMSXFile(msx_file_path)
|
|
542
|
+
self.__f_msx_in = msx_file_path
|
|
540
543
|
|
|
541
544
|
if export_sensor_config is True:
|
|
542
545
|
report_desc = "\n\n[REPORT]\n"
|
|
@@ -691,7 +694,7 @@ class ScenarioSimulator():
|
|
|
691
694
|
return {"code": qual_info.QualityCode,
|
|
692
695
|
"type": qual_info.QualityType,
|
|
693
696
|
"chemical_name": qual_info.QualityChemName,
|
|
694
|
-
"units":
|
|
697
|
+
"units": qualityunit_to_id(qual_info.QualityChemUnits),
|
|
695
698
|
"trace_node_id": qual_info.TraceNode}
|
|
696
699
|
|
|
697
700
|
def get_scenario_config(self) -> ScenarioConfig:
|
|
@@ -708,8 +711,9 @@ class ScenarioSimulator():
|
|
|
708
711
|
|
|
709
712
|
general_params = {"hydraulic_time_step": self.get_hydraulic_time_step(),
|
|
710
713
|
"quality_time_step": self.get_quality_time_step(),
|
|
714
|
+
"reporting_time_step": self.epanet_api.getTimeReportingStep(),
|
|
711
715
|
"simulation_duration": self.get_simulation_duration(),
|
|
712
|
-
"
|
|
716
|
+
"flow_units_id": self.get_flow_units(),
|
|
713
717
|
"quality_model": self.get_quality_model(),
|
|
714
718
|
"demand_model": self.get_demand_model()}
|
|
715
719
|
|
|
@@ -761,8 +765,12 @@ class ScenarioSimulator():
|
|
|
761
765
|
nodes_id = self.epanet_api.getNodeNameID()
|
|
762
766
|
nodes_elevation = self.epanet_api.getNodeElevations()
|
|
763
767
|
nodes_type = [self.epanet_api.TYPENODE[i] for i in self.epanet_api.getNodeTypeIndex()]
|
|
768
|
+
nodes_coord = [self.epanet_api.api.ENgetcoord(node_idx)
|
|
769
|
+
for node_idx in self.epanet_api.getNodeIndex()]
|
|
770
|
+
node_tank_names = self.epanet_api.getNodeTankNameID()
|
|
764
771
|
|
|
765
772
|
links_id = self.epanet_api.getLinkNameID()
|
|
773
|
+
links_type = self.epanet_api.getLinkType()
|
|
766
774
|
links_data = self.epanet_api.getNodesConnectingLinksID()
|
|
767
775
|
links_diameter = self.epanet_api.getLinkDiameter()
|
|
768
776
|
links_length = self.epanet_api.getLinkLength()
|
|
@@ -771,22 +779,49 @@ class ScenarioSimulator():
|
|
|
771
779
|
links_wall_coeff = self.epanet_api.getLinkWallReactionCoeff()
|
|
772
780
|
links_loss_coeff = self.epanet_api.getLinkMinorLossCoeff()
|
|
773
781
|
|
|
782
|
+
pumps_id = self.epanet_api.getLinkPumpNameID()
|
|
783
|
+
pumps_type = self.epanet_api.getLinkPumpType()
|
|
784
|
+
|
|
785
|
+
valves_id = self.epanet_api.getLinkValveNameID()
|
|
786
|
+
|
|
774
787
|
# Build graph describing the topology
|
|
775
788
|
nodes = []
|
|
776
|
-
for
|
|
777
|
-
|
|
789
|
+
for node_id, node_elevation, node_type, node_coord in zip(nodes_id, nodes_elevation,
|
|
790
|
+
nodes_type, nodes_coord):
|
|
791
|
+
node_info = {"elevation": node_elevation,
|
|
792
|
+
"coord": node_coord,
|
|
793
|
+
"type": node_type}
|
|
794
|
+
if node_type == "TANK":
|
|
795
|
+
node_tank_idx = node_tank_names.index(node_id) + 1
|
|
796
|
+
node_info["diameter"] = float(self.epanet_api.getNodeTankDiameter(node_tank_idx))
|
|
778
797
|
|
|
779
|
-
|
|
780
|
-
for link_id, link, diameter, length, roughness_coeff, bulk_coeff, wall_coeff, loss_coeff \
|
|
781
|
-
in zip(links_id, links_data, links_diameter, links_length, links_roughness_coeff,
|
|
782
|
-
links_bulk_coeff, links_wall_coeff, links_loss_coeff):
|
|
783
|
-
links.append((link_id, link, {"diameter": diameter, "length": length,
|
|
784
|
-
"roughness_coeff": roughness_coeff,
|
|
785
|
-
"bulk_coeff": bulk_coeff, "wall_coeff": wall_coeff,
|
|
786
|
-
"loss_coeff": loss_coeff}))
|
|
798
|
+
nodes.append((node_id, node_info))
|
|
787
799
|
|
|
788
|
-
|
|
789
|
-
|
|
800
|
+
links = []
|
|
801
|
+
for link_id, link_type, link, diameter, length, roughness_coeff, bulk_coeff, wall_coeff, loss_coeff \
|
|
802
|
+
in zip(links_id, links_type, links_data, links_diameter, links_length,
|
|
803
|
+
links_roughness_coeff, links_bulk_coeff, links_wall_coeff, links_loss_coeff):
|
|
804
|
+
links.append((link_id, list(link),
|
|
805
|
+
{"type": link_type, "diameter": diameter, "length": length,
|
|
806
|
+
"roughness_coeff": roughness_coeff,
|
|
807
|
+
"bulk_coeff": bulk_coeff, "wall_coeff": wall_coeff,
|
|
808
|
+
"loss_coeff": loss_coeff}))
|
|
809
|
+
|
|
810
|
+
pumps = {}
|
|
811
|
+
for pump_id, pump_type in zip(pumps_id, pumps_type):
|
|
812
|
+
link_idx = links_id.index(pump_id)
|
|
813
|
+
link = links_data[link_idx]
|
|
814
|
+
pumps[pump_id] = {"type": pump_type, "end_points": link}
|
|
815
|
+
|
|
816
|
+
valves = {}
|
|
817
|
+
for valve_id in valves_id:
|
|
818
|
+
link_idx = links_id.index(valve_id)
|
|
819
|
+
link = links_data[link_idx]
|
|
820
|
+
valve_type = links_type[link_idx]
|
|
821
|
+
valves[valve_id] = {"type": valve_type, "end_points": link}
|
|
822
|
+
|
|
823
|
+
return NetworkTopology(f_inp=self.f_inp_in, nodes=nodes, links=links, pumps=pumps,
|
|
824
|
+
valves=valves, units=self.get_units_category())
|
|
790
825
|
|
|
791
826
|
def randomize_demands(self) -> None:
|
|
792
827
|
"""
|
|
@@ -1739,8 +1774,7 @@ class ScenarioSimulator():
|
|
|
1739
1774
|
# Fetch data
|
|
1740
1775
|
pressure_data = self.epanet_api.getNodePressure().reshape(1, -1)
|
|
1741
1776
|
flow_data = self.epanet_api.getLinkFlows().reshape(1, -1)
|
|
1742
|
-
demand_data = self.epanet_api.getNodeActualDemand().reshape(1,
|
|
1743
|
-
-1) # TODO: Does not go back after emitter coefficient is changed back to zero
|
|
1777
|
+
demand_data = self.epanet_api.getNodeActualDemand().reshape(1, -1)
|
|
1744
1778
|
quality_node_data = self.epanet_api.getNodeActualQuality().reshape(1, -1)
|
|
1745
1779
|
quality_link_data = self.epanet_api.getLinkActualQuality().reshape(1, -1)
|
|
1746
1780
|
pumps_state_data = self.epanet_api.getLinkPumpState().reshape(1, -1)
|
|
@@ -1808,14 +1842,14 @@ class ScenarioSimulator():
|
|
|
1808
1842
|
|
|
1809
1843
|
Parameters
|
|
1810
1844
|
----------
|
|
1811
|
-
model_uncertainty : :class:`~epyt_flow.
|
|
1845
|
+
model_uncertainty : :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`
|
|
1812
1846
|
Model uncertainty specifications.
|
|
1813
1847
|
"""
|
|
1814
1848
|
self.__adapt_to_network_changes()
|
|
1815
1849
|
|
|
1816
1850
|
if not isinstance(model_uncertainty, ModelUncertainty):
|
|
1817
1851
|
raise TypeError("'model_uncertainty' must be an instance of " +
|
|
1818
|
-
"'epyt_flow.
|
|
1852
|
+
"'epyt_flow.uncertainty.ModelUncertainty' but not of " +
|
|
1819
1853
|
f"'{type(model_uncertainty)}'")
|
|
1820
1854
|
|
|
1821
1855
|
self.__model_uncertainty = model_uncertainty
|
|
@@ -1841,7 +1875,7 @@ class ScenarioSimulator():
|
|
|
1841
1875
|
def set_general_parameters(self, demand_model: dict = None, simulation_duration: int = None,
|
|
1842
1876
|
hydraulic_time_step: int = None, quality_time_step: int = None,
|
|
1843
1877
|
reporting_time_step: int = None, reporting_time_start: int = None,
|
|
1844
|
-
|
|
1878
|
+
flow_units_id: int = None, quality_model: dict = None) -> None:
|
|
1845
1879
|
"""
|
|
1846
1880
|
Sets some general parameters.
|
|
1847
1881
|
|
|
@@ -1886,22 +1920,22 @@ class ScenarioSimulator():
|
|
|
1886
1920
|
Start time (in seconds) at which reporting of hydraulic and quality states starts.
|
|
1887
1921
|
|
|
1888
1922
|
The default is None.
|
|
1889
|
-
|
|
1923
|
+
flow_units_id : `int`, optional
|
|
1890
1924
|
Specifies the flow units -- i.e. all flows will be reported in these units.
|
|
1891
1925
|
If None, the units from the .inp file will be used.
|
|
1892
1926
|
|
|
1893
1927
|
Must be one of the following EPANET toolkit constants:
|
|
1894
1928
|
|
|
1895
|
-
- EN_CFS
|
|
1896
|
-
- EN_GPM
|
|
1897
|
-
- EN_MGD
|
|
1898
|
-
- EN_IMGD = 3
|
|
1899
|
-
- EN_AFD
|
|
1900
|
-
- EN_LPS
|
|
1901
|
-
- EN_LPM
|
|
1902
|
-
- EN_MLD
|
|
1903
|
-
- EN_CMH
|
|
1904
|
-
- EN_CMD
|
|
1929
|
+
- EN_CFS = 0 (cubic foot/sec)
|
|
1930
|
+
- EN_GPM = 1 (gal/min)
|
|
1931
|
+
- EN_MGD = 2 (Million gal/day)
|
|
1932
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
1933
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
1934
|
+
- EN_LPS = 5 (liter/sec)
|
|
1935
|
+
- EN_LPM = 6 (liter/min)
|
|
1936
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
1937
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
1938
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
1905
1939
|
|
|
1906
1940
|
The default is None.
|
|
1907
1941
|
quality_model : `dict`, optional
|
|
@@ -1922,8 +1956,7 @@ class ScenarioSimulator():
|
|
|
1922
1956
|
if not isinstance(simulation_duration, int) or simulation_duration <= 0:
|
|
1923
1957
|
raise ValueError("'simulation_duration' must be a positive integer specifying " +
|
|
1924
1958
|
"the number of seconds to simulate")
|
|
1925
|
-
self.epanet_api.setTimeSimulationDuration(simulation_duration)
|
|
1926
|
-
# duration from .inp file seems to break EPANET-MSX
|
|
1959
|
+
self.epanet_api.setTimeSimulationDuration(simulation_duration)
|
|
1927
1960
|
|
|
1928
1961
|
if hydraulic_time_step is not None:
|
|
1929
1962
|
if not isinstance(hydraulic_time_step, int) or hydraulic_time_step <= 0:
|
|
@@ -1958,29 +1991,29 @@ class ScenarioSimulator():
|
|
|
1958
1991
|
"greater than the hydraulic time step")
|
|
1959
1992
|
self.epanet_api.setTimeQualityStep(quality_time_step)
|
|
1960
1993
|
|
|
1961
|
-
if
|
|
1962
|
-
if
|
|
1994
|
+
if flow_units_id is not None:
|
|
1995
|
+
if flow_units_id == ToolkitConstants.EN_CFS:
|
|
1963
1996
|
self.epanet_api.setFlowUnitsCFS()
|
|
1964
|
-
elif
|
|
1997
|
+
elif flow_units_id == ToolkitConstants.EN_GPM:
|
|
1965
1998
|
self.epanet_api.setFlowUnitsGPM()
|
|
1966
|
-
elif
|
|
1999
|
+
elif flow_units_id == ToolkitConstants.EN_MGD:
|
|
1967
2000
|
self.epanet_api.setFlowUnitsMGD()
|
|
1968
|
-
elif
|
|
2001
|
+
elif flow_units_id == ToolkitConstants.EN_IMGD:
|
|
1969
2002
|
self.epanet_api.setFlowUnitsIMGD()
|
|
1970
|
-
elif
|
|
2003
|
+
elif flow_units_id == ToolkitConstants.EN_AFD:
|
|
1971
2004
|
self.epanet_api.setFlowUnitsAFD()
|
|
1972
|
-
elif
|
|
2005
|
+
elif flow_units_id == ToolkitConstants.EN_LPS:
|
|
1973
2006
|
self.epanet_api.setFlowUnitsLPS()
|
|
1974
|
-
elif
|
|
2007
|
+
elif flow_units_id == ToolkitConstants.EN_LPM:
|
|
1975
2008
|
self.epanet_api.setFlowUnitsLPM()
|
|
1976
|
-
elif
|
|
2009
|
+
elif flow_units_id == ToolkitConstants.EN_MLD:
|
|
1977
2010
|
self.epanet_api.setFlowUnitsMLD()
|
|
1978
|
-
elif
|
|
2011
|
+
elif flow_units_id == ToolkitConstants.EN_CMH:
|
|
1979
2012
|
self.epanet_api.setFlowUnitsCMH()
|
|
1980
|
-
elif
|
|
2013
|
+
elif flow_units_id == ToolkitConstants.EN_CMD:
|
|
1981
2014
|
self.epanet_api.setFlowUnitsCMD()
|
|
1982
2015
|
else:
|
|
1983
|
-
raise ValueError(f"Unknown flow units '{
|
|
2016
|
+
raise ValueError(f"Unknown flow units '{flow_units_id}'")
|
|
1984
2017
|
|
|
1985
2018
|
if quality_model is not None:
|
|
1986
2019
|
if quality_model["type"] == "NONE":
|
|
@@ -1989,7 +2022,7 @@ class ScenarioSimulator():
|
|
|
1989
2022
|
self.epanet_api.setQualityType("age")
|
|
1990
2023
|
elif quality_model["type"] == "CHEM":
|
|
1991
2024
|
self.epanet_api.setQualityType("chem", quality_model["chemical_name"],
|
|
1992
|
-
|
|
2025
|
+
qualityunit_to_str(quality_model["units"]))
|
|
1993
2026
|
elif quality_model["type"] == "TRACE":
|
|
1994
2027
|
self.epanet_api.setQualityType("trace", quality_model["trace_node_id"])
|
|
1995
2028
|
else:
|