epyt-flow 0.3.0__py3-none-any.whl → 0.5.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/VERSION +1 -1
- epyt_flow/data/benchmarks/leakdb.py +1 -1
- epyt_flow/simulation/events/leakages.py +1 -1
- epyt_flow/simulation/parallel_simulation.py +7 -7
- epyt_flow/simulation/scada/scada_data.py +6 -0
- epyt_flow/simulation/scada/scada_data_export.py +1 -1
- epyt_flow/simulation/scenario_config.py +5 -3
- epyt_flow/simulation/scenario_simulator.py +37 -13
- epyt_flow/simulation/scenario_visualizer.py +1 -1
- epyt_flow/simulation/sensor_config.py +37 -1
- epyt_flow/topology.py +282 -10
- epyt_flow/uncertainty/model_uncertainty.py +26 -19
- epyt_flow/utils.py +26 -3
- {epyt_flow-0.3.0.dist-info → epyt_flow-0.5.0.dist-info}/METADATA +2 -1
- {epyt_flow-0.3.0.dist-info → epyt_flow-0.5.0.dist-info}/RECORD +18 -18
- {epyt_flow-0.3.0.dist-info → epyt_flow-0.5.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.3.0.dist-info → epyt_flow-0.5.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.3.0.dist-info → epyt_flow-0.5.0.dist-info}/top_level.txt +0 -0
epyt_flow/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.0
|
|
@@ -543,7 +543,7 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
|
|
|
543
543
|
|
|
544
544
|
my_uncertainties = {"pipe_length_uncertainty": MyUniformUncertainty(low=0, high=0.25),
|
|
545
545
|
"pipe_roughness_uncertainty": MyUniformUncertainty(low=0, high=0.25),
|
|
546
|
-
"
|
|
546
|
+
"base_demand_uncertainty": MyUniformUncertainty(low=0, high=0.25)}
|
|
547
547
|
model_uncertainty = ModelUncertainty(**my_uncertainties)
|
|
548
548
|
|
|
549
549
|
# Create sensor config (place pressure and flow sensors everywhere)
|
|
@@ -182,7 +182,7 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
182
182
|
raise TypeError(f"Can not compare 'Leakage' instance with '{type(other)}' instance")
|
|
183
183
|
|
|
184
184
|
return super().__eq__(other) and self.__link_id == other.link_id \
|
|
185
|
-
and self.__diameter == other.diameter and self.__profile == other.profile \
|
|
185
|
+
and self.__diameter == other.diameter and np.all(self.__profile == other.profile) \
|
|
186
186
|
and self.__node_id == other.node_id and self.area == other.area
|
|
187
187
|
|
|
188
188
|
def __str__(self) -> str:
|
|
@@ -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)
|
|
@@ -1163,6 +1163,9 @@ class ScadaData(Serializable):
|
|
|
1163
1163
|
:func:`~epyt_flow.simulation.scada.scada_data.ScadaData.get_data` --
|
|
1164
1164
|
calling this function is the only way of accessing the energy usage of each pump.
|
|
1165
1165
|
|
|
1166
|
+
The odering in the returned NumPy array corresponds to the ordering
|
|
1167
|
+
of the pumps in EPANET.
|
|
1168
|
+
|
|
1166
1169
|
Returns
|
|
1167
1170
|
-------
|
|
1168
1171
|
`numpy.ndarray`
|
|
@@ -1180,6 +1183,9 @@ class ScadaData(Serializable):
|
|
|
1180
1183
|
:func:`~epyt_flow.simulation.scada.scada_data.ScadaData.get_data` --
|
|
1181
1184
|
calling this function is the only way of accessing the pumps' efficiency.
|
|
1182
1185
|
|
|
1186
|
+
The odering in the returned NumPy array corresponds to the ordering
|
|
1187
|
+
of the pumps in EPANET.
|
|
1188
|
+
|
|
1183
1189
|
Returns
|
|
1184
1190
|
-------
|
|
1185
1191
|
`numpy.ndarray`
|
|
@@ -109,7 +109,7 @@ class ScadaDataExport():
|
|
|
109
109
|
|
|
110
110
|
def __get_sensor_unit(sensor_type):
|
|
111
111
|
if sensor_type == "pressure":
|
|
112
|
-
if is_flowunit_simetric(scada_data.sensor_config.flow_unit):
|
|
112
|
+
if not is_flowunit_simetric(scada_data.sensor_config.flow_unit):
|
|
113
113
|
return "psi"
|
|
114
114
|
else:
|
|
115
115
|
return "meter"
|
|
@@ -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} " + \
|
|
@@ -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"
|
|
@@ -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
|
|
|
@@ -766,6 +770,7 @@ class ScenarioSimulator():
|
|
|
766
770
|
node_tank_names = self.epanet_api.getNodeTankNameID()
|
|
767
771
|
|
|
768
772
|
links_id = self.epanet_api.getLinkNameID()
|
|
773
|
+
links_type = self.epanet_api.getLinkType()
|
|
769
774
|
links_data = self.epanet_api.getNodesConnectingLinksID()
|
|
770
775
|
links_diameter = self.epanet_api.getLinkDiameter()
|
|
771
776
|
links_length = self.epanet_api.getLinkLength()
|
|
@@ -774,6 +779,11 @@ class ScenarioSimulator():
|
|
|
774
779
|
links_wall_coeff = self.epanet_api.getLinkWallReactionCoeff()
|
|
775
780
|
links_loss_coeff = self.epanet_api.getLinkMinorLossCoeff()
|
|
776
781
|
|
|
782
|
+
pumps_id = self.epanet_api.getLinkPumpNameID()
|
|
783
|
+
pumps_type = self.epanet_api.getLinkPumpType()
|
|
784
|
+
|
|
785
|
+
valves_id = self.epanet_api.getLinkValveNameID()
|
|
786
|
+
|
|
777
787
|
# Build graph describing the topology
|
|
778
788
|
nodes = []
|
|
779
789
|
for node_id, node_elevation, node_type, node_coord in zip(nodes_id, nodes_elevation,
|
|
@@ -788,16 +798,30 @@ class ScenarioSimulator():
|
|
|
788
798
|
nodes.append((node_id, node_info))
|
|
789
799
|
|
|
790
800
|
links = []
|
|
791
|
-
for link_id, link, diameter, length, roughness_coeff, bulk_coeff, wall_coeff, loss_coeff \
|
|
792
|
-
in zip(links_id, links_data, links_diameter, links_length,
|
|
793
|
-
links_bulk_coeff, links_wall_coeff, links_loss_coeff):
|
|
794
|
-
links.append((link_id, link,
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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())
|
|
801
825
|
|
|
802
826
|
def randomize_demands(self) -> None:
|
|
803
827
|
"""
|
|
@@ -1818,14 +1842,14 @@ class ScenarioSimulator():
|
|
|
1818
1842
|
|
|
1819
1843
|
Parameters
|
|
1820
1844
|
----------
|
|
1821
|
-
model_uncertainty : :class:`~epyt_flow.
|
|
1845
|
+
model_uncertainty : :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`
|
|
1822
1846
|
Model uncertainty specifications.
|
|
1823
1847
|
"""
|
|
1824
1848
|
self.__adapt_to_network_changes()
|
|
1825
1849
|
|
|
1826
1850
|
if not isinstance(model_uncertainty, ModelUncertainty):
|
|
1827
1851
|
raise TypeError("'model_uncertainty' must be an instance of " +
|
|
1828
|
-
"'epyt_flow.
|
|
1852
|
+
"'epyt_flow.uncertainty.ModelUncertainty' but not of " +
|
|
1829
1853
|
f"'{type(model_uncertainty)}'")
|
|
1830
1854
|
|
|
1831
1855
|
self.__model_uncertainty = model_uncertainty
|
|
@@ -12,7 +12,7 @@ class ScenarioVisualizer():
|
|
|
12
12
|
|
|
13
13
|
Parameters
|
|
14
14
|
----------
|
|
15
|
-
scenario : :class
|
|
15
|
+
scenario : :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
16
16
|
Scenario to be visualized.
|
|
17
17
|
"""
|
|
18
18
|
def __init__(self, scenario: ScenarioSimulator):
|
|
@@ -1647,6 +1647,42 @@ class SensorConfig(JsonSerializable):
|
|
|
1647
1647
|
"""
|
|
1648
1648
|
return deepcopy(self.__sensors_id_to_idx)
|
|
1649
1649
|
|
|
1650
|
+
def get_as_dict(self) -> dict:
|
|
1651
|
+
"""
|
|
1652
|
+
Gets the sensor configuration as a dictionary.
|
|
1653
|
+
|
|
1654
|
+
Returns
|
|
1655
|
+
-------
|
|
1656
|
+
`dict`
|
|
1657
|
+
Dictionary of set sensors -- the keys are the sensor types.
|
|
1658
|
+
"""
|
|
1659
|
+
r = {}
|
|
1660
|
+
|
|
1661
|
+
if self.__pressure_sensors != []:
|
|
1662
|
+
r["pressure"] = self.__pressure_sensors
|
|
1663
|
+
if self.__flow_sensors != []:
|
|
1664
|
+
r["flow"] = self.__flow_sensors
|
|
1665
|
+
if self.__demand_sensors != []:
|
|
1666
|
+
r["demand"] = self.__demand_sensors
|
|
1667
|
+
if self.__tank_volume_sensors != []:
|
|
1668
|
+
r["tank_volume"] = self.__tank_volume_sensors
|
|
1669
|
+
if self.__valve_state_sensors != []:
|
|
1670
|
+
r["valve_state"] = self.__valve_state_sensors
|
|
1671
|
+
if self.__pump_state_sensors != []:
|
|
1672
|
+
r["pump_state"] = self.__pump_state_sensors
|
|
1673
|
+
if self.__quality_node_sensors != []:
|
|
1674
|
+
r["node_quality"] = self.__quality_node_sensors
|
|
1675
|
+
if self.__quality_link_sensors != []:
|
|
1676
|
+
r["link_quality"] = self.__quality_link_sensors
|
|
1677
|
+
if self.__bulk_species_node_sensors != {}:
|
|
1678
|
+
r["node_bulk_species"] = self.__bulk_species_node_sensors
|
|
1679
|
+
if self.__bulk_species_link_sensors != {}:
|
|
1680
|
+
r["link_bulk_species"] = self.__bulk_species_link_sensors
|
|
1681
|
+
if self.__surface_species_sensors != {}:
|
|
1682
|
+
r["surface_species"] = self.__surface_species_sensors
|
|
1683
|
+
|
|
1684
|
+
return r
|
|
1685
|
+
|
|
1650
1686
|
def get_attributes(self) -> dict:
|
|
1651
1687
|
attr = {"nodes": self.__nodes, "links": self.__links,
|
|
1652
1688
|
"valves": self.__valves, "pumps": self.__pumps,
|
|
@@ -1719,7 +1755,7 @@ class SensorConfig(JsonSerializable):
|
|
|
1719
1755
|
f"pump_id_to_idx: {self.__pump_id_to_idx} tank_id_to_idx: {self.__tank_id_to_idx} " +\
|
|
1720
1756
|
f"valve_id_to_idx: {self.__valve_id_to_idx} " +\
|
|
1721
1757
|
f"bulkspecies_id_to_idx: {self.__bulkspecies_id_to_idx} " +\
|
|
1722
|
-
f"surfacespecies_id_to_idx: {self.__surfacespecies_id_to_idx}" +\
|
|
1758
|
+
f"surfacespecies_id_to_idx: {self.__surfacespecies_id_to_idx} " +\
|
|
1723
1759
|
f"pressure_sensors: {self.__pressure_sensors} flow_sensors: {self.__flow_sensors} " +\
|
|
1724
1760
|
f"demand_sensors: {self.__demand_sensors} " +\
|
|
1725
1761
|
f"quality_node_sensors: {self.__quality_node_sensors} " +\
|
epyt_flow/topology.py
CHANGED
|
@@ -7,6 +7,8 @@ from typing import Any
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import networkx as nx
|
|
9
9
|
from scipy.sparse import bsr_array
|
|
10
|
+
from geopandas import GeoDataFrame
|
|
11
|
+
from shapely.geometry import Point, LineString
|
|
10
12
|
|
|
11
13
|
from .serialization import serializable, JsonSerializable, NETWORK_TOPOLOGY_ID
|
|
12
14
|
|
|
@@ -55,9 +57,15 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
55
57
|
Path to .inp file to which this topology belongs.
|
|
56
58
|
nodes : `list[tuple[str, dict]]`
|
|
57
59
|
List of all nodes -- i.e. node ID and node information such as type and elevation.
|
|
58
|
-
links : `list[tuple[tuple[str, str], dict]]`
|
|
60
|
+
links : `list[tuple[str, tuple[str, str], dict]]`
|
|
59
61
|
List of all links/pipes -- i.e. link ID, ID of connecting nodes, and link information
|
|
60
62
|
such as pipe diameter, length, etc.
|
|
63
|
+
pumps : `dict`
|
|
64
|
+
List of all pumps -- i.e. valve ID, and information such as
|
|
65
|
+
pump type and connecting nodes.
|
|
66
|
+
valves : `dict`
|
|
67
|
+
List of all valves -- i.e. valve ID, and information such as
|
|
68
|
+
valve type and connecting nodes.
|
|
61
69
|
units : `int`
|
|
62
70
|
Measurement units category -- i.e. US Customary or SI Metric.
|
|
63
71
|
|
|
@@ -68,12 +76,16 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
68
76
|
"""
|
|
69
77
|
def __init__(self, f_inp: str, nodes: list[tuple[str, dict]],
|
|
70
78
|
links: list[tuple[str, tuple[str, str], dict]],
|
|
79
|
+
pumps: dict,
|
|
80
|
+
valves: dict,
|
|
71
81
|
units: int = None,
|
|
72
82
|
**kwds):
|
|
73
83
|
super().__init__(name=f_inp, **kwds)
|
|
74
84
|
|
|
75
85
|
self.__nodes = nodes
|
|
76
86
|
self.__links = links
|
|
87
|
+
self.__pumps = pumps
|
|
88
|
+
self.__valves = valves
|
|
77
89
|
self.__units = units
|
|
78
90
|
|
|
79
91
|
if units is None:
|
|
@@ -87,15 +99,16 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
87
99
|
self.add_node(node_id, info={"elevation": node_elevation, "type": node_type})
|
|
88
100
|
|
|
89
101
|
for link_id, link, link_info in links:
|
|
102
|
+
link_type = link_info["type"]
|
|
90
103
|
link_diameter = link_info["diameter"]
|
|
91
104
|
link_length = link_info["length"]
|
|
92
105
|
self.add_edge(link[0], link[1], length=link_length,
|
|
93
|
-
info={"id": link_id, "
|
|
94
|
-
"length": link_length})
|
|
106
|
+
info={"id": link_id, "type": link_type, "nodes": link,
|
|
107
|
+
"diameter": link_diameter, "length": link_length})
|
|
95
108
|
|
|
96
109
|
def convert_units(self, units: int) -> Any:
|
|
97
110
|
"""
|
|
98
|
-
Converts this instance to a :class
|
|
111
|
+
Converts this instance to a :class:`~epyt_flow.topology.NetworkTopology` instance
|
|
99
112
|
where everything is measured in given measurement units category
|
|
100
113
|
(US Customary or SI Metric).
|
|
101
114
|
|
|
@@ -111,7 +124,7 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
111
124
|
|
|
112
125
|
Returns
|
|
113
126
|
-------
|
|
114
|
-
:class
|
|
127
|
+
:class:`~epyt_flow.topology.NetworkTopology`
|
|
115
128
|
Network topology with the new measurements units.
|
|
116
129
|
"""
|
|
117
130
|
if self.__units is None:
|
|
@@ -161,7 +174,8 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
161
174
|
|
|
162
175
|
links.append((link_id, link_nodes, link_info))
|
|
163
176
|
|
|
164
|
-
return NetworkTopology(f_inp=self.name, nodes=nodes, links=links,
|
|
177
|
+
return NetworkTopology(f_inp=self.name, nodes=nodes, links=links, pumps=self.pumps,
|
|
178
|
+
valves=self.valves, units=units)
|
|
165
179
|
|
|
166
180
|
def get_all_nodes(self) -> list[str]:
|
|
167
181
|
"""
|
|
@@ -185,6 +199,98 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
185
199
|
"""
|
|
186
200
|
return [(link_id, end_points) for link_id, end_points, _ in self.__links]
|
|
187
201
|
|
|
202
|
+
def get_all_junctions(self) -> list[str]:
|
|
203
|
+
"""
|
|
204
|
+
Gets all junctions -- i.e. nodes that are not tanks or reservoirs.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
`list[str]`
|
|
209
|
+
List of all junctions.
|
|
210
|
+
"""
|
|
211
|
+
r = []
|
|
212
|
+
|
|
213
|
+
for node_id in self.get_all_nodes():
|
|
214
|
+
if self.get_node_info(node_id)["type"] == "JUNCTION":
|
|
215
|
+
r.append(node_id)
|
|
216
|
+
|
|
217
|
+
return r
|
|
218
|
+
|
|
219
|
+
def get_all_tanks(self) -> list[str]:
|
|
220
|
+
"""
|
|
221
|
+
Gets all tanks -- i.e. nodes that are not junctions or reservoirs.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
`list[str]`
|
|
226
|
+
List of all tanks.
|
|
227
|
+
"""
|
|
228
|
+
r = []
|
|
229
|
+
|
|
230
|
+
for node_id in self.get_all_nodes():
|
|
231
|
+
if self.get_node_info(node_id)["type"] == "TANK":
|
|
232
|
+
r.append(node_id)
|
|
233
|
+
|
|
234
|
+
return r
|
|
235
|
+
|
|
236
|
+
def get_all_reservoirs(self) -> list[str]:
|
|
237
|
+
"""
|
|
238
|
+
Gets all reservoirs -- i.e. nodes that are not junctions or tanks.
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
`list[str]`
|
|
243
|
+
List of all reservoirs.
|
|
244
|
+
"""
|
|
245
|
+
r = []
|
|
246
|
+
|
|
247
|
+
for node_id in self.get_all_nodes():
|
|
248
|
+
if self.get_node_info(node_id)["type"] == "RESERVOIR":
|
|
249
|
+
r.append(node_id)
|
|
250
|
+
|
|
251
|
+
return r
|
|
252
|
+
|
|
253
|
+
def get_all_pipes(self) -> list[tuple[str, tuple[str, str]]]:
|
|
254
|
+
"""
|
|
255
|
+
Gets all pipes -- i.e. links that not valves or pumps.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
`list[tuple[str, tuple[str, str]]]`
|
|
260
|
+
List of all pipes -- (link ID, (left node ID, right node ID)).
|
|
261
|
+
"""
|
|
262
|
+
r = []
|
|
263
|
+
|
|
264
|
+
for link_id, link_nodes in self.get_all_links():
|
|
265
|
+
link_info = self.get_link_info(link_id)
|
|
266
|
+
|
|
267
|
+
if link_info["type"] == "PIPE":
|
|
268
|
+
r.append((link_id, link_nodes))
|
|
269
|
+
|
|
270
|
+
return r
|
|
271
|
+
|
|
272
|
+
def get_all_pumps(self) -> list[str]:
|
|
273
|
+
"""
|
|
274
|
+
Gets the IDs of all pumps.
|
|
275
|
+
|
|
276
|
+
Returns
|
|
277
|
+
-------
|
|
278
|
+
`list[str]`
|
|
279
|
+
Pump IDs.
|
|
280
|
+
"""
|
|
281
|
+
return self.__pumps.keys()
|
|
282
|
+
|
|
283
|
+
def get_all_valves(self) -> list[str]:
|
|
284
|
+
"""
|
|
285
|
+
Gets the IDs of all valves.
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
`list[str]`
|
|
290
|
+
Valve IDs.
|
|
291
|
+
"""
|
|
292
|
+
return self.__valves.keys()
|
|
293
|
+
|
|
188
294
|
def get_node_info(self, node_id: str) -> dict:
|
|
189
295
|
"""
|
|
190
296
|
Gets all information (e.g. elevation, type, etc.) associated with a given node.
|
|
@@ -207,17 +313,19 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
207
313
|
|
|
208
314
|
def get_link_info(self, link_id: str) -> dict:
|
|
209
315
|
"""
|
|
210
|
-
Gets all information (e.g. diameter, length, etc.) associated with a given link
|
|
316
|
+
Gets all information (e.g. diameter, length, etc.) associated with a given link.
|
|
317
|
+
|
|
318
|
+
Note that links can be pipes, pumps, or valves.
|
|
211
319
|
|
|
212
320
|
Parameters
|
|
213
321
|
----------
|
|
214
322
|
link_id : `str`
|
|
215
|
-
ID of the link
|
|
323
|
+
ID of the link.
|
|
216
324
|
|
|
217
325
|
Returns
|
|
218
326
|
-------
|
|
219
327
|
`dict`
|
|
220
|
-
Information associated with the given link
|
|
328
|
+
Information associated with the given link.
|
|
221
329
|
"""
|
|
222
330
|
for link_id_, link_nodes, link_info in self.__links:
|
|
223
331
|
if link_id_ == link_id:
|
|
@@ -225,6 +333,68 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
225
333
|
|
|
226
334
|
raise ValueError(f"Unknown link '{link_id}'")
|
|
227
335
|
|
|
336
|
+
def get_pump_info(self, pump_id: str) -> dict:
|
|
337
|
+
"""
|
|
338
|
+
Gets all information associated with a given pump.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
pump_id : `str`
|
|
343
|
+
ID of the pump.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
`dict`
|
|
348
|
+
Pump information.
|
|
349
|
+
"""
|
|
350
|
+
if pump_id in self.__pumps:
|
|
351
|
+
return self.__pumps[pump_id]
|
|
352
|
+
else:
|
|
353
|
+
raise ValueError(f"Unknown pump: '{pump_id}'")
|
|
354
|
+
|
|
355
|
+
def get_valve_info(self, valve_id: str) -> dict:
|
|
356
|
+
"""
|
|
357
|
+
Gets all information associated with a given valve.
|
|
358
|
+
|
|
359
|
+
Parameters
|
|
360
|
+
----------
|
|
361
|
+
valve_id : `str`
|
|
362
|
+
ID of the valve.
|
|
363
|
+
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
`dict`
|
|
367
|
+
Valve information.
|
|
368
|
+
"""
|
|
369
|
+
if valve_id in self.__valves:
|
|
370
|
+
return self.__valves[valve_id]
|
|
371
|
+
else:
|
|
372
|
+
raise ValueError(f"Unknown valve: '{valve_id}'")
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def pumps(self) -> dict:
|
|
376
|
+
"""
|
|
377
|
+
Gets all pumps -- i.e. ID and associated information such as the pump type.
|
|
378
|
+
|
|
379
|
+
Returns
|
|
380
|
+
-------
|
|
381
|
+
`dict`
|
|
382
|
+
All pumps and their associated information.
|
|
383
|
+
"""
|
|
384
|
+
return deepcopy(self.__pumps)
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def valves(self) -> dict:
|
|
388
|
+
"""
|
|
389
|
+
Gets all valves -- i.e. ID and associated information such as the valve type.
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
`dict`
|
|
394
|
+
All valves and their associated information.
|
|
395
|
+
"""
|
|
396
|
+
return deepcopy(self.__valves)
|
|
397
|
+
|
|
228
398
|
@property
|
|
229
399
|
def units(self) -> int:
|
|
230
400
|
"""
|
|
@@ -251,18 +421,120 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
251
421
|
self.get_all_nodes() == other.get_all_nodes() \
|
|
252
422
|
and all(link_a[0] == link_b[0] and all(link_a[1] == link_b[1])
|
|
253
423
|
for link_a, link_b in zip(self.get_all_links(), other.get_all_links())) \
|
|
254
|
-
and self.__units == other.units
|
|
424
|
+
and self.__units == other.units \
|
|
425
|
+
and self.__pumps == other.pumps \
|
|
426
|
+
and self.__valves == other.valves
|
|
255
427
|
|
|
256
428
|
def __str__(self) -> str:
|
|
257
429
|
return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links} " +\
|
|
430
|
+
f"pumps: {self.__pumps} valves: {self.__valves} " +\
|
|
258
431
|
f"units: {unitscategoryid_to_str(self.__units)}"
|
|
259
432
|
|
|
260
433
|
def get_attributes(self) -> dict:
|
|
261
434
|
return super().get_attributes() | {"f_inp": self.name,
|
|
262
435
|
"nodes": self.__nodes,
|
|
263
436
|
"links": self.__links,
|
|
437
|
+
"pumps": self.__pumps,
|
|
438
|
+
"valves": self.__valves,
|
|
264
439
|
"units": self.__units}
|
|
265
440
|
|
|
441
|
+
def to_gis(self, coord_reference_system: str = None, pumps_as_points: bool = False,
|
|
442
|
+
valves_as_points: bool = False) -> dict:
|
|
443
|
+
"""
|
|
444
|
+
Gets the network topology as a dictionary of `geopandas.GeoDataFrames` instances --
|
|
445
|
+
i.e. each quantity (nodes, links/pipes, valves, etc.) is represented by a
|
|
446
|
+
`geopandas.GeoDataFrames` instance.
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
coord_reference_system : `str`, optional
|
|
451
|
+
Coordinate reference system.
|
|
452
|
+
|
|
453
|
+
The default is None.
|
|
454
|
+
pumps_as_points : `bool`, optional
|
|
455
|
+
If True, pumps are represented by points, otherwise by lines.
|
|
456
|
+
|
|
457
|
+
The default is False.
|
|
458
|
+
|
|
459
|
+
valves_as_points : `bool`, optional
|
|
460
|
+
If True, valves are represented by points, otherwise by lines.
|
|
461
|
+
|
|
462
|
+
The default is False.
|
|
463
|
+
|
|
464
|
+
Returns
|
|
465
|
+
-------
|
|
466
|
+
`dict`
|
|
467
|
+
Network topology as a dictionary of `geopandas.GeoDataFrames` instances.
|
|
468
|
+
If a quantity does not exist, the data frame will be None.
|
|
469
|
+
"""
|
|
470
|
+
gis = {"nodes": None, "links": None,
|
|
471
|
+
"tanks": None, "reservoirs": None,
|
|
472
|
+
"valves": None, "pumps": None}
|
|
473
|
+
|
|
474
|
+
# Nodes
|
|
475
|
+
node_data = {"id": [], "type": [], "elevation": [], "geometry": []}
|
|
476
|
+
tank_data = {"id": [], "elevation": [], "diameter": [], "geometry": []}
|
|
477
|
+
reservoir_data = {"id": [], "elevation": [], "geometry": []}
|
|
478
|
+
for node_id in self.get_all_nodes():
|
|
479
|
+
node_info = self.get_node_info(node_id)
|
|
480
|
+
|
|
481
|
+
node_data["id"].append(node_id)
|
|
482
|
+
node_data["type"].append(node_info["type"])
|
|
483
|
+
node_data["elevation"].append(node_info["elevation"])
|
|
484
|
+
node_data["geometry"].append(Point(node_info["coord"]))
|
|
485
|
+
|
|
486
|
+
if node_info["type"] == "TANK":
|
|
487
|
+
tank_data["id"].append(node_id)
|
|
488
|
+
tank_data["elevation"].append(node_info["elevation"])
|
|
489
|
+
tank_data["diameter"].append(node_info["diameter"])
|
|
490
|
+
tank_data["geometry"].append(Point(node_info["coord"]))
|
|
491
|
+
elif node_info["type"] == "RESERVOIR":
|
|
492
|
+
reservoir_data["id"].append(node_id)
|
|
493
|
+
reservoir_data["elevation"].append(node_info["elevation"])
|
|
494
|
+
reservoir_data["geometry"].append(Point(node_info["coord"]))
|
|
495
|
+
|
|
496
|
+
gis["nodes"] = GeoDataFrame(node_data, crs=coord_reference_system)
|
|
497
|
+
gis["tanks"] = GeoDataFrame(tank_data, crs=coord_reference_system)
|
|
498
|
+
gis["reservoirs"] = GeoDataFrame(reservoir_data, crs=coord_reference_system)
|
|
499
|
+
|
|
500
|
+
# Links
|
|
501
|
+
pipe_data = {"id": [], "type": [], "end_point_a": [], "end_point_b": [],
|
|
502
|
+
"length": [], "diameter": [], "geometry": []}
|
|
503
|
+
valve_data = {"id": [], "type": [], "geometry": []}
|
|
504
|
+
pump_data = {"id": [], "type": [], "geometry": []}
|
|
505
|
+
for link_id, link_nodes in self.get_all_links():
|
|
506
|
+
link_info = self.get_link_info(link_id)
|
|
507
|
+
end_points_coord = [self.get_node_info(n)["coord"] for n in link_nodes]
|
|
508
|
+
|
|
509
|
+
if link_info["type"] == "PIPE":
|
|
510
|
+
pipe_data["id"].append(link_id)
|
|
511
|
+
pipe_data["type"].append(link_info["type"])
|
|
512
|
+
pipe_data["end_point_a"].append(link_nodes[0])
|
|
513
|
+
pipe_data["end_point_b"].append(link_nodes[1])
|
|
514
|
+
pipe_data["length"].append(link_info["length"])
|
|
515
|
+
pipe_data["diameter"].append(link_info["diameter"])
|
|
516
|
+
pipe_data["geometry"].append(LineString(end_points_coord))
|
|
517
|
+
elif link_info["type"] == "PUMP":
|
|
518
|
+
pump_data["id"].append(link_id)
|
|
519
|
+
pump_data["type"].append(self.get_pump_info(link_id)["type"])
|
|
520
|
+
if pumps_as_points is True:
|
|
521
|
+
pump_data["geometry"].append(Point(end_points_coord[0]))
|
|
522
|
+
else:
|
|
523
|
+
pump_data["geometry"].append(LineString(end_points_coord))
|
|
524
|
+
else: # Valve
|
|
525
|
+
valve_data["id"].append(link_id)
|
|
526
|
+
valve_data["type"].append(self.get_valve_info[link_id]["type"])
|
|
527
|
+
if valves_as_points is True:
|
|
528
|
+
valve_data["geometry"].append(Point(end_points_coord[0]))
|
|
529
|
+
else:
|
|
530
|
+
valve_data["geometry"].append(LineString(end_points_coord))
|
|
531
|
+
|
|
532
|
+
gis["pipes"] = GeoDataFrame(pipe_data, crs=coord_reference_system)
|
|
533
|
+
gis["valves"] = GeoDataFrame(valve_data, crs=coord_reference_system)
|
|
534
|
+
gis["pumps"] = GeoDataFrame(pump_data, crs=coord_reference_system)
|
|
535
|
+
|
|
536
|
+
return gis
|
|
537
|
+
|
|
266
538
|
def get_adj_matrix(self) -> bsr_array:
|
|
267
539
|
"""
|
|
268
540
|
Gets the adjacency matrix of this graph.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Module provides a class for implementing model uncertainty.
|
|
3
3
|
"""
|
|
4
4
|
from copy import deepcopy
|
|
5
|
+
import warnings
|
|
5
6
|
import epyt
|
|
6
7
|
import numpy as np
|
|
7
8
|
|
|
@@ -29,7 +30,7 @@ class ModelUncertainty(JsonSerializable):
|
|
|
29
30
|
Uncertainty of pipe diameters. None, in the case of no uncertainty.
|
|
30
31
|
|
|
31
32
|
The default is None.
|
|
32
|
-
|
|
33
|
+
base_demand_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional
|
|
33
34
|
Uncertainty of base demands. None, in the case of no uncertainty.
|
|
34
35
|
|
|
35
36
|
The default is None.
|
|
@@ -53,11 +54,17 @@ class ModelUncertainty(JsonSerializable):
|
|
|
53
54
|
def __init__(self, pipe_length_uncertainty: Uncertainty = None,
|
|
54
55
|
pipe_roughness_uncertainty: Uncertainty = None,
|
|
55
56
|
pipe_diameter_uncertainty: Uncertainty = None,
|
|
56
|
-
|
|
57
|
+
base_demand_uncertainty: Uncertainty = None,
|
|
57
58
|
demand_pattern_uncertainty: Uncertainty = None,
|
|
58
59
|
elevation_uncertainty: Uncertainty = None,
|
|
59
60
|
constants_uncertainty: Uncertainty = None,
|
|
60
|
-
parameters_uncertainty: Uncertainty = None,
|
|
61
|
+
parameters_uncertainty: Uncertainty = None,
|
|
62
|
+
demand_base_uncertainty: Uncertainty = None, **kwds):
|
|
63
|
+
if demand_base_uncertainty is not None:
|
|
64
|
+
warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
|
|
65
|
+
" -- support of such old files will be removed in the next release!",
|
|
66
|
+
DeprecationWarning)
|
|
67
|
+
|
|
61
68
|
if pipe_length_uncertainty is not None:
|
|
62
69
|
if not isinstance(pipe_length_uncertainty, Uncertainty):
|
|
63
70
|
raise TypeError("'pipe_length_uncertainty' must be an instance of " +
|
|
@@ -73,11 +80,11 @@ class ModelUncertainty(JsonSerializable):
|
|
|
73
80
|
raise TypeError("'pipe_diameter_uncertainty' must be an instance of " +
|
|
74
81
|
"'epyt_flow.uncertainty.Uncertainty' but not of " +
|
|
75
82
|
f"'{type(pipe_diameter_uncertainty)}'")
|
|
76
|
-
if
|
|
77
|
-
if not isinstance(
|
|
78
|
-
raise TypeError("'
|
|
83
|
+
if base_demand_uncertainty is not None:
|
|
84
|
+
if not isinstance(base_demand_uncertainty, Uncertainty):
|
|
85
|
+
raise TypeError("'base_demand_uncertainty' must be an instance of " +
|
|
79
86
|
"'epyt_flow.uncertainty.Uncertainty' but not of " +
|
|
80
|
-
f"'{type(
|
|
87
|
+
f"'{type(base_demand_uncertainty)}'")
|
|
81
88
|
if demand_pattern_uncertainty is not None:
|
|
82
89
|
if not isinstance(demand_pattern_uncertainty, Uncertainty):
|
|
83
90
|
raise TypeError("'demand_pattern_uncertainty' must be an instance of " +
|
|
@@ -102,7 +109,7 @@ class ModelUncertainty(JsonSerializable):
|
|
|
102
109
|
self.__pipe_length = pipe_length_uncertainty
|
|
103
110
|
self.__pipe_roughness = pipe_roughness_uncertainty
|
|
104
111
|
self.__pipe_diameter = pipe_diameter_uncertainty
|
|
105
|
-
self.
|
|
112
|
+
self.__base_demand = base_demand_uncertainty
|
|
106
113
|
self.__demand_pattern = demand_pattern_uncertainty
|
|
107
114
|
self.__elevation = elevation_uncertainty
|
|
108
115
|
self.__constants = constants_uncertainty
|
|
@@ -147,16 +154,16 @@ class ModelUncertainty(JsonSerializable):
|
|
|
147
154
|
return deepcopy(self.__pipe_diameter)
|
|
148
155
|
|
|
149
156
|
@property
|
|
150
|
-
def
|
|
157
|
+
def base_demand(self) -> Uncertainty:
|
|
151
158
|
"""
|
|
152
|
-
Gets the demand
|
|
159
|
+
Gets the base demand uncertainty.
|
|
153
160
|
|
|
154
161
|
Returns
|
|
155
162
|
-------
|
|
156
163
|
:class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
|
|
157
164
|
Demand base uncertainty.
|
|
158
165
|
"""
|
|
159
|
-
return deepcopy(self.
|
|
166
|
+
return deepcopy(self.__base_demand)
|
|
160
167
|
|
|
161
168
|
@property
|
|
162
169
|
def demand_pattern(self) -> Uncertainty:
|
|
@@ -210,7 +217,7 @@ class ModelUncertainty(JsonSerializable):
|
|
|
210
217
|
return super().get_attributes() | {"pipe_length_uncertainty": self.__pipe_length,
|
|
211
218
|
"pipe_roughness_uncertainty": self.__pipe_roughness,
|
|
212
219
|
"pipe_diameter_uncertainty": self.__pipe_diameter,
|
|
213
|
-
"
|
|
220
|
+
"base_demand_uncertainty": self.__base_demand,
|
|
214
221
|
"demand_pattern_uncertainty": self.__demand_pattern,
|
|
215
222
|
"elevation_uncertainty": self.__elevation,
|
|
216
223
|
"constants_uncertainty": self.__constants,
|
|
@@ -224,14 +231,14 @@ class ModelUncertainty(JsonSerializable):
|
|
|
224
231
|
return self.__pipe_length == other.pipe_length \
|
|
225
232
|
and self.__pipe_roughness == other.pipe_roughness \
|
|
226
233
|
and self.__pipe_diameter == other.pipe_diameter \
|
|
227
|
-
and self.
|
|
234
|
+
and self.__base_demand == other.base_demand \
|
|
228
235
|
and self.__demand_pattern == other.demand_pattern \
|
|
229
236
|
and self.__elevation == other.elevation \
|
|
230
237
|
and self.__parameters == other.parameters and self.__constants == other.constants
|
|
231
238
|
|
|
232
239
|
def __str__(self) -> str:
|
|
233
240
|
return f"pipe_length: {self.__pipe_length} pipe_roughness: {self.__pipe_roughness} " + \
|
|
234
|
-
f"pipe_diameter: {self.__pipe_diameter} demand_base: {self.
|
|
241
|
+
f"pipe_diameter: {self.__pipe_diameter} demand_base: {self.__base_demand} " + \
|
|
235
242
|
f"demand_pattern: {self.__demand_pattern} elevation: {self.__elevation} " + \
|
|
236
243
|
f"constants: {self.__constants} parameters: {self.__parameters}"
|
|
237
244
|
|
|
@@ -259,14 +266,14 @@ class ModelUncertainty(JsonSerializable):
|
|
|
259
266
|
coeffs = self.__pipe_roughness.apply_batch(coeffs)
|
|
260
267
|
epanet_api.setLinkRoughnessCoeff(coeffs)
|
|
261
268
|
|
|
262
|
-
if self.
|
|
269
|
+
if self.__base_demand is not None:
|
|
263
270
|
all_nodes_idx = epanet_api.getNodeIndex()
|
|
264
271
|
for node_idx in all_nodes_idx:
|
|
265
272
|
n_demand_categories = epanet_api.getNodeDemandCategoriesNumber(node_idx)
|
|
266
|
-
for demand_category in range(
|
|
267
|
-
base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category]
|
|
268
|
-
base_demand = self.
|
|
269
|
-
epanet_api.setNodeBaseDemands(node_idx, demand_category, base_demand)
|
|
273
|
+
for demand_category in range(n_demand_categories):
|
|
274
|
+
base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1]
|
|
275
|
+
base_demand = self.__base_demand.apply(base_demand)
|
|
276
|
+
epanet_api.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
|
|
270
277
|
|
|
271
278
|
if self.__demand_pattern is not None:
|
|
272
279
|
demand_patterns_idx = epanet_api.getNodeDemandPatternIndex()
|
epyt_flow/utils.py
CHANGED
|
@@ -153,8 +153,10 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
156
|
-
confidence_interval: np.ndarray = None,
|
|
157
|
-
|
|
156
|
+
confidence_interval: np.ndarray = None,
|
|
157
|
+
x_axis_label: str = None, y_axis_label: str = None,
|
|
158
|
+
show: bool = True, ax: matplotlib.axes.Axes = None
|
|
159
|
+
) -> matplotlib.axes.Axes:
|
|
158
160
|
"""
|
|
159
161
|
Plots the prediction (e.g. forecast) of *single* time series together with the
|
|
160
162
|
ground truth time series. In addition, confidence intervals can be plotted as well.
|
|
@@ -169,6 +171,14 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
169
171
|
Confidence interval (upper and lower value) for each prediction in `y_pred`.
|
|
170
172
|
If not None, the confidence interval is plotted as well.
|
|
171
173
|
|
|
174
|
+
The default is None.
|
|
175
|
+
x_axis_label : `str`, optional
|
|
176
|
+
X axis label.
|
|
177
|
+
|
|
178
|
+
The default is None.
|
|
179
|
+
y_axis_label : `str`, optional
|
|
180
|
+
Y axis label.
|
|
181
|
+
|
|
172
182
|
The default is None.
|
|
173
183
|
show : `bool`, optional
|
|
174
184
|
If True, the plot/figure is shown in a window.
|
|
@@ -198,6 +208,14 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
198
208
|
raise ValueError("'y_pred' must be a 1d array")
|
|
199
209
|
if len(y.shape) != 1:
|
|
200
210
|
raise ValueError("'y' must be a 1d array")
|
|
211
|
+
if x_axis_label is not None:
|
|
212
|
+
if not isinstance(x_axis_label, str):
|
|
213
|
+
raise TypeError("'x_axis_label' must be an instance of 'str' " +
|
|
214
|
+
f"but not of '{type(x_axis_label)}'")
|
|
215
|
+
if y_axis_label is not None:
|
|
216
|
+
if not isinstance(y_axis_label, str):
|
|
217
|
+
raise TypeError("'y_axis_label' must be an instance of 'str' " +
|
|
218
|
+
f"but not of '{type(y_axis_label)}'")
|
|
201
219
|
if not isinstance(show, bool):
|
|
202
220
|
raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
|
|
203
221
|
if ax is not None:
|
|
@@ -214,10 +232,15 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
214
232
|
y_pred - confidence_interval[0],
|
|
215
233
|
y_pred + confidence_interval[1],
|
|
216
234
|
alpha=0.5)
|
|
217
|
-
ax.plot(y_pred, ".-", label="Prediction")
|
|
218
235
|
ax.plot(y, ".-", label="Ground truth")
|
|
236
|
+
ax.plot(y_pred, ".-", label="Prediction")
|
|
219
237
|
ax.legend()
|
|
220
238
|
|
|
239
|
+
if x_axis_label is not None:
|
|
240
|
+
ax.set_xlabel(x_axis_label)
|
|
241
|
+
if y_axis_label is not None:
|
|
242
|
+
ax.set_ylabel(y_axis_label)
|
|
243
|
+
|
|
221
244
|
if show is True and fig is not None:
|
|
222
245
|
plt.show()
|
|
223
246
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: epyt-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -30,6 +30,7 @@ Requires-Dist: tqdm >=4.66.2
|
|
|
30
30
|
Requires-Dist: openpyxl >=3.1.2
|
|
31
31
|
Requires-Dist: falcon >=3.1.3
|
|
32
32
|
Requires-Dist: multiprocess >=0.70.16
|
|
33
|
+
Requires-Dist: geopandas >=0.14.4
|
|
33
34
|
Requires-Dist: psutil
|
|
34
35
|
|
|
35
36
|
[](https://pypi.org/project/epyt-flow/)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
epyt_flow/VERSION,sha256=
|
|
1
|
+
epyt_flow/VERSION,sha256=oK1QZAE5pST4ZZEVcUW_HUZ06pwGW_6iFVjw97BEMMg,6
|
|
2
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
|
|
5
|
+
epyt_flow/topology.py,sha256=yKIspoX4uoo9rAeVR5eWJ6D91QNozHzvzjiry9bFkNI,23424
|
|
6
|
+
epyt_flow/utils.py,sha256=AB2MuknQ_16UE-URQe1WShIS7dmSyFwZYVHxMVT539k,12379
|
|
7
7
|
epyt_flow/EPANET/compile_linux.sh,sha256=wcrDyiB8NkivmaC-X9FI2WxhY3IJqDLiyIbVTv2XEPY,489
|
|
8
8
|
epyt_flow/EPANET/compile_macos.sh,sha256=1K33-bPdgr01EIf87YUvmOFHXyOkBWI6mKXQ8x1Hzmo,504
|
|
9
9
|
epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS,sha256=yie5yAsEEPY0984PmkSRUdqEU9rVvRSGGWmjxdwCYMU,925
|
|
@@ -87,7 +87,7 @@ epyt_flow/data/benchmarks/batadal_data.py,sha256=oIzcysGivMPAgrfzrk5l8i-j6Ii96DP
|
|
|
87
87
|
epyt_flow/data/benchmarks/battledim.py,sha256=K0j9tbfxuXJmqBQY_e5XBJZGDYavaOZ7efkCT96Fb8A,20167
|
|
88
88
|
epyt_flow/data/benchmarks/battledim_data.py,sha256=0vHm-2eAiLv6U-n5dqUUWS1o_szFRy9mVJ3eqDRp4PE,3373
|
|
89
89
|
epyt_flow/data/benchmarks/gecco_water_quality.py,sha256=1buZRJiNf4jsqWYg4Ud90GhqaiLVo4yij3RAZJkzsqE,10985
|
|
90
|
-
epyt_flow/data/benchmarks/leakdb.py,sha256=
|
|
90
|
+
epyt_flow/data/benchmarks/leakdb.py,sha256=Z_ah6FXAyN4M7dn4jH0qYFmKTtViNHd6Jn2L9I6nMYg,24952
|
|
91
91
|
epyt_flow/data/benchmarks/leakdb_data.py,sha256=FNssgMkC1wqWVlaOrrihr4Od9trEZY7KeK5KuBeRMvM,507058
|
|
92
92
|
epyt_flow/data/benchmarks/water_usage.py,sha256=FLqjff3pha33oEU9ZM3UGPXn9eJJumsJH8Gdj7YFX3A,4778
|
|
93
93
|
epyt_flow/gym/__init__.py,sha256=KNTDtPTEtHwZ4ehHfj9qGw81Z9acFqPIgMzYUzH5_uM,115
|
|
@@ -110,30 +110,30 @@ epyt_flow/rest_api/scenario/handlers.py,sha256=bDqMa-jM4hOMVqs0XvQJtHbQDZx9FP40G
|
|
|
110
110
|
epyt_flow/rest_api/scenario/simulation_handlers.py,sha256=oY1Ch6ZQgYT_5WeE1I7tvGqpDKlT664GHLdmcVKP7ek,5905
|
|
111
111
|
epyt_flow/rest_api/scenario/uncertainty_handlers.py,sha256=uuu6AP11ZZUp2P3Dnukjg5ZTjyYKljlubg1xLN1GnXY,4048
|
|
112
112
|
epyt_flow/simulation/__init__.py,sha256=VGGJqJRUoXZjKJ0-m6KPp3JQqD_1TFW0pofLgkwZJ8M,164
|
|
113
|
-
epyt_flow/simulation/parallel_simulation.py,sha256=
|
|
114
|
-
epyt_flow/simulation/scenario_config.py,sha256=
|
|
115
|
-
epyt_flow/simulation/scenario_simulator.py,sha256=
|
|
116
|
-
epyt_flow/simulation/scenario_visualizer.py,sha256=
|
|
117
|
-
epyt_flow/simulation/sensor_config.py,sha256=
|
|
113
|
+
epyt_flow/simulation/parallel_simulation.py,sha256=VmC7xemjxRB_N0fx1AAQ7ux82tnyTi7jk7jfFpeg7gM,6523
|
|
114
|
+
epyt_flow/simulation/scenario_config.py,sha256=_Zs0EPyfet0mTLpAt-oMN3k7jCW3x7_VbOQHpHVntrA,26597
|
|
115
|
+
epyt_flow/simulation/scenario_simulator.py,sha256=1amMxO-PVHBU_AN6K9Ga6xzFmpaX2ZZnhwkirtj6IgY,93036
|
|
116
|
+
epyt_flow/simulation/scenario_visualizer.py,sha256=fpj67zl69q-byg7Oxocqhmu1S3P7B3ROCkSYzWyM--0,2187
|
|
117
|
+
epyt_flow/simulation/sensor_config.py,sha256=MGeerEnLhyFLE6XA2p-X9Swd4k0o0T7QboeGuTUFq7I,81584
|
|
118
118
|
epyt_flow/simulation/events/__init__.py,sha256=tIdqzs7_Cus4X2kbZG4Jl2zs-zsk_4rnajFOCvL0zlI,185
|
|
119
119
|
epyt_flow/simulation/events/actuator_events.py,sha256=2_MPYbYO9As6fMkm5Oy9pjSB9kCvFuKpGu8ykYDAydg,7903
|
|
120
120
|
epyt_flow/simulation/events/event.py,sha256=kARPV20XCAl6zxnJwI9U7ICtZUPACO_rgAmtHm1mGCs,2603
|
|
121
|
-
epyt_flow/simulation/events/leakages.py,sha256=
|
|
121
|
+
epyt_flow/simulation/events/leakages.py,sha256=g4asHeB3s7mJekY7WTdmTFb6NsoHov8lEciclS3um0I,15275
|
|
122
122
|
epyt_flow/simulation/events/sensor_faults.py,sha256=XX6k-GJh9RWZ4x54eGj9um-Ir9Eq41tY_9pRSCeYeqc,8447
|
|
123
123
|
epyt_flow/simulation/events/sensor_reading_attack.py,sha256=bo5VavArN0wD5AHbIXJC9NFGZ7KR1uyWE6tBtwj0k9I,7538
|
|
124
124
|
epyt_flow/simulation/events/sensor_reading_event.py,sha256=rQ-CmdpSUyZzDFYwNUGH2jGoj0oyU-aAb-7E8Oshhqw,6785
|
|
125
125
|
epyt_flow/simulation/events/system_event.py,sha256=0KI2iaAaOyC9Y-FIfFVazeKT_4ORQRp26gWyMBUu_3c,2396
|
|
126
126
|
epyt_flow/simulation/scada/__init__.py,sha256=ZFAxJVqwEVsgiyFilFetnb13gPhZg1JEOPWYvKIJT4c,90
|
|
127
127
|
epyt_flow/simulation/scada/advanced_control.py,sha256=Enox02ggt36HdFLX7ZNxgxuqsTEeu9AACHrzU8CXGrg,4489
|
|
128
|
-
epyt_flow/simulation/scada/scada_data.py,sha256=
|
|
129
|
-
epyt_flow/simulation/scada/scada_data_export.py,sha256=
|
|
128
|
+
epyt_flow/simulation/scada/scada_data.py,sha256=WaFSppBHvJOwSyLYNs9Pu2GngSzWsfkpqPfBQoeiEcU,105101
|
|
129
|
+
epyt_flow/simulation/scada/scada_data_export.py,sha256=0BwDgV-5qZx17wIyWQ8Nl2TPgho3mBI49027RDq8sDA,11217
|
|
130
130
|
epyt_flow/uncertainty/__init__.py,sha256=ZRjuJL9rDpWVSdPwObPxFpEmMTcgAl3VmPOsS6cIyGg,89
|
|
131
|
-
epyt_flow/uncertainty/model_uncertainty.py,sha256
|
|
131
|
+
epyt_flow/uncertainty/model_uncertainty.py,sha256=-2QT2AffZerKZyZ_w_mmeqYpfBALyPDvV61sCrvcK1o,13966
|
|
132
132
|
epyt_flow/uncertainty/sensor_noise.py,sha256=zJVULxnxVPSSqc6UW0iwZ9O-HGf9dn4CwScPqf4yCY0,2324
|
|
133
133
|
epyt_flow/uncertainty/uncertainties.py,sha256=X-o7GZUC0HELtzpoXIAJaAeYOw35N05TuRoSmStcCpI,17669
|
|
134
134
|
epyt_flow/uncertainty/utils.py,sha256=gq66c9-QMOxOqI6wgWLyFxjVV0fbG0_8Yzd6mQjNYNo,5315
|
|
135
|
-
epyt_flow-0.
|
|
136
|
-
epyt_flow-0.
|
|
137
|
-
epyt_flow-0.
|
|
138
|
-
epyt_flow-0.
|
|
139
|
-
epyt_flow-0.
|
|
135
|
+
epyt_flow-0.5.0.dist-info/LICENSE,sha256=-4hYIY2BLmCkdOv2_PehEwlnMKTCes8_oyIUXjKtkug,1076
|
|
136
|
+
epyt_flow-0.5.0.dist-info/METADATA,sha256=a7kEbUctgxuMBJ06GNrwJnAlr_arMtcxHg-TZzSiD8M,7087
|
|
137
|
+
epyt_flow-0.5.0.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
|
|
138
|
+
epyt_flow-0.5.0.dist-info/top_level.txt,sha256=Wh_kd7TRL8ownCw3Y3dxx-9C0iTSk6wNauv_NX9JcrY,10
|
|
139
|
+
epyt_flow-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|