epyt-flow 0.5.0__py3-none-any.whl → 0.7.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/battledim.py +6 -2
- epyt_flow/data/benchmarks/leakdb.py +11 -9
- epyt_flow/data/networks.py +1 -2
- epyt_flow/gym/scenario_control_env.py +101 -6
- epyt_flow/metrics.py +66 -4
- epyt_flow/serialization.py +46 -3
- epyt_flow/simulation/scada/advanced_control.py +1 -1
- epyt_flow/simulation/scada/scada_data.py +160 -62
- epyt_flow/simulation/scenario_config.py +6 -2
- epyt_flow/simulation/scenario_simulator.py +362 -65
- epyt_flow/simulation/sensor_config.py +186 -15
- epyt_flow/topology.py +93 -5
- epyt_flow/uncertainty/uncertainties.py +8 -0
- epyt_flow/utils.py +69 -2
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/METADATA +49 -6
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/RECORD +20 -20
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -4,6 +4,7 @@ Module provides a class for scenario simulations.
|
|
|
4
4
|
import sys
|
|
5
5
|
import os
|
|
6
6
|
import pathlib
|
|
7
|
+
import time
|
|
7
8
|
from typing import Generator, Union
|
|
8
9
|
from copy import deepcopy
|
|
9
10
|
import shutil
|
|
@@ -21,8 +22,9 @@ from .sensor_config import SensorConfig, areaunit_to_id, massunit_to_id, quality
|
|
|
21
22
|
qualityunit_to_str, MASS_UNIT_MG, \
|
|
22
23
|
SENSOR_TYPE_LINK_FLOW, SENSOR_TYPE_LINK_QUALITY, SENSOR_TYPE_NODE_DEMAND, \
|
|
23
24
|
SENSOR_TYPE_NODE_PRESSURE, SENSOR_TYPE_NODE_QUALITY, \
|
|
24
|
-
SENSOR_TYPE_PUMP_STATE,
|
|
25
|
-
|
|
25
|
+
SENSOR_TYPE_PUMP_STATE, SENSOR_TYPE_PUMP_EFFICIENCY, SENSOR_TYPE_PUMP_ENERGYCONSUMPTION, \
|
|
26
|
+
SENSOR_TYPE_TANK_VOLUME, SENSOR_TYPE_VALVE_STATE, SENSOR_TYPE_NODE_BULK_SPECIES, \
|
|
27
|
+
SENSOR_TYPE_LINK_BULK_SPECIES, SENSOR_TYPE_SURFACE_SPECIES
|
|
26
28
|
from ..uncertainty import ModelUncertainty, SensorNoise
|
|
27
29
|
from .events import SystemEvent, Leakage, ActuatorEvent, SensorFault, SensorReadingAttack, \
|
|
28
30
|
SensorReadingEvent
|
|
@@ -92,6 +94,7 @@ class ScenarioSimulator():
|
|
|
92
94
|
self.__controls = []
|
|
93
95
|
self.__system_events = []
|
|
94
96
|
self.__sensor_reading_events = []
|
|
97
|
+
self.__running_simulation = False
|
|
95
98
|
|
|
96
99
|
custom_epanet_lib = None
|
|
97
100
|
custom_epanetmsx_lib = None
|
|
@@ -109,12 +112,50 @@ class ScenarioSimulator():
|
|
|
109
112
|
if os.path.isfile(os.path.join(path_to_custom_libs, libepanetmsx_name)):
|
|
110
113
|
custom_epanetmsx_lib = os.path.join(path_to_custom_libs, libepanetmsx_name)
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
with warnings.catch_warnings():
|
|
116
|
+
# Treat all warnings as exceptions when trying to load .inp and .msx files
|
|
117
|
+
warnings.simplefilter('error')
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
# Workaround for EPyT bug concerning parallel simulations (see EPyT issue #54):
|
|
120
|
+
# 1. Create random tmp folder (make sure it is unique!)
|
|
121
|
+
# 2. Copy .inp and .msx file there
|
|
122
|
+
# 3. Use those copies when loading EPyT
|
|
123
|
+
tmp_folder_path = os.path.join(get_temp_folder(), f"{random.randint(int(1e5), int(1e7))}{time.time()}")
|
|
124
|
+
pathlib.Path(tmp_folder_path).mkdir(parents=True, exist_ok=False)
|
|
125
|
+
|
|
126
|
+
def __file_exists(file_in: str) -> bool:
|
|
127
|
+
try:
|
|
128
|
+
return pathlib.Path(file_in).is_file()
|
|
129
|
+
except Exception:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
if not __file_exists(self.__f_inp_in):
|
|
133
|
+
my_f_inp_in = self.__f_inp_in
|
|
134
|
+
self.__my_f_inp_in = None
|
|
135
|
+
else:
|
|
136
|
+
my_f_inp_in = os.path.join(tmp_folder_path, pathlib.Path(self.__f_inp_in).name)
|
|
137
|
+
shutil.copyfile(self.__f_inp_in, my_f_inp_in)
|
|
138
|
+
self.__my_f_inp_in = my_f_inp_in
|
|
139
|
+
|
|
140
|
+
if self.__f_msx_in is not None:
|
|
141
|
+
if not __file_exists(self.__f_msx_in):
|
|
142
|
+
my_f_msx_in = self.__f_msx_in
|
|
143
|
+
self.__my_f_msx_in = None
|
|
144
|
+
else:
|
|
145
|
+
my_f_msx_in = os.path.join(tmp_folder_path, pathlib.Path(self.__f_msx_in).name)
|
|
146
|
+
shutil.copyfile(self.__f_msx_in, my_f_msx_in)
|
|
147
|
+
self.__my_f_msx_in = my_f_msx_in
|
|
148
|
+
else:
|
|
149
|
+
my_f_msx_in = None
|
|
150
|
+
self.__my_f_msx_in = None
|
|
151
|
+
|
|
152
|
+
self.epanet_api = epanet(my_f_inp_in, ph=self.__f_msx_in is None,
|
|
153
|
+
customlib=custom_epanet_lib, loadfile=True,
|
|
154
|
+
display_msg=epanet_verbose,
|
|
155
|
+
display_warnings=False)
|
|
156
|
+
|
|
157
|
+
if self.__f_msx_in is not None:
|
|
158
|
+
self.epanet_api.loadMSXFile(my_f_msx_in, customMSXlib=custom_epanetmsx_lib)
|
|
118
159
|
|
|
119
160
|
self.__sensor_config = self.__get_empty_sensor_config()
|
|
120
161
|
if scenario_config is not None:
|
|
@@ -393,6 +434,9 @@ class ScenarioSimulator():
|
|
|
393
434
|
new_sensor_config.quality_node_sensors = self.__sensor_config.quality_node_sensors
|
|
394
435
|
new_sensor_config.quality_link_sensors = self.__sensor_config.quality_link_sensors
|
|
395
436
|
new_sensor_config.pump_state_sensors = self.__sensor_config.pump_state_sensors
|
|
437
|
+
new_sensor_config.pump_efficiency_sensors = self.__sensor_config.pump_efficiency_sensors
|
|
438
|
+
new_sensor_config.pump_energyconsumption_sensors = self.__sensor_config.\
|
|
439
|
+
pump_energyconsumption_sensors
|
|
396
440
|
new_sensor_config.valve_state_sensors = self.__sensor_config.valve_state_sensors
|
|
397
441
|
new_sensor_config.tank_volume_sensors = self.__sensor_config.tank_volume_sensors
|
|
398
442
|
new_sensor_config.bulk_species_node_sensors = self.__sensor_config.bulk_species_node_sensors
|
|
@@ -412,6 +456,9 @@ class ScenarioSimulator():
|
|
|
412
456
|
|
|
413
457
|
self.epanet_api.unload()
|
|
414
458
|
|
|
459
|
+
if self.__my_f_inp_in is not None:
|
|
460
|
+
shutil.rmtree(pathlib.Path(self.__my_f_inp_in).parent)
|
|
461
|
+
|
|
415
462
|
def __enter__(self):
|
|
416
463
|
return self
|
|
417
464
|
|
|
@@ -697,6 +744,19 @@ class ScenarioSimulator():
|
|
|
697
744
|
"units": qualityunit_to_id(qual_info.QualityChemUnits),
|
|
698
745
|
"trace_node_id": qual_info.TraceNode}
|
|
699
746
|
|
|
747
|
+
def get_reporting_time_step(self) -> int:
|
|
748
|
+
"""
|
|
749
|
+
Gets the reporting time steps -- i.e. time steps at which sensor readings are provided.
|
|
750
|
+
|
|
751
|
+
Is always a multiple of the hydraulic time step.
|
|
752
|
+
|
|
753
|
+
Returns
|
|
754
|
+
-------
|
|
755
|
+
`int`
|
|
756
|
+
Reporting time steps in seconds.
|
|
757
|
+
"""
|
|
758
|
+
return self.epanet_api.getTimeReportingStep()
|
|
759
|
+
|
|
700
760
|
def get_scenario_config(self) -> ScenarioConfig:
|
|
701
761
|
"""
|
|
702
762
|
Gets the configuration of this scenario -- i.e. all information & elements
|
|
@@ -711,7 +771,7 @@ class ScenarioSimulator():
|
|
|
711
771
|
|
|
712
772
|
general_params = {"hydraulic_time_step": self.get_hydraulic_time_step(),
|
|
713
773
|
"quality_time_step": self.get_quality_time_step(),
|
|
714
|
-
"reporting_time_step": self.
|
|
774
|
+
"reporting_time_step": self.get_reporting_time_step(),
|
|
715
775
|
"simulation_duration": self.get_simulation_duration(),
|
|
716
776
|
"flow_units_id": self.get_flow_units(),
|
|
717
777
|
"quality_model": self.get_quality_model(),
|
|
@@ -756,7 +816,7 @@ class ScenarioSimulator():
|
|
|
756
816
|
|
|
757
817
|
Returns
|
|
758
818
|
-------
|
|
759
|
-
|
|
819
|
+
:class:`~epyt_flow.topology.NetworkTopology`
|
|
760
820
|
Topology of this WDN as a graph.
|
|
761
821
|
"""
|
|
762
822
|
self.__adapt_to_network_changes()
|
|
@@ -827,6 +887,9 @@ class ScenarioSimulator():
|
|
|
827
887
|
"""
|
|
828
888
|
Randomizes all demand patterns.
|
|
829
889
|
"""
|
|
890
|
+
if self.__running_simulation is True:
|
|
891
|
+
raise RuntimeError("Can not change general parameters when simulation is running.")
|
|
892
|
+
|
|
830
893
|
self.__adapt_to_network_changes()
|
|
831
894
|
|
|
832
895
|
# Get all demand patterns
|
|
@@ -949,6 +1012,9 @@ class ScenarioSimulator():
|
|
|
949
1012
|
event : :class:`~epyt_flow.simulation.events.system_event.SystemEvent`
|
|
950
1013
|
System event.
|
|
951
1014
|
"""
|
|
1015
|
+
if self.__running_simulation is True:
|
|
1016
|
+
raise RuntimeError("Can not add events when simulation is running.")
|
|
1017
|
+
|
|
952
1018
|
self.__adapt_to_network_changes()
|
|
953
1019
|
|
|
954
1020
|
if not isinstance(event, SystemEvent):
|
|
@@ -968,6 +1034,9 @@ class ScenarioSimulator():
|
|
|
968
1034
|
sensor_fault_event : :class:`~epyt_flow.simulation.events.sensor_faults.SensorFault`
|
|
969
1035
|
Sensor fault specifications.
|
|
970
1036
|
"""
|
|
1037
|
+
if self.__running_simulation is True:
|
|
1038
|
+
raise RuntimeError("Can not add events when simulation is running.")
|
|
1039
|
+
|
|
971
1040
|
self.__adapt_to_network_changes()
|
|
972
1041
|
|
|
973
1042
|
sensor_fault_event.validate(self.__sensor_config)
|
|
@@ -988,6 +1057,9 @@ class ScenarioSimulator():
|
|
|
988
1057
|
sensor_reading_attack : :class:`~epyt_flow.simulation.events.sensor_reading_attack.SensorReadingAttack`
|
|
989
1058
|
Sensor fault specifications.
|
|
990
1059
|
"""
|
|
1060
|
+
if self.__running_simulation is True:
|
|
1061
|
+
raise RuntimeError("Can not add events when simulation is running.")
|
|
1062
|
+
|
|
991
1063
|
self.__adapt_to_network_changes()
|
|
992
1064
|
|
|
993
1065
|
sensor_reading_attack.validate(self.__sensor_config)
|
|
@@ -1008,6 +1080,9 @@ class ScenarioSimulator():
|
|
|
1008
1080
|
event : :class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`
|
|
1009
1081
|
Sensor reading event.
|
|
1010
1082
|
"""
|
|
1083
|
+
if self.__running_simulation is True:
|
|
1084
|
+
raise RuntimeError("Can not add events when simulation is running.")
|
|
1085
|
+
|
|
1011
1086
|
self.__adapt_to_network_changes()
|
|
1012
1087
|
|
|
1013
1088
|
event.validate(self.__sensor_config)
|
|
@@ -1027,16 +1102,18 @@ class ScenarioSimulator():
|
|
|
1027
1102
|
----------
|
|
1028
1103
|
sensor_type : `int`
|
|
1029
1104
|
Sensor type. Must be one of the following:
|
|
1030
|
-
- SENSOR_TYPE_NODE_PRESSURE
|
|
1031
|
-
- SENSOR_TYPE_NODE_QUALITY
|
|
1032
|
-
- SENSOR_TYPE_NODE_DEMAND
|
|
1033
|
-
- SENSOR_TYPE_LINK_FLOW
|
|
1034
|
-
- SENSOR_TYPE_LINK_QUALITY
|
|
1035
|
-
- SENSOR_TYPE_VALVE_STATE
|
|
1036
|
-
- SENSOR_TYPE_PUMP_STATE
|
|
1037
|
-
- SENSOR_TYPE_TANK_VOLUME
|
|
1038
|
-
- SENSOR_TYPE_BULK_SPECIES
|
|
1039
|
-
- SENSOR_TYPE_SURFACE_SPECIES
|
|
1105
|
+
- SENSOR_TYPE_NODE_PRESSURE = 1
|
|
1106
|
+
- SENSOR_TYPE_NODE_QUALITY = 2
|
|
1107
|
+
- SENSOR_TYPE_NODE_DEMAND = 3
|
|
1108
|
+
- SENSOR_TYPE_LINK_FLOW = 4
|
|
1109
|
+
- SENSOR_TYPE_LINK_QUALITY = 5
|
|
1110
|
+
- SENSOR_TYPE_VALVE_STATE = 6
|
|
1111
|
+
- SENSOR_TYPE_PUMP_STATE = 7
|
|
1112
|
+
- SENSOR_TYPE_TANK_VOLUME = 8
|
|
1113
|
+
- SENSOR_TYPE_BULK_SPECIES = 9
|
|
1114
|
+
- SENSOR_TYPE_SURFACE_SPECIES = 10
|
|
1115
|
+
- SENSOR_TYPE_PUMP_EFFICIENCY = 12
|
|
1116
|
+
- SENSOR_TYPE_PUMP_ENERGYCONSUMPTION = 13
|
|
1040
1117
|
sensor_locations : `list[str]` or `dict`
|
|
1041
1118
|
Locations (IDs) of sensors either as a list or as a dict in the case of
|
|
1042
1119
|
bulk and surface species.
|
|
@@ -1057,6 +1134,10 @@ class ScenarioSimulator():
|
|
|
1057
1134
|
self.__sensor_config.valve_state_sensors = sensor_locations
|
|
1058
1135
|
elif sensor_type == SENSOR_TYPE_PUMP_STATE:
|
|
1059
1136
|
self.__sensor_config.pump_state_sensors = sensor_locations
|
|
1137
|
+
elif sensor_type == SENSOR_TYPE_PUMP_EFFICIENCY:
|
|
1138
|
+
self.__sensor_config.pump_efficiency_sensors = sensor_locations
|
|
1139
|
+
elif sensor_type == SENSOR_TYPE_PUMP_ENERGYCONSUMPTION:
|
|
1140
|
+
self.__sensor_config.pump_energyconsumption_sensors = sensor_locations
|
|
1060
1141
|
elif sensor_type == SENSOR_TYPE_TANK_VOLUME:
|
|
1061
1142
|
self.__sensor_config.tank_volume_sensors = sensor_locations
|
|
1062
1143
|
elif sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES:
|
|
@@ -1081,6 +1162,22 @@ class ScenarioSimulator():
|
|
|
1081
1162
|
"""
|
|
1082
1163
|
self.set_sensors(SENSOR_TYPE_NODE_PRESSURE, sensor_locations)
|
|
1083
1164
|
|
|
1165
|
+
def place_pressure_sensors_everywhere(self, junctions_only: bool = False) -> None:
|
|
1166
|
+
"""
|
|
1167
|
+
Places a pressure sensor at every node in the network.
|
|
1168
|
+
|
|
1169
|
+
Parameters
|
|
1170
|
+
----------
|
|
1171
|
+
junctions_only : `bool`, optional
|
|
1172
|
+
If True, pressure sensors are only placed at junctions but not at tanks and reservoirs.
|
|
1173
|
+
|
|
1174
|
+
The default is False.
|
|
1175
|
+
"""
|
|
1176
|
+
if junctions_only is True:
|
|
1177
|
+
self.set_pressure_sensors(self.epanet_api.getNodeJunctionNameID())
|
|
1178
|
+
else:
|
|
1179
|
+
self.set_pressure_sensors(self.__sensor_config.nodes)
|
|
1180
|
+
|
|
1084
1181
|
def set_flow_sensors(self, sensor_locations: list[str]) -> None:
|
|
1085
1182
|
"""
|
|
1086
1183
|
Sets the flow sensors -- i.e. measuring flows at some links/pipes in the network.
|
|
@@ -1092,6 +1189,12 @@ class ScenarioSimulator():
|
|
|
1092
1189
|
"""
|
|
1093
1190
|
self.set_sensors(SENSOR_TYPE_LINK_FLOW, sensor_locations)
|
|
1094
1191
|
|
|
1192
|
+
def place_flow_sensors_everywhere(self) -> None:
|
|
1193
|
+
"""
|
|
1194
|
+
Places a flow sensors at every link/pipe in the network.
|
|
1195
|
+
"""
|
|
1196
|
+
self.set_flow_sensors(self.__sensor_config.links)
|
|
1197
|
+
|
|
1095
1198
|
def set_demand_sensors(self, sensor_locations: list[str]) -> None:
|
|
1096
1199
|
"""
|
|
1097
1200
|
Sets the demand sensors -- i.e. measuring demands at some nodes in the network.
|
|
@@ -1103,6 +1206,12 @@ class ScenarioSimulator():
|
|
|
1103
1206
|
"""
|
|
1104
1207
|
self.set_sensors(SENSOR_TYPE_NODE_DEMAND, sensor_locations)
|
|
1105
1208
|
|
|
1209
|
+
def place_demand_sensors_everywhere(self) -> None:
|
|
1210
|
+
"""
|
|
1211
|
+
Places a demand sensor at every node in the network.
|
|
1212
|
+
"""
|
|
1213
|
+
self.set_demand_sensors(self.__sensor_config.nodes)
|
|
1214
|
+
|
|
1106
1215
|
def set_node_quality_sensors(self, sensor_locations: list[str]) -> None:
|
|
1107
1216
|
"""
|
|
1108
1217
|
Sets the node quality sensors -- i.e. measuring the water quality
|
|
@@ -1115,6 +1224,12 @@ class ScenarioSimulator():
|
|
|
1115
1224
|
"""
|
|
1116
1225
|
self.set_sensors(SENSOR_TYPE_NODE_QUALITY, sensor_locations)
|
|
1117
1226
|
|
|
1227
|
+
def place_node_quality_sensors_everywhere(self) -> None:
|
|
1228
|
+
"""
|
|
1229
|
+
Places a water quality sensor at every node in the network.
|
|
1230
|
+
"""
|
|
1231
|
+
self.set_node_quality_sensors(self.__sensor_config.nodes)
|
|
1232
|
+
|
|
1118
1233
|
def set_link_quality_sensors(self, sensor_locations: list[str]) -> None:
|
|
1119
1234
|
"""
|
|
1120
1235
|
Sets the link quality sensors -- i.e. measuring the water quality
|
|
@@ -1127,6 +1242,12 @@ class ScenarioSimulator():
|
|
|
1127
1242
|
"""
|
|
1128
1243
|
self.set_sensors(SENSOR_TYPE_LINK_QUALITY, sensor_locations)
|
|
1129
1244
|
|
|
1245
|
+
def place_link_quality_sensors_everywhere(self) -> None:
|
|
1246
|
+
"""
|
|
1247
|
+
Places a water quality sensor at every link/pipe in the network.
|
|
1248
|
+
"""
|
|
1249
|
+
self.set_link_quality_sensors(self.__sensor_config.links)
|
|
1250
|
+
|
|
1130
1251
|
def set_valve_sensors(self, sensor_locations: list[str]) -> None:
|
|
1131
1252
|
"""
|
|
1132
1253
|
Sets the valve state sensors -- i.e. retrieving the state of some valves in the network.
|
|
@@ -1138,7 +1259,16 @@ class ScenarioSimulator():
|
|
|
1138
1259
|
"""
|
|
1139
1260
|
self.set_sensors(SENSOR_TYPE_VALVE_STATE, sensor_locations)
|
|
1140
1261
|
|
|
1141
|
-
def
|
|
1262
|
+
def place_valve_sensors_everywhere(self) -> None:
|
|
1263
|
+
"""
|
|
1264
|
+
Places a valve state sensor at every valve in the network.
|
|
1265
|
+
"""
|
|
1266
|
+
if len(self.__sensor_config.valves) == 0:
|
|
1267
|
+
warnings.warn("Network does not contain any valves", UserWarning)
|
|
1268
|
+
|
|
1269
|
+
self.set_valve_sensors(self.__sensor_config.valves)
|
|
1270
|
+
|
|
1271
|
+
def set_pump_state_sensors(self, sensor_locations: list[str]) -> None:
|
|
1142
1272
|
"""
|
|
1143
1273
|
Sets the pump state sensors -- i.e. retrieving the state of some pumps in the network.
|
|
1144
1274
|
|
|
@@ -1149,6 +1279,81 @@ class ScenarioSimulator():
|
|
|
1149
1279
|
"""
|
|
1150
1280
|
self.set_sensors(SENSOR_TYPE_PUMP_STATE, sensor_locations)
|
|
1151
1281
|
|
|
1282
|
+
def place_pump_state_sensors_everywhere(self) -> None:
|
|
1283
|
+
"""
|
|
1284
|
+
Places a pump state sensor at every pump in the network.
|
|
1285
|
+
"""
|
|
1286
|
+
if len(self.__sensor_config.pumps) == 0:
|
|
1287
|
+
warnings.warn("Network does not contain any pumps", UserWarning)
|
|
1288
|
+
|
|
1289
|
+
self.set_pump_state_sensors(self.__sensor_config.pumps)
|
|
1290
|
+
|
|
1291
|
+
def set_pump_efficiency_sensors(self, sensor_locations: list[str]) -> None:
|
|
1292
|
+
"""
|
|
1293
|
+
Sets the pump efficiency sensors -- i.e. retrieving the efficiency of
|
|
1294
|
+
some pumps in the network.
|
|
1295
|
+
|
|
1296
|
+
Parameters
|
|
1297
|
+
----------
|
|
1298
|
+
sensor_locations : `list[str]`
|
|
1299
|
+
Locations (IDs) of sensors.
|
|
1300
|
+
"""
|
|
1301
|
+
self.set_sensors(SENSOR_TYPE_PUMP_EFFICIENCY, sensor_locations)
|
|
1302
|
+
|
|
1303
|
+
def place_pump_efficiency_sensors_everywhere(self) -> None:
|
|
1304
|
+
"""
|
|
1305
|
+
Places a pump efficiency sensor at every pump in the network.
|
|
1306
|
+
"""
|
|
1307
|
+
if len(self.__sensor_config.pumps) == 0:
|
|
1308
|
+
warnings.warn("Network does not contain any pumps", UserWarning)
|
|
1309
|
+
|
|
1310
|
+
self.set_pump_efficiency_sensors(self.__sensor_config.pumps)
|
|
1311
|
+
|
|
1312
|
+
def set_pump_energyconsumption_sensors(self, sensor_locations: list[str]) -> None:
|
|
1313
|
+
"""
|
|
1314
|
+
Sets the pump energy consumption sensors -- i.e. retrieving the energy consumption of
|
|
1315
|
+
some pumps in the network.
|
|
1316
|
+
|
|
1317
|
+
Parameters
|
|
1318
|
+
----------
|
|
1319
|
+
sensor_locations : `list[str]`
|
|
1320
|
+
Locations (IDs) of sensors.
|
|
1321
|
+
"""
|
|
1322
|
+
self.set_sensors(SENSOR_TYPE_PUMP_ENERGYCONSUMPTION, sensor_locations)
|
|
1323
|
+
|
|
1324
|
+
def place_pump_energyconsumption_sensors_everywhere(self) -> None:
|
|
1325
|
+
"""
|
|
1326
|
+
Places a pump energy consumption sensor at every pump in the network.
|
|
1327
|
+
"""
|
|
1328
|
+
if len(self.__sensor_config.pumps) == 0:
|
|
1329
|
+
warnings.warn("Network does not contain any pumps", UserWarning)
|
|
1330
|
+
|
|
1331
|
+
self.set_pump_energyconsumption_sensors(self.__sensor_config.pumps)
|
|
1332
|
+
|
|
1333
|
+
def set_pump_sensors(self, sensor_locations: list[str]) -> None:
|
|
1334
|
+
"""
|
|
1335
|
+
Sets the pump sensors -- i.e. retrieving the state, efficiency, and energy consumption
|
|
1336
|
+
of some pumps in the network.
|
|
1337
|
+
|
|
1338
|
+
Parameters
|
|
1339
|
+
----------
|
|
1340
|
+
sensor_locations : `list[str]`
|
|
1341
|
+
Locations (IDs) of sensors.
|
|
1342
|
+
"""
|
|
1343
|
+
self.set_sensors(SENSOR_TYPE_PUMP_STATE, sensor_locations)
|
|
1344
|
+
self.set_sensors(SENSOR_TYPE_PUMP_EFFICIENCY, sensor_locations)
|
|
1345
|
+
self.set_sensors(SENSOR_TYPE_PUMP_ENERGYCONSUMPTION, sensor_locations)
|
|
1346
|
+
|
|
1347
|
+
def place_pump_sensors_everywhere(self) -> None:
|
|
1348
|
+
"""
|
|
1349
|
+
Palces pump sensors at every pump in the network -- i.e. retrieving the state, efficiency,
|
|
1350
|
+
and energy consumption of all pumps in the network.
|
|
1351
|
+
"""
|
|
1352
|
+
if len(self.__sensor_config.pumps) == 0:
|
|
1353
|
+
warnings.warn("Network does not contain any pumps", UserWarning)
|
|
1354
|
+
|
|
1355
|
+
self.set_pump_sensors(self.__sensor_config.pumps)
|
|
1356
|
+
|
|
1152
1357
|
def set_tank_sensors(self, sensor_locations: list[str]) -> None:
|
|
1153
1358
|
"""
|
|
1154
1359
|
Sets the tank volume sensors -- i.e. measuring water volumes in some tanks in the network.
|
|
@@ -1160,6 +1365,15 @@ class ScenarioSimulator():
|
|
|
1160
1365
|
"""
|
|
1161
1366
|
self.set_sensors(SENSOR_TYPE_TANK_VOLUME, sensor_locations)
|
|
1162
1367
|
|
|
1368
|
+
def place_tank_sensors_everywhere(self) -> None:
|
|
1369
|
+
"""
|
|
1370
|
+
Places a water tank volume sensor at every tank in the network.
|
|
1371
|
+
"""
|
|
1372
|
+
if len(self.__sensor_config.tanks) == 0:
|
|
1373
|
+
warnings.warn("Network does not contain any tanks", UserWarning)
|
|
1374
|
+
|
|
1375
|
+
self.set_tank_sensors(self.__sensor_config.tanks)
|
|
1376
|
+
|
|
1163
1377
|
def set_bulk_species_node_sensors(self, sensor_info: dict) -> None:
|
|
1164
1378
|
"""
|
|
1165
1379
|
Sets the bulk species node sensors -- i.e. measuring bulk species concentrations
|
|
@@ -1172,6 +1386,14 @@ class ScenarioSimulator():
|
|
|
1172
1386
|
"""
|
|
1173
1387
|
self.set_sensors(SENSOR_TYPE_NODE_BULK_SPECIES, sensor_info)
|
|
1174
1388
|
|
|
1389
|
+
def place_bulk_species_node_sensors_everywhere(self) -> None:
|
|
1390
|
+
"""
|
|
1391
|
+
Places bulk species concentration sensors at every node in the network for
|
|
1392
|
+
every bulk species.
|
|
1393
|
+
"""
|
|
1394
|
+
self.set_bulk_species_node_sensors({species_id: self.__sensor_config.nodes
|
|
1395
|
+
for species_id in self.__sensor_config.bulk_species})
|
|
1396
|
+
|
|
1175
1397
|
def set_bulk_species_link_sensors(self, sensor_info: dict) -> None:
|
|
1176
1398
|
"""
|
|
1177
1399
|
Sets the bulk species link/pipe sensors -- i.e. measuring bulk species concentrations
|
|
@@ -1184,6 +1406,14 @@ class ScenarioSimulator():
|
|
|
1184
1406
|
"""
|
|
1185
1407
|
self.set_sensors(SENSOR_TYPE_LINK_BULK_SPECIES, sensor_info)
|
|
1186
1408
|
|
|
1409
|
+
def place_bulk_species_link_sensors_everywhere(self) -> None:
|
|
1410
|
+
"""
|
|
1411
|
+
Places bulk species concentration sensors at every link/pipe in the network
|
|
1412
|
+
for every bulk species.
|
|
1413
|
+
"""
|
|
1414
|
+
self.set_bulk_species_link_sensors({species_id: self.__sensor_config.links
|
|
1415
|
+
for species_id in self.__sensor_config.bulk_species})
|
|
1416
|
+
|
|
1187
1417
|
def set_surface_species_sensors(self, sensor_info: dict) -> None:
|
|
1188
1418
|
"""
|
|
1189
1419
|
Sets the surface species sensors -- i.e. measuring surface species concentrations
|
|
@@ -1196,6 +1426,22 @@ class ScenarioSimulator():
|
|
|
1196
1426
|
"""
|
|
1197
1427
|
self.set_sensors(SENSOR_TYPE_SURFACE_SPECIES, sensor_info)
|
|
1198
1428
|
|
|
1429
|
+
def place_surface_species_sensors_everywhere(self) -> None:
|
|
1430
|
+
"""
|
|
1431
|
+
Places surface species concentration sensors at every link/pipe in the network
|
|
1432
|
+
for every surface species.
|
|
1433
|
+
"""
|
|
1434
|
+
self.set_bulk_species_node_sensors({species_id: self.__sensor_config.links
|
|
1435
|
+
for species_id in
|
|
1436
|
+
self.__sensor_config.surface_species})
|
|
1437
|
+
|
|
1438
|
+
def place_sensors_everywhere(self) -> None:
|
|
1439
|
+
"""
|
|
1440
|
+
Places sensors everywhere -- i.e. every possible quantity is monitored
|
|
1441
|
+
at every position in the network.
|
|
1442
|
+
"""
|
|
1443
|
+
self.__sensor_config.place_sensors_everywhere()
|
|
1444
|
+
|
|
1199
1445
|
def __prepare_simulation(self) -> None:
|
|
1200
1446
|
self.__adapt_to_network_changes()
|
|
1201
1447
|
|
|
@@ -1234,6 +1480,9 @@ class ScenarioSimulator():
|
|
|
1234
1480
|
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
1235
1481
|
Quality simulation results as SCADA data.
|
|
1236
1482
|
"""
|
|
1483
|
+
if self.__running_simulation is True:
|
|
1484
|
+
raise RuntimeError("A simulation is already running.")
|
|
1485
|
+
|
|
1237
1486
|
if self.__f_msx_in is None:
|
|
1238
1487
|
raise ValueError("No .msx file specified")
|
|
1239
1488
|
|
|
@@ -1298,6 +1547,9 @@ class ScenarioSimulator():
|
|
|
1298
1547
|
Generator containing the current EPANET-MSX simulation results as SCADA data
|
|
1299
1548
|
(i.e. species concentrations).
|
|
1300
1549
|
"""
|
|
1550
|
+
if self.__running_simulation is True:
|
|
1551
|
+
raise RuntimeError("A simulation is already running.")
|
|
1552
|
+
|
|
1301
1553
|
if self.__f_msx_in is None:
|
|
1302
1554
|
raise ValueError("No .msx file specified")
|
|
1303
1555
|
|
|
@@ -1314,6 +1566,8 @@ class ScenarioSimulator():
|
|
|
1314
1566
|
|
|
1315
1567
|
self.epanet_api.initializeMSXQualityAnalysis(ToolkitConstants.EN_NOSAVE)
|
|
1316
1568
|
|
|
1569
|
+
self.__running_simulation = True
|
|
1570
|
+
|
|
1317
1571
|
bulk_species_idx = self.epanet_api.getMSXSpeciesIndex(self.__sensor_config.bulk_species)
|
|
1318
1572
|
surface_species_idx = self.epanet_api.getMSXSpeciesIndex(
|
|
1319
1573
|
self.__sensor_config.surface_species)
|
|
@@ -1406,6 +1660,7 @@ class ScenarioSimulator():
|
|
|
1406
1660
|
|
|
1407
1661
|
# Run step-by-step simulation
|
|
1408
1662
|
tleft = 1
|
|
1663
|
+
total_time = 0
|
|
1409
1664
|
while tleft > 0:
|
|
1410
1665
|
if support_abort is True: # Can the simulation be aborted? If so, handle it.
|
|
1411
1666
|
abort = yield
|
|
@@ -1448,6 +1703,8 @@ class ScenarioSimulator():
|
|
|
1448
1703
|
sensor_noise=self.__sensor_noise,
|
|
1449
1704
|
frozen_sensor_config=frozen_sensor_config)
|
|
1450
1705
|
|
|
1706
|
+
self.__running_simulation = False
|
|
1707
|
+
|
|
1451
1708
|
def run_basic_quality_simulation(self, hyd_file_in: str, verbose: bool = False,
|
|
1452
1709
|
frozen_sensor_config: bool = False) -> ScadaData:
|
|
1453
1710
|
"""
|
|
@@ -1473,6 +1730,9 @@ class ScenarioSimulator():
|
|
|
1473
1730
|
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
1474
1731
|
Quality simulation results as SCADA data.
|
|
1475
1732
|
"""
|
|
1733
|
+
if self.__running_simulation is True:
|
|
1734
|
+
raise RuntimeError("A simulation is already running.")
|
|
1735
|
+
|
|
1476
1736
|
result = None
|
|
1477
1737
|
|
|
1478
1738
|
# Run simulation step-by-step
|
|
@@ -1533,6 +1793,9 @@ class ScenarioSimulator():
|
|
|
1533
1793
|
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
1534
1794
|
Generator with the current simulation results/states as SCADA data.
|
|
1535
1795
|
"""
|
|
1796
|
+
if self.__running_simulation is True:
|
|
1797
|
+
raise RuntimeError("A simulation is already running.")
|
|
1798
|
+
|
|
1536
1799
|
requested_time_step = self.epanet_api.getTimeHydraulicStep()
|
|
1537
1800
|
reporting_time_start = self.epanet_api.getTimeReportingStart()
|
|
1538
1801
|
reporting_time_step = self.epanet_api.getTimeReportingStep()
|
|
@@ -1626,6 +1889,9 @@ class ScenarioSimulator():
|
|
|
1626
1889
|
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
1627
1890
|
Simulation results as SCADA data (i.e. sensor readings).
|
|
1628
1891
|
"""
|
|
1892
|
+
if self.__running_simulation is True:
|
|
1893
|
+
raise RuntimeError("A simulation is already running.")
|
|
1894
|
+
|
|
1629
1895
|
self.__adapt_to_network_changes()
|
|
1630
1896
|
|
|
1631
1897
|
result = None
|
|
@@ -1720,10 +1986,15 @@ class ScenarioSimulator():
|
|
|
1720
1986
|
Generator with the current simulation results/states as SCADA data
|
|
1721
1987
|
(i.e. sensor readings).
|
|
1722
1988
|
"""
|
|
1989
|
+
if self.__running_simulation is True:
|
|
1990
|
+
raise RuntimeError("A simulation is already running.")
|
|
1991
|
+
|
|
1723
1992
|
self.__adapt_to_network_changes()
|
|
1724
1993
|
|
|
1725
1994
|
self.__prepare_simulation()
|
|
1726
1995
|
|
|
1996
|
+
self.__running_simulation = True
|
|
1997
|
+
|
|
1727
1998
|
self.epanet_api.openHydraulicAnalysis()
|
|
1728
1999
|
self.epanet_api.openQualityAnalysis()
|
|
1729
2000
|
self.epanet_api.initializeHydraulicAnalysis(ToolkitConstants.EN_SAVE)
|
|
@@ -1745,11 +2016,6 @@ class ScenarioSimulator():
|
|
|
1745
2016
|
tstep = 1
|
|
1746
2017
|
first_itr = True
|
|
1747
2018
|
while tstep > 0:
|
|
1748
|
-
if support_abort is True: # Can the simulation be aborted? If so, handle it.
|
|
1749
|
-
abort = yield
|
|
1750
|
-
if abort is not False:
|
|
1751
|
-
break
|
|
1752
|
-
|
|
1753
2019
|
if first_itr is True: # Fix current time in the first iteration
|
|
1754
2020
|
tstep = 0
|
|
1755
2021
|
first_itr = False
|
|
@@ -1781,8 +2047,8 @@ class ScenarioSimulator():
|
|
|
1781
2047
|
tanks_volume_data = self.epanet_api.getNodeTankVolume().reshape(1, -1)
|
|
1782
2048
|
|
|
1783
2049
|
pump_idx = self.epanet_api.getLinkPumpIndex()
|
|
1784
|
-
|
|
1785
|
-
|
|
2050
|
+
pumps_energy_usage_data = self.epanet_api.getLinkEnergy(pump_idx).reshape(1, -1)
|
|
2051
|
+
pumps_efficiency_data = self.epanet_api.getLinkPumpEfficiency().reshape(1, -1)
|
|
1786
2052
|
|
|
1787
2053
|
link_valve_idx = self.epanet_api.getLinkValveIndex()
|
|
1788
2054
|
valves_state_data = self.epanet_api.getLinkStatus(link_valve_idx).reshape(1, -1)
|
|
@@ -1796,8 +2062,8 @@ class ScenarioSimulator():
|
|
|
1796
2062
|
pumps_state_data_raw=pumps_state_data,
|
|
1797
2063
|
valves_state_data_raw=valves_state_data,
|
|
1798
2064
|
tanks_volume_data_raw=tanks_volume_data,
|
|
1799
|
-
|
|
1800
|
-
|
|
2065
|
+
pumps_energy_usage_data_raw=pumps_energy_usage_data,
|
|
2066
|
+
pumps_efficiency_data_raw=pumps_efficiency_data,
|
|
1801
2067
|
sensor_readings_time=np.array([total_time]),
|
|
1802
2068
|
sensor_reading_events=self.__sensor_reading_events,
|
|
1803
2069
|
sensor_noise=self.__sensor_noise,
|
|
@@ -1806,19 +2072,26 @@ class ScenarioSimulator():
|
|
|
1806
2072
|
# Yield results in a regular time interval only!
|
|
1807
2073
|
if total_time % reporting_time_step == 0 and total_time >= reporting_time_start:
|
|
1808
2074
|
if return_as_dict is True:
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2075
|
+
data = {"pressure_data_raw": pressure_data,
|
|
2076
|
+
"flow_data_raw": flow_data,
|
|
2077
|
+
"demand_data_raw": demand_data,
|
|
2078
|
+
"node_quality_data_raw": quality_node_data,
|
|
2079
|
+
"link_quality_data_raw": quality_link_data,
|
|
2080
|
+
"pumps_state_data_raw": pumps_state_data,
|
|
2081
|
+
"valves_state_data_raw": valves_state_data,
|
|
2082
|
+
"tanks_volume_data_raw": tanks_volume_data,
|
|
2083
|
+
"pumps_energy_usage_data_raw": pumps_energy_usage_data,
|
|
2084
|
+
"pumps_efficiency_data_raw": pumps_efficiency_data,
|
|
2085
|
+
"sensor_readings_time": np.array([total_time])}
|
|
1820
2086
|
else:
|
|
1821
|
-
|
|
2087
|
+
data = scada_data
|
|
2088
|
+
|
|
2089
|
+
if support_abort is True: # Can the simulation be aborted? If so, handle it.
|
|
2090
|
+
abort = yield
|
|
2091
|
+
if abort is not False:
|
|
2092
|
+
break
|
|
2093
|
+
|
|
2094
|
+
yield data
|
|
1822
2095
|
|
|
1823
2096
|
# Apply control modules
|
|
1824
2097
|
for control in self.__controls:
|
|
@@ -1831,9 +2104,12 @@ class ScenarioSimulator():
|
|
|
1831
2104
|
self.epanet_api.closeQualityAnalysis()
|
|
1832
2105
|
self.epanet_api.closeHydraulicAnalysis()
|
|
1833
2106
|
|
|
2107
|
+
self.__running_simulation = False
|
|
2108
|
+
|
|
1834
2109
|
if hyd_export is not None:
|
|
1835
2110
|
self.epanet_api.saveHydraulicFile(hyd_export)
|
|
1836
2111
|
except Exception as ex:
|
|
2112
|
+
self.__running_simulation = False
|
|
1837
2113
|
raise ex
|
|
1838
2114
|
|
|
1839
2115
|
def set_model_uncertainty(self, model_uncertainty: ModelUncertainty) -> None:
|
|
@@ -1845,6 +2121,9 @@ class ScenarioSimulator():
|
|
|
1845
2121
|
model_uncertainty : :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`
|
|
1846
2122
|
Model uncertainty specifications.
|
|
1847
2123
|
"""
|
|
2124
|
+
if self.__running_simulation is True:
|
|
2125
|
+
raise RuntimeError("Can not set uncertainties when simulation is running.")
|
|
2126
|
+
|
|
1848
2127
|
self.__adapt_to_network_changes()
|
|
1849
2128
|
|
|
1850
2129
|
if not isinstance(model_uncertainty, ModelUncertainty):
|
|
@@ -1863,6 +2142,9 @@ class ScenarioSimulator():
|
|
|
1863
2142
|
sensor_noise : :class:`~epyt_flow.uncertainties.sensor_noise.SensorNoise`
|
|
1864
2143
|
Sensor noise specification.
|
|
1865
2144
|
"""
|
|
2145
|
+
if self.__running_simulation is True:
|
|
2146
|
+
raise RuntimeError("Can not set sensor noise when simulation is running.")
|
|
2147
|
+
|
|
1866
2148
|
self.__adapt_to_network_changes()
|
|
1867
2149
|
|
|
1868
2150
|
if not isinstance(sensor_noise, SensorNoise):
|
|
@@ -1945,8 +2227,35 @@ class ScenarioSimulator():
|
|
|
1945
2227
|
|
|
1946
2228
|
The default is None.
|
|
1947
2229
|
"""
|
|
2230
|
+
if self.__running_simulation is True:
|
|
2231
|
+
raise RuntimeError("Can not change general parameters when simulation is running.")
|
|
2232
|
+
|
|
1948
2233
|
self.__adapt_to_network_changes()
|
|
1949
2234
|
|
|
2235
|
+
if flow_units_id is not None:
|
|
2236
|
+
if flow_units_id == ToolkitConstants.EN_CFS:
|
|
2237
|
+
self.epanet_api.setFlowUnitsCFS()
|
|
2238
|
+
elif flow_units_id == ToolkitConstants.EN_GPM:
|
|
2239
|
+
self.epanet_api.setFlowUnitsGPM()
|
|
2240
|
+
elif flow_units_id == ToolkitConstants.EN_MGD:
|
|
2241
|
+
self.epanet_api.setFlowUnitsMGD()
|
|
2242
|
+
elif flow_units_id == ToolkitConstants.EN_IMGD:
|
|
2243
|
+
self.epanet_api.setFlowUnitsIMGD()
|
|
2244
|
+
elif flow_units_id == ToolkitConstants.EN_AFD:
|
|
2245
|
+
self.epanet_api.setFlowUnitsAFD()
|
|
2246
|
+
elif flow_units_id == ToolkitConstants.EN_LPS:
|
|
2247
|
+
self.epanet_api.setFlowUnitsLPS()
|
|
2248
|
+
elif flow_units_id == ToolkitConstants.EN_LPM:
|
|
2249
|
+
self.epanet_api.setFlowUnitsLPM()
|
|
2250
|
+
elif flow_units_id == ToolkitConstants.EN_MLD:
|
|
2251
|
+
self.epanet_api.setFlowUnitsMLD()
|
|
2252
|
+
elif flow_units_id == ToolkitConstants.EN_CMH:
|
|
2253
|
+
self.epanet_api.setFlowUnitsCMH()
|
|
2254
|
+
elif flow_units_id == ToolkitConstants.EN_CMD:
|
|
2255
|
+
self.epanet_api.setFlowUnitsCMD()
|
|
2256
|
+
else:
|
|
2257
|
+
raise ValueError(f"Unknown flow units '{flow_units_id}'")
|
|
2258
|
+
|
|
1950
2259
|
if demand_model is not None:
|
|
1951
2260
|
self.epanet_api.setDemandModel(demand_model["type"], demand_model["pressure_min"],
|
|
1952
2261
|
demand_model["pressure_required"],
|
|
@@ -1991,30 +2300,6 @@ class ScenarioSimulator():
|
|
|
1991
2300
|
"greater than the hydraulic time step")
|
|
1992
2301
|
self.epanet_api.setTimeQualityStep(quality_time_step)
|
|
1993
2302
|
|
|
1994
|
-
if flow_units_id is not None:
|
|
1995
|
-
if flow_units_id == ToolkitConstants.EN_CFS:
|
|
1996
|
-
self.epanet_api.setFlowUnitsCFS()
|
|
1997
|
-
elif flow_units_id == ToolkitConstants.EN_GPM:
|
|
1998
|
-
self.epanet_api.setFlowUnitsGPM()
|
|
1999
|
-
elif flow_units_id == ToolkitConstants.EN_MGD:
|
|
2000
|
-
self.epanet_api.setFlowUnitsMGD()
|
|
2001
|
-
elif flow_units_id == ToolkitConstants.EN_IMGD:
|
|
2002
|
-
self.epanet_api.setFlowUnitsIMGD()
|
|
2003
|
-
elif flow_units_id == ToolkitConstants.EN_AFD:
|
|
2004
|
-
self.epanet_api.setFlowUnitsAFD()
|
|
2005
|
-
elif flow_units_id == ToolkitConstants.EN_LPS:
|
|
2006
|
-
self.epanet_api.setFlowUnitsLPS()
|
|
2007
|
-
elif flow_units_id == ToolkitConstants.EN_LPM:
|
|
2008
|
-
self.epanet_api.setFlowUnitsLPM()
|
|
2009
|
-
elif flow_units_id == ToolkitConstants.EN_MLD:
|
|
2010
|
-
self.epanet_api.setFlowUnitsMLD()
|
|
2011
|
-
elif flow_units_id == ToolkitConstants.EN_CMH:
|
|
2012
|
-
self.epanet_api.setFlowUnitsCMH()
|
|
2013
|
-
elif flow_units_id == ToolkitConstants.EN_CMD:
|
|
2014
|
-
self.epanet_api.setFlowUnitsCMD()
|
|
2015
|
-
else:
|
|
2016
|
-
raise ValueError(f"Unknown flow units '{flow_units_id}'")
|
|
2017
|
-
|
|
2018
2303
|
if quality_model is not None:
|
|
2019
2304
|
if quality_model["type"] == "NONE":
|
|
2020
2305
|
self.epanet_api.setQualityType("none")
|
|
@@ -2067,6 +2352,9 @@ class ScenarioSimulator():
|
|
|
2067
2352
|
Sets water age analysis -- i.e. estimates the water age (in hours) at
|
|
2068
2353
|
all places in the network.
|
|
2069
2354
|
"""
|
|
2355
|
+
if self.__running_simulation is True:
|
|
2356
|
+
raise RuntimeError("Can not change general parameters when simulation is running.")
|
|
2357
|
+
|
|
2070
2358
|
self.__adapt_to_network_changes()
|
|
2071
2359
|
|
|
2072
2360
|
self.__warn_if_quality_set()
|
|
@@ -2095,6 +2383,9 @@ class ScenarioSimulator():
|
|
|
2095
2383
|
|
|
2096
2384
|
The default is MASS_UNIT_MG.
|
|
2097
2385
|
"""
|
|
2386
|
+
if self.__running_simulation is True:
|
|
2387
|
+
raise RuntimeError("Can not change general parameters when simulation is running.")
|
|
2388
|
+
|
|
2098
2389
|
self.__adapt_to_network_changes()
|
|
2099
2390
|
|
|
2100
2391
|
self.__warn_if_quality_set()
|
|
@@ -2139,6 +2430,9 @@ class ScenarioSimulator():
|
|
|
2139
2430
|
|
|
2140
2431
|
The default is 1.
|
|
2141
2432
|
"""
|
|
2433
|
+
if self.__running_simulation is True:
|
|
2434
|
+
raise RuntimeError("Can not change general parameters when simulation is running.")
|
|
2435
|
+
|
|
2142
2436
|
self.__adapt_to_network_changes()
|
|
2143
2437
|
|
|
2144
2438
|
if self.epanet_api.getQualityInfo().QualityCode != ToolkitConstants.EN_CHEM:
|
|
@@ -2175,6 +2469,9 @@ class ScenarioSimulator():
|
|
|
2175
2469
|
trace_node_id : `str`
|
|
2176
2470
|
ID of the node traced in the source tracing analysis.
|
|
2177
2471
|
"""
|
|
2472
|
+
if self.__running_simulation is True:
|
|
2473
|
+
raise RuntimeError("Can not change general parameters when simulation is running.")
|
|
2474
|
+
|
|
2178
2475
|
self.__adapt_to_network_changes()
|
|
2179
2476
|
|
|
2180
2477
|
if trace_node_id not in self.__sensor_config.nodes:
|