epyt-flow 0.13.0__py3-none-any.whl → 0.14.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.
@@ -1,4 +1,4 @@
1
1
  #!/bin/bash
2
2
  mkdir -p "../customlibs/"
3
- gcc-12 -w -O3 -march=native -dynamiclib -fPIC -install_name libepanet2_2.dylib -o "../customlibs/libepanet2_2.dylib" EPANET/SRC_engines/*.c -IEPANET/SRC_engines/include -lc -lm -pthread
4
- gcc-12 -w -O3 -march=native -dynamiclib -fPIC -install_name libepanetmsx2_2_0.dylib -o "../customlibs/libepanetmsx2_2_0.dylib" -fopenmp -Depanetmsx_EXPORTS -IEPANET-MSX/Src/include -IEPANET/SRC_engines/include EPANET-MSX/Src/*.c -L'../customlibs' -lepanet2_2 -lm -lgomp -lpthread
3
+ gcc-15 -w -O3 -march=native -dynamiclib -fPIC -install_name libepanet2_2.dylib -o "../customlibs/libepanet2_2.dylib" EPANET/SRC_engines/*.c -IEPANET/SRC_engines/include -lc -lm -pthread
4
+ gcc-15 -w -O3 -march=native -dynamiclib -fPIC -install_name libepanetmsx2_2_0.dylib -o "../customlibs/libepanetmsx2_2_0.dylib" -fopenmp -Depanetmsx_EXPORTS -IEPANET-MSX/Src/include -IEPANET/SRC_engines/include EPANET-MSX/Src/*.c -L'../customlibs' -lepanet2_2 -lm -lgomp -lpthread
epyt_flow/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.0
1
+ 0.14.0
epyt_flow/__init__.py CHANGED
@@ -39,4 +39,4 @@ def compile_libraries_unix(lib_epanet_name: str, compile_script_name: str,
39
39
  if sys.platform.startswith("linux"):
40
40
  compile_libraries_unix("libepanet2_2.so", "compile_linux.sh")
41
41
  elif sys.platform.startswith("darwin"):
42
- compile_libraries_unix("libepanet2_2.dylib", "compile_macos.sh", gcc_name="gcc-12")
42
+ compile_libraries_unix("libepanet2_2.dylib", "compile_macos.sh", gcc_name="gcc-15")
@@ -1,6 +1,7 @@
1
1
  """
2
2
  This module contains a wrapper for EPyT that allows a better error/warning handling.
3
3
  """
4
+ import warnings
4
5
  from ctypes import byref, create_string_buffer
5
6
  from epyt import epanet
6
7
  from epyt.epanet import epanetapi, epanetmsxapi
@@ -30,6 +31,10 @@ class EPyT(epanet):
30
31
  self.LibEPANETpath = self.api.LibEPANETpath
31
32
  self.LibEPANET = self.api.LibEPANET
32
33
 
34
+ def _logFunctionError(self, function_name):
35
+ # Do not print warnings.
36
+ pass
37
+
33
38
  def loadMSXFile(self, msxname, customMSXlib=None, ignore_properties=False):
34
39
  super().loadMSXFile(msxname, customMSXlib, ignore_properties)
35
40
 
@@ -40,17 +45,25 @@ class EPyT(epanet):
40
45
  msxrealfile=self.MSXFile)
41
46
  return self.msx
42
47
 
43
- def set_error_handling(self, raise_exception_on_error: bool) -> None:
48
+ def set_error_handling(self, raise_exception_on_error: bool, warn_on_error: bool,
49
+ ignore_error_codes: list[int]) -> None:
44
50
  """
45
51
  Specifies the behavior in the case of an error/warning --
46
- i.e. should an exception be raised or not?
52
+ i.e. should an exception or warning be raised or not?
47
53
 
48
54
  Parameters
49
55
  ----------
50
56
  raise_exception_on_error : `bool`
51
57
  True if an exception should be raise, False otherwise.
58
+ warn_on_error : `bool`
59
+ True if a warning should be generated, False otherwise.
60
+ ignore_error_codes : `list[int]`
61
+ List of error codes that should be ignored -- i.e., no exception or
62
+ warning will be generated.
52
63
  """
53
- self.api.set_error_handling(raise_exception_on_error)
64
+ self.api.set_error_handling(raise_exception_on_error, warn_on_error, ignore_error_codes)
65
+ if self.msx is not None:
66
+ self.msx.set_error_handling(raise_exception_on_error, warn_on_error, ignore_error_codes)
54
67
 
55
68
  def was_last_func_successful(self) -> bool:
56
69
  """
@@ -96,24 +109,39 @@ class MyEpanetMsxAPI(epanetmsxapi):
96
109
  Wrapper for the `epyt.epanet.epanetmsxapi <https://epanet-python-toolkit-epyt.readthedocs.io/en/latest/api.html#epyt.epanet.epanetmsxapi>`_
97
110
  class adding error/warning storage functionalities.
98
111
  """
99
- def __init__(self, raise_on_error: bool = True, **kwds):
112
+ def __init__(self, raise_on_error: bool = True, warn_on_error: bool = False,
113
+ ignore_error_codes: list[int] = [], **kwds):
100
114
  self.__raise_on_error = raise_on_error
115
+ self.__warn_on_error = warn_on_error
116
+ self.__ignore_error_codes = ignore_error_codes
101
117
  self.__last_error_code = None
102
118
  self.__last_error_desc = None
103
119
 
104
120
  super().__init__(**kwds)
105
121
 
106
- def set_error_handling(self, raise_on_error: bool) -> None:
122
+ def _logFunctionError(self, function_name):
123
+ # Do not print warnings.
124
+ pass
125
+
126
+ def set_error_handling(self, raise_on_error: bool, warn_on_error: bool,
127
+ ignore_error_codes: list[int] = []) -> None:
107
128
  """
108
129
  Specifies the behavior in the case of an error/warning --
109
- i.e. should an exception be raised or not?
130
+ i.e. should an exception or warning be raised or not?
110
131
 
111
132
  Parameters
112
133
  ----------
113
134
  raise_exception_on_error : `bool`
114
135
  True if an exception should be raise, False otherwise.
136
+ warn_on_error : `bool`
137
+ True if a warning should be generated, False otherwise.
138
+ ignore_error_codes : `list[int]`
139
+ List of error codes that should be ignored -- i.e., no exception or
140
+ warning will be generated.
115
141
  """
116
142
  self.__raise_on_error = raise_on_error
143
+ self.__warn_on_error = warn_on_error
144
+ self.__ignore_error_codes = ignore_error_codes
117
145
 
118
146
  def get_last_error_desc(self) -> str:
119
147
  """
@@ -161,8 +189,11 @@ class MyEpanetMsxAPI(epanetmsxapi):
161
189
  self.__last_error_code = err_code
162
190
  self.__last_error_desc = error_desc.value.decode()
163
191
 
164
- if self.__raise_on_error:
165
- raise RuntimeError(self.__last_error_desc)
192
+ if self.__last_error_code not in self.__ignore_error_codes:
193
+ if self.__warn_on_error:
194
+ warnings.warn(self.__last_error_desc, RuntimeWarning)
195
+ if self.__raise_on_error:
196
+ raise RuntimeError(self.__last_error_desc)
166
197
 
167
198
  def MSXopen(self, msxfile, msxrealfile):
168
199
  self._reset_error()
@@ -286,24 +317,35 @@ class MyEpanetAPI(epanetapi):
286
317
  Wrapper for the `epyt.epanet.epanetapi <https://epanet-python-toolkit-epyt.readthedocs.io/en/latest/api.html#epyt.epanet.epanetapi>`_
287
318
  class adding error/warning storage functionalities.
288
319
  """
289
- def __init__(self, raise_on_error: bool = True, **kwds):
320
+ def __init__(self, raise_on_error: bool = True, warn_on_error: bool = False,
321
+ ignore_error_codes: list[int] = [], **kwds):
290
322
  self.__raise_on_error = raise_on_error
323
+ self.__warn_on_error = warn_on_error
324
+ self.__ignore_error_codes = ignore_error_codes
291
325
  self.__last_error_code = None
292
326
  self.__last_error_desc = None
293
327
 
294
328
  super().__init__(**kwds)
295
329
 
296
- def set_error_handling(self, raise_on_error: bool) -> None:
330
+ def set_error_handling(self, raise_on_error: bool, warn_on_error: bool,
331
+ ignore_error_codes: list[int] = []) -> None:
297
332
  """
298
333
  Specifies the behavior in the case of an error/warning --
299
- i.e. should an exception be raised or not?
334
+ i.e. should an exception or warning be raised or not?
300
335
 
301
336
  Parameters
302
337
  ----------
303
338
  raise_exception_on_error : `bool`
304
339
  True if an exception should be raise, False otherwise.
340
+ warn_on_error : `bool`
341
+ True if a warning should be generated, False otherwise.
342
+ ignore_error_codes : `list[int]`
343
+ List of error codes that should be ignored -- i.e., no exception or
344
+ warning will be generated.
305
345
  """
306
346
  self.__raise_on_error = raise_on_error
347
+ self.__warn_on_error = warn_on_error
348
+ self.__ignore_error_codes = ignore_error_codes
307
349
 
308
350
  def get_last_error_desc(self) -> str:
309
351
  """
@@ -353,8 +395,11 @@ class MyEpanetAPI(epanetapi):
353
395
  self.__last_error_code = self.errcode
354
396
  self.__last_error_desc = error_desc.value.decode()
355
397
 
356
- if self.__raise_on_error:
357
- raise RuntimeError(self.__last_error_desc)
398
+ if self.__last_error_code not in self.__ignore_error_codes:
399
+ if self.__warn_on_error:
400
+ warnings.warn(self.__last_error_desc, RuntimeWarning)
401
+ if self.__raise_on_error:
402
+ raise RuntimeError(self.__last_error_desc)
358
403
 
359
404
  def ENepanet(self, inpfile="", rptfile="", binfile=""):
360
405
  self._reset_error()
@@ -167,9 +167,7 @@ class ScadaData(Serializable):
167
167
  raise TypeError("'sensor_readings_time' must be an instance of 'numpy.ndarray' " +
168
168
  f"but not of '{type(sensor_readings_time)}'")
169
169
  if warnings_code is None:
170
- warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
171
- " -- support of such old files will be removed in the next release!",
172
- DeprecationWarning)
170
+ warnings_code = [0] * len(sensor_readings_time)
173
171
  else:
174
172
  if not isinstance(warnings_code, np.ndarray):
175
173
  raise TypeError("'warnings_code' must be an instance of 'numpy.ndarray' " +
@@ -376,6 +374,8 @@ class ScadaData(Serializable):
376
374
  self.__sensor_readings = None
377
375
  self.__frozen_sensor_config = frozen_sensor_config
378
376
  self.__sensor_readings_time = sensor_readings_time
377
+ self.__sensor_readings_time_to_idx = {time: idx for idx, time in
378
+ enumerate(self.__sensor_readings_time)}
379
379
 
380
380
  if self.__frozen_sensor_config is False:
381
381
  self.__pressure_data_raw = pressure_data_raw
@@ -1748,8 +1748,8 @@ class ScadaData(Serializable):
1748
1748
  else:
1749
1749
  end_time = self.__sensor_readings_time[-1]
1750
1750
 
1751
- start_idx = self.__sensor_readings_time.tolist().index(start_time)
1752
- end_idx = self.__sensor_readings_time.tolist().index(end_time) + 1
1751
+ start_idx = self.__sensor_readings_time_to_idx[start_time]
1752
+ end_idx = self.__sensor_readings_time_to_idx[end_time] + 1
1753
1753
 
1754
1754
  pressure_data_raw = None
1755
1755
  if self.__pressure_data_raw is not None:
@@ -2375,11 +2375,10 @@ class ScadaData(Serializable):
2375
2375
  mask = np.zeros(len(self.__sensor_config.nodes))
2376
2376
  node_features = np.array([[default_missing_value] * len(self.__sensor_config.nodes)
2377
2377
  for _ in range(len(self.__sensor_readings_time))])
2378
- nodes_id = self.__network_topo.get_all_nodes()
2379
2378
 
2380
2379
  pressure_readings = self.get_data_pressures()
2381
2380
  for pressures_idx, node_id in enumerate(self.__sensor_config.pressure_sensors):
2382
- idx = nodes_id.index(node_id)
2381
+ idx = self.__sensor_config.map_node_id_to_idx(node_id)
2383
2382
  node_features[:, idx] = pressure_readings[:, pressures_idx]
2384
2383
  mask[idx] = 1
2385
2384
 
@@ -2617,11 +2616,10 @@ class ScadaData(Serializable):
2617
2616
  mask = np.zeros(len(self.__sensor_config.nodes))
2618
2617
  node_features = np.array([[default_missing_value] * len(self.__sensor_config.nodes)
2619
2618
  for _ in range(len(self.__sensor_readings_time))])
2620
- nodes_id = self.__network_topo.get_all_nodes()
2621
2619
 
2622
2620
  demand_readings = self.get_data_demands()
2623
2621
  for demands_idx, node_id in enumerate(self.__sensor_config.demand_sensors):
2624
- idx = nodes_id.index(node_id)
2622
+ idx = self.__sensor_config.map_node_id_to_idx(node_id)
2625
2623
  node_features[:, idx] = demand_readings[:, demands_idx]
2626
2624
  mask[idx] = 1
2627
2625
 
@@ -2737,11 +2735,10 @@ class ScadaData(Serializable):
2737
2735
  mask = np.zeros(len(self.__sensor_config.nodes))
2738
2736
  node_features = np.array([[default_missing_value] * len(self.__sensor_config.nodes)
2739
2737
  for _ in range(len(self.__sensor_readings_time))])
2740
- nodes_id = self.__network_topo.get_all_nodes()
2741
2738
 
2742
2739
  node_quality_readings = self.get_data_nodes_quality()
2743
2740
  for quality_idx, node_id in enumerate(self.__sensor_config.quality_node_sensors):
2744
- idx = nodes_id.index(node_id)
2741
+ idx = self.__sensor_config.map_node_id_to_idx(node_id)
2745
2742
  node_features[:, idx] = node_quality_readings[:, quality_idx]
2746
2743
  mask[idx] = 1
2747
2744
 
@@ -3752,7 +3749,6 @@ class ScadaData(Serializable):
3752
3749
  masks = []
3753
3750
  results = []
3754
3751
 
3755
- all_nodes_id = self.__network_topo.get_all_nodes()
3756
3752
  bulk_species_sensor_locations = self.__sensor_config.bulk_species_node_sensors
3757
3753
 
3758
3754
  for species_id, nodes_id in bulk_species_sensor_locations.items():
@@ -3762,7 +3758,7 @@ class ScadaData(Serializable):
3762
3758
 
3763
3759
  sensor_readings = self.get_data_bulk_species_node_concentration({species_id: nodes_id})
3764
3760
  for sensor_readings_idx, node_id in enumerate(nodes_id):
3765
- idx = all_nodes_id.index(node_id)
3761
+ idx = self.__sensor_config.map_node_id_to_idx(node_id)
3766
3762
  node_features[:, idx] = sensor_readings[:, sensor_readings_idx]
3767
3763
  mask[idx] = 1
3768
3764
 
@@ -59,6 +59,20 @@ class ScenarioSimulator():
59
59
  If True, EPyT is verbose and might print messages from time to time.
60
60
 
61
61
  The default is False.
62
+ raise_exception_on_error : `bool`, optional
63
+ If True, an exception is raised whenever an error occurs in EPANET or EPANET-MSX.
64
+
65
+ The default is False.
66
+ warn_on_error : `bool`, optional
67
+ If True, a warning is generated whenever an error occurs in EPANET or EPANET-MSX.
68
+
69
+ The default is True.
70
+ ignore_error_codes : `list[int]`, optional
71
+ List of error codes that should be ignored -- i.e., no exception or
72
+ warning will be generated.
73
+ However, error codes will still be included in the SCADA data.
74
+
75
+ The default is [].
62
76
 
63
77
  Attributes
64
78
  ----------
@@ -83,7 +97,9 @@ class ScenarioSimulator():
83
97
  """
84
98
 
85
99
  def __init__(self, f_inp_in: str = None, f_msx_in: str = None,
86
- scenario_config: ScenarioConfig = None, epanet_verbose: bool = False):
100
+ scenario_config: ScenarioConfig = None, epanet_verbose: bool = False,
101
+ raise_exception_on_error: bool = False, warn_on_error: bool = True,
102
+ ignore_error_codes: list[int] = []):
87
103
  if f_msx_in is not None and f_inp_in is None:
88
104
  raise ValueError("'f_inp_in' must be set if 'f_msx_in' is set.")
89
105
  if f_inp_in is None and scenario_config is None:
@@ -118,6 +134,7 @@ class ScenarioSimulator():
118
134
  self._system_events = []
119
135
  self._sensor_reading_events = []
120
136
  self.__running_simulation = False
137
+ self.__uncertainties_applied = False
121
138
 
122
139
  # Check availability of custom EPANET libraries
123
140
  custom_epanet_lib = None
@@ -176,7 +193,9 @@ class ScenarioSimulator():
176
193
  self.epanet_api.loadMSXFile(my_f_msx_in, customMSXlib=custom_epanetmsx_lib)
177
194
 
178
195
  # Do not raise exceptions in the case of EPANET warnings and errors
179
- self.epanet_api.set_error_handling(False)
196
+ self.epanet_api.set_error_handling(raise_exception_on_error=raise_exception_on_error,
197
+ warn_on_error=warn_on_error,
198
+ ignore_error_codes=ignore_error_codes)
180
199
 
181
200
  # Parse and initialize scenario
182
201
  self._simple_controls = self._parse_simple_control_rules()
@@ -1943,11 +1962,13 @@ class ScenarioSimulator():
1943
1962
  """
1944
1963
  self._sensor_config.place_sensors_everywhere()
1945
1964
 
1946
- def _prepare_simulation(self) -> None:
1965
+ def _prepare_simulation(self, reapply_uncertainties: bool = False) -> None:
1947
1966
  self._adapt_to_network_changes()
1948
1967
 
1949
1968
  if self._model_uncertainty is not None:
1950
- self._model_uncertainty.apply(self.epanet_api)
1969
+ if self.__uncertainties_applied is True and reapply_uncertainties is True:
1970
+ self._model_uncertainty.apply(self.epanet_api)
1971
+ self.__uncertainties_applied = True
1951
1972
 
1952
1973
  for event in self._system_events:
1953
1974
  event.reset()
@@ -1958,7 +1979,8 @@ class ScenarioSimulator():
1958
1979
 
1959
1980
  def run_advanced_quality_simulation(self, hyd_file_in: str, verbose: bool = False,
1960
1981
  frozen_sensor_config: bool = False,
1961
- use_quality_time_step_as_reporting_time_step: bool = False
1982
+ use_quality_time_step_as_reporting_time_step: bool = False,
1983
+ reapply_uncertainties: bool = False
1962
1984
  ) -> ScadaData:
1963
1985
  """
1964
1986
  Runs an advanced quality analysis using EPANET-MSX.
@@ -1983,6 +2005,10 @@ class ScenarioSimulator():
1983
2005
  As a consequence, the simualtion results can not be merged
1984
2006
  with the hydraulic simulation.
1985
2007
 
2008
+ The default is False.
2009
+ reapply_uncertainties: bool = False : `bool`, optional
2010
+ If True, the uncertainties are re-applied.
2011
+
1986
2012
  The default is False.
1987
2013
 
1988
2014
  Returns
@@ -2004,7 +2030,8 @@ class ScenarioSimulator():
2004
2030
  return_as_dict=True,
2005
2031
  frozen_sensor_config=frozen_sensor_config,
2006
2032
  use_quality_time_step_as_reporting_time_step=
2007
- use_quality_time_step_as_reporting_time_step):
2033
+ use_quality_time_step_as_reporting_time_step,
2034
+ reapply_uncertainties=reapply_uncertainties):
2008
2035
  if result is None:
2009
2036
  result = {}
2010
2037
  for data_type, data in scada_data.items():
@@ -2032,6 +2059,7 @@ class ScenarioSimulator():
2032
2059
  return_as_dict: bool = False,
2033
2060
  frozen_sensor_config: bool = False,
2034
2061
  use_quality_time_step_as_reporting_time_step: bool = False,
2062
+ reapply_uncertainties: bool = False
2035
2063
  ) -> Generator[Union[tuple[ScadaData, bool], tuple[dict, bool]], bool, None]:
2036
2064
  """
2037
2065
  Runs an advanced quality analysis using EPANET-MSX.
@@ -2060,6 +2088,10 @@ class ScenarioSimulator():
2060
2088
  As a consequence, the simualtion results can not be merged
2061
2089
  with the hydraulic simulation.
2062
2090
 
2091
+ The default is False.
2092
+ reapply_uncertainties: bool = False : `bool`, optional
2093
+ If True, the uncertainties are re-applied.
2094
+
2063
2095
  The default is False.
2064
2096
 
2065
2097
  Returns
@@ -2074,6 +2106,8 @@ class ScenarioSimulator():
2074
2106
  if self.__f_msx_in is None:
2075
2107
  raise ValueError("No .msx file specified")
2076
2108
 
2109
+ self._prepare_simulation(reapply_uncertainties)
2110
+
2077
2111
  # Load pre-computed hydraulics
2078
2112
  self.epanet_api.useMSXHydraulicFile(hyd_file_in)
2079
2113
 
@@ -2171,12 +2205,14 @@ class ScenarioSimulator():
2171
2205
  pass
2172
2206
 
2173
2207
  if reporting_time_start == 0:
2208
+ msx_error_code = self.epanet_api.msx.get_last_error_code()
2209
+
2174
2210
  if return_as_dict is True:
2175
2211
  data = {"bulk_species_node_concentration_raw": bulk_species_node_concentrations,
2176
2212
  "bulk_species_link_concentration_raw": bulk_species_link_concentrations,
2177
2213
  "surface_species_concentration_raw": surface_species_concentrations,
2178
2214
  "sensor_readings_time": np.array([0]),
2179
- "warnings_code": np.array([0]) # TODO: Replace with MSX error
2215
+ "warnings_code": np.array([msx_error_code])
2180
2216
  }
2181
2217
  else:
2182
2218
  data = ScadaData(network_topo=network_topo, sensor_config=self._sensor_config,
@@ -2184,7 +2220,7 @@ class ScenarioSimulator():
2184
2220
  bulk_species_link_concentration_raw=bulk_species_link_concentrations,
2185
2221
  surface_species_concentration_raw=surface_species_concentrations,
2186
2222
  sensor_readings_time=np.array([0]),
2187
- warnings_code=np.array([0]), # TODO: Replace with MSX error
2223
+ warnings_code=np.array([msx_error_code]),
2188
2224
  sensor_reading_events=self._sensor_reading_events,
2189
2225
  sensor_noise=self._sensor_noise,
2190
2226
  frozen_sensor_config=frozen_sensor_config)
@@ -2199,9 +2235,13 @@ class ScenarioSimulator():
2199
2235
  # Run step-by-step simulation
2200
2236
  tleft = 1
2201
2237
  total_time = 0
2238
+ last_msx_error_code = 0
2202
2239
  while tleft > 0:
2203
2240
  # Compute current time step
2204
2241
  total_time, tleft = self.epanet_api.stepMSXQualityAnalysisTimeLeft()
2242
+ msx_error_code = self.epanet_api.msx.get_last_error_code()
2243
+ if last_msx_error_code == 0:
2244
+ last_msx_error_code = msx_error_code
2205
2245
 
2206
2246
  # Fetch data at regular time intervals
2207
2247
  if total_time % hyd_time_step == 0:
@@ -2223,7 +2263,7 @@ class ScenarioSimulator():
2223
2263
  bulk_species_link_concentrations,
2224
2264
  "surface_species_concentration_raw": surface_species_concentrations,
2225
2265
  "sensor_readings_time": np.array([total_time]),
2226
- "warnings_code": np.array([0]), # TODO: Replace with MSX error
2266
+ "warnings_code": np.array([last_msx_error_code]),
2227
2267
  }
2228
2268
  else:
2229
2269
  data = ScadaData(network_topo=network_topo,
@@ -2235,7 +2275,7 @@ class ScenarioSimulator():
2235
2275
  surface_species_concentration_raw=
2236
2276
  surface_species_concentrations,
2237
2277
  sensor_readings_time=np.array([total_time]),
2238
- warnings_code=np.array([0]), # TODO: Replace with MSX error
2278
+ warnings_code=np.array([last_msx_error_code]),
2239
2279
  sensor_reading_events=self._sensor_reading_events,
2240
2280
  sensor_noise=self._sensor_noise,
2241
2281
  frozen_sensor_config=frozen_sensor_config)
@@ -2388,6 +2428,7 @@ class ScenarioSimulator():
2388
2428
  total_time = 0
2389
2429
  tstep = 1
2390
2430
  first_itr = True
2431
+ last_error_code = 0
2391
2432
  while tstep > 0:
2392
2433
  if first_itr is True: # Fix current time in the first iteration
2393
2434
  tstep = 0
@@ -2406,6 +2447,8 @@ class ScenarioSimulator():
2406
2447
 
2407
2448
  # Fetch data
2408
2449
  error_code = self.epanet_api.get_last_error_code()
2450
+ if last_error_code == 0:
2451
+ last_error_code = error_code
2409
2452
  quality_node_data = self.epanet_api.getNodeActualQuality().reshape(1, -1)
2410
2453
  quality_link_data = self.epanet_api.getLinkActualQuality().reshape(1, -1)
2411
2454
 
@@ -2415,14 +2458,14 @@ class ScenarioSimulator():
2415
2458
  data = {"node_quality_data_raw": quality_node_data,
2416
2459
  "link_quality_data_raw": quality_link_data,
2417
2460
  "sensor_readings_time": np.array([total_time]),
2418
- "warnings_code": np.array([error_code])}
2461
+ "warnings_code": np.array([last_error_code])}
2419
2462
  else:
2420
2463
  data = ScadaData(network_topo=network_topo,
2421
2464
  sensor_config=self._sensor_config,
2422
2465
  node_quality_data_raw=quality_node_data,
2423
2466
  link_quality_data_raw=quality_link_data,
2424
2467
  sensor_readings_time=np.array([total_time]),
2425
- warnings_code=np.array([error_code]),
2468
+ warnings_code=np.array([last_error_code]),
2426
2469
  sensor_reading_events=self._sensor_reading_events,
2427
2470
  sensor_noise=self._sensor_noise,
2428
2471
  frozen_sensor_config=frozen_sensor_config)
@@ -2432,15 +2475,19 @@ class ScenarioSimulator():
2432
2475
  if abort is True:
2433
2476
  break
2434
2477
 
2435
- yield (data, total_time < requested_total_time)
2478
+ yield (data, total_time >= requested_total_time)
2436
2479
 
2437
2480
  # Next
2438
2481
  tstep = self.epanet_api.api.ENstepQ()
2482
+ error_code = self.epanet_api.get_last_error_code()
2483
+ if last_error_code == 0:
2484
+ last_error_code = error_code
2439
2485
 
2440
2486
  self.epanet_api.closeQualityAnalysis()
2441
2487
 
2442
2488
  def run_hydraulic_simulation(self, hyd_export: str = None, verbose: bool = False,
2443
- frozen_sensor_config: bool = False) -> ScadaData:
2489
+ frozen_sensor_config: bool = False,
2490
+ reapply_uncertainties: bool = False) -> ScadaData:
2444
2491
  """
2445
2492
  Runs the hydraulic simulation of this scenario (incl. basic quality if set).
2446
2493
 
@@ -2463,6 +2510,10 @@ class ScenarioSimulator():
2463
2510
  If True, the sensor config can not be changed and only the required sensor nodes/links
2464
2511
  will be stored -- this usually leads to a significant reduction in memory consumption.
2465
2512
 
2513
+ The default is False.
2514
+ reapply_uncertainties: bool = False : `bool`, optional
2515
+ If True, the uncertainties are re-applied.
2516
+
2466
2517
  The default is False.
2467
2518
 
2468
2519
  Returns
@@ -2482,7 +2533,8 @@ class ScenarioSimulator():
2482
2533
  for scada_data, _ in gen(hyd_export=hyd_export,
2483
2534
  verbose=verbose,
2484
2535
  return_as_dict=True,
2485
- frozen_sensor_config=frozen_sensor_config):
2536
+ frozen_sensor_config=frozen_sensor_config,
2537
+ reapply_uncertainties=reapply_uncertainties):
2486
2538
  if result is None:
2487
2539
  result = {}
2488
2540
  for data_type, data in scada_data.items():
@@ -2507,6 +2559,7 @@ class ScenarioSimulator():
2507
2559
  support_abort: bool = False,
2508
2560
  return_as_dict: bool = False,
2509
2561
  frozen_sensor_config: bool = False,
2562
+ reapply_uncertainties: bool = False
2510
2563
  ) -> Generator[Union[tuple[ScadaData, bool], tuple[dict, bool]], bool, None]:
2511
2564
  """
2512
2565
  Runs the hydraulic simulation of this scenario (incl. basic quality if set) and
@@ -2542,6 +2595,10 @@ class ScenarioSimulator():
2542
2595
  If True, the sensor config can not be changed and only the required sensor nodes/links
2543
2596
  will be stored -- this usually leads to a significant reduction in memory consumption.
2544
2597
 
2598
+ The default is False.
2599
+ reapply_uncertainties: bool = False : `bool`, optional
2600
+ If True, the uncertainties are re-applied.
2601
+
2545
2602
  The default is False.
2546
2603
 
2547
2604
  Returns
@@ -2555,7 +2612,7 @@ class ScenarioSimulator():
2555
2612
 
2556
2613
  self._adapt_to_network_changes()
2557
2614
 
2558
- self._prepare_simulation()
2615
+ self._prepare_simulation(reapply_uncertainties)
2559
2616
 
2560
2617
  self.__running_simulation = True
2561
2618
 
@@ -2582,6 +2639,7 @@ class ScenarioSimulator():
2582
2639
  total_time = 0
2583
2640
  tstep = 1
2584
2641
  first_itr = True
2642
+ last_error_code = 0
2585
2643
  while tstep > 0:
2586
2644
  if first_itr is True: # Fix current time in the first iteration
2587
2645
  tstep = 0
@@ -2606,6 +2664,8 @@ class ScenarioSimulator():
2606
2664
  if error_code == 0:
2607
2665
  error_code = self.epanet_api.get_last_error_code()
2608
2666
  total_time = t
2667
+ if last_error_code == 0:
2668
+ last_error_code = error_code
2609
2669
 
2610
2670
  # Fetch data
2611
2671
  pressure_data = self.epanet_api.getNodePressure().reshape(1, -1)
@@ -2636,7 +2696,7 @@ class ScenarioSimulator():
2636
2696
  pumps_energy_usage_data_raw=pumps_energy_usage_data,
2637
2697
  pumps_efficiency_data_raw=pumps_efficiency_data,
2638
2698
  sensor_readings_time=np.array([total_time]),
2639
- warnings_code=np.array([error_code]),
2699
+ warnings_code=np.array([last_error_code]),
2640
2700
  sensor_reading_events=self._sensor_reading_events,
2641
2701
  sensor_noise=self._sensor_noise,
2642
2702
  frozen_sensor_config=frozen_sensor_config)
@@ -2655,16 +2715,18 @@ class ScenarioSimulator():
2655
2715
  "pumps_energy_usage_data_raw": pumps_energy_usage_data,
2656
2716
  "pumps_efficiency_data_raw": pumps_efficiency_data,
2657
2717
  "sensor_readings_time": np.array([total_time]),
2658
- "warnings_code": np.array([error_code])}
2718
+ "warnings_code": np.array([last_error_code])}
2659
2719
  else:
2660
2720
  data = scada_data
2661
2721
 
2722
+ last_error_code = 0
2723
+
2662
2724
  if support_abort is True: # Can the simulation be aborted? If so, handle it.
2663
2725
  abort = yield
2664
2726
  if abort is True:
2665
2727
  break
2666
2728
 
2667
- yield (data, total_time < requested_total_time)
2729
+ yield (data, total_time >= requested_total_time)
2668
2730
 
2669
2731
  # Apply control modules
2670
2732
  for control in self._custom_controls:
@@ -2672,7 +2734,14 @@ class ScenarioSimulator():
2672
2734
 
2673
2735
  # Next
2674
2736
  tstep = self.epanet_api.api.ENnextH()
2737
+ error_code = self.epanet_api.get_last_error_code()
2738
+ if last_error_code == 0:
2739
+ last_error_code = error_code
2740
+
2675
2741
  self.epanet_api.api.ENnextQ()
2742
+ error_code = self.epanet_api.get_last_error_code()
2743
+ if last_error_code == 0:
2744
+ last_error_code = error_code
2676
2745
 
2677
2746
  self.epanet_api.api.ENcloseQ()
2678
2747
  self.epanet_api.api.ENcloseH()
@@ -2686,7 +2755,8 @@ class ScenarioSimulator():
2686
2755
  raise ex
2687
2756
 
2688
2757
  def run_simulation(self, hyd_export: str = None, verbose: bool = False,
2689
- frozen_sensor_config: bool = False) -> ScadaData:
2758
+ frozen_sensor_config: bool = False,
2759
+ reapply_uncertainties: bool = False) -> ScadaData:
2690
2760
  """
2691
2761
  Runs the simulation of this scenario.
2692
2762
 
@@ -2707,6 +2777,10 @@ class ScenarioSimulator():
2707
2777
  If True, the sensor config can not be changed and only the required sensor nodes/links
2708
2778
  will be stored -- this usually leads to a significant reduction in memory consumption.
2709
2779
 
2780
+ The default is False.
2781
+ reapply_uncertainties: bool = False : `bool`, optional
2782
+ If True, the uncertainties are re-applied.
2783
+
2710
2784
  The default is False.
2711
2785
 
2712
2786
  Returns
@@ -2727,14 +2801,16 @@ class ScenarioSimulator():
2727
2801
 
2728
2802
  # Run hydraulic simulation step-by-step
2729
2803
  result = self.run_hydraulic_simulation(hyd_export=hyd_export, verbose=verbose,
2730
- frozen_sensor_config=frozen_sensor_config)
2804
+ frozen_sensor_config=frozen_sensor_config,
2805
+ reapply_uncertainties=reapply_uncertainties)
2731
2806
 
2732
2807
  # If necessary, run advanced quality simulation utilizing the computed hydraulics
2733
2808
  if self.f_msx_in is not None:
2734
2809
  gen = self.run_advanced_quality_simulation
2735
2810
  result_msx = gen(hyd_file_in=hyd_export,
2736
2811
  verbose=verbose,
2737
- frozen_sensor_config=frozen_sensor_config)
2812
+ frozen_sensor_config=frozen_sensor_config,
2813
+ reapply_uncertainties=reapply_uncertainties)
2738
2814
  result.join(result_msx)
2739
2815
 
2740
2816
  if hyd_export_old is not None:
@@ -2768,6 +2844,7 @@ class ScenarioSimulator():
2768
2844
  f"'{type(model_uncertainty)}'")
2769
2845
 
2770
2846
  self._model_uncertainty = model_uncertainty
2847
+ self.__uncertainties_applied = False
2771
2848
 
2772
2849
  def set_sensor_noise(self, sensor_noise: SensorNoise) -> None:
2773
2850
  """
@@ -126,9 +126,9 @@ class Uncertainty(ABC):
126
126
  Clipped data.
127
127
  """
128
128
  if self.__min_value is not None:
129
- data = np.min([data, self.__min_value])
129
+ data = np.max([data, self.__min_value])
130
130
  if self.__max_value is not None:
131
- data = np.max([data, self.__max_value])
131
+ data = np.min([data, self.__max_value])
132
132
 
133
133
  return data
134
134
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epyt-flow
3
- Version: 0.13.0
3
+ Version: 0.14.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-Expression: MIT
@@ -85,12 +85,12 @@ By this, we not only aim to achieve a better performance of the simulations but
85
85
  compatibility issues of pre-compiled binaries.
86
86
 
87
87
  #### Prerequisites for macOS users
88
- The "true" *gcc* compiler (version 12) is needed which is not the
88
+ The "true" *gcc* compiler (version 15) is needed which is not the
89
89
  *clang* compiler that is shipped with Xcode and is linked to gcc!
90
90
 
91
91
  The correct version of the "true" *gcc* can be installed via [brew](https://brew.sh/):
92
92
  ```
93
- brew install gcc@12
93
+ brew install gcc@15
94
94
  ```
95
95
 
96
96
  ### PyPI
@@ -1,10 +1,10 @@
1
- epyt_flow/VERSION,sha256=2EyeWWx9apTl90V5742JEqgHsNKFgkdJAK0137Pt_PQ,7
2
- epyt_flow/__init__.py,sha256=n-zI5G3DUofs_pNC5YNWopNPCPUtpL-jYi8RPH6eakw,1768
1
+ epyt_flow/VERSION,sha256=BlWCZVqs1vyD_3QqVxXAS7Slc5W_PuRVl5j6QsLORYk,7
2
+ epyt_flow/__init__.py,sha256=wktX10SXX9GEamDUWR-tcm-8T4d5s6j6aPYr_x4EpOc,1768
3
3
  epyt_flow/serialization.py,sha256=uGGN1iZ21ek1u6Xzs4z2xum5Qt8554Wem-wEMGEaa7I,14574
4
4
  epyt_flow/topology.py,sha256=Ih6m_tMT1JlPOJv8rHDVBtNayFOarPyAmCbMyojJtQg,26037
5
5
  epyt_flow/utils.py,sha256=R_IE6uuTF2MRjUkgWDEa3xOYk8iorieHCqqQ0tDUZ40,17476
6
6
  epyt_flow/EPANET/compile_linux.sh,sha256=wcrDyiB8NkivmaC-X9FI2WxhY3IJqDLiyIbVTv2XEPY,489
7
- epyt_flow/EPANET/compile_macos.sh,sha256=1K33-bPdgr01EIf87YUvmOFHXyOkBWI6mKXQ8x1Hzmo,504
7
+ epyt_flow/EPANET/compile_macos.sh,sha256=JfXkCDe5OHhNXgukU13_ViCsD7KPV8qClzSXGLH1HVk,504
8
8
  epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS,sha256=yie5yAsEEPY0984PmkSRUdqEU9rVvRSGGWmjxdwCYMU,925
9
9
  epyt_flow/EPANET/EPANET/SRC_engines/LICENSE,sha256=8SIIcPPO-ga2HotvptcK3uRccZOEGCeUOIU0Asiq7CU,1070
10
10
  epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt,sha256=7LWHGbghkYJb18wkIskUzYswRq0ZTMu_m6nV0IfvCOs,1005
@@ -108,10 +108,10 @@ epyt_flow/rest_api/scenario/uncertainty_handlers.py,sha256=Pdo2YmiawOqKXWcLs2P-l
108
108
  epyt_flow/simulation/__init__.py,sha256=nihvZ8O2rJjYQkv7JhtVMqNacO5bA38VtS8Y_0BWrVQ,129
109
109
  epyt_flow/simulation/parallel_simulation.py,sha256=ph4KXw9jCt-hiJFJbmC6fNvEsrbQoWV-tFKE5-qSfoQ,6523
110
110
  epyt_flow/simulation/scenario_config.py,sha256=uHHwwzCRwooVdODxDNoCOUgfrlol1K-TS8P8_Ja9Coc,30435
111
- epyt_flow/simulation/scenario_simulator.py,sha256=AhsMh3KcVRmhqqRgwH3WU26YqUvJuTZbNKxsRYwDukA,161461
111
+ epyt_flow/simulation/scenario_simulator.py,sha256=pEHpV_HJkgwPmnPlvBzJCAXaTNIBBRIVmTdkopcgxEc,165155
112
112
  epyt_flow/simulation/sensor_config.py,sha256=AGAIC5vqcu2UZsglSCwv2pvZ3E0kbTtdaqtFPEMBwSw,94262
113
113
  epyt_flow/simulation/backend/__init__.py,sha256=tkJb2NB_hruTv39sYHAq9ffazyA-UzZzFmiy2nYVTqA,23
114
- epyt_flow/simulation/backend/my_epyt.py,sha256=edkhDDOZBtYSpaQmnui_opdetebaS9ZzBwngnIpkc3s,32040
114
+ epyt_flow/simulation/backend/my_epyt.py,sha256=9zcHUo-shPuUNewsuxFpbBgtAbbTu7ppSaK6x4Xgt50,34414
115
115
  epyt_flow/simulation/events/__init__.py,sha256=gv8ZcvwjJN0Z5MwRXEOVFRNq4X5NPyyqXIQnhBxszQ0,215
116
116
  epyt_flow/simulation/events/actuator_events.py,sha256=gNOIZL9WNiIhEANi8yEyGxKx4vSMBZiRpiSJ4BbDL60,8171
117
117
  epyt_flow/simulation/events/event.py,sha256=kARPV20XCAl6zxnJwI9U7ICtZUPACO_rgAmtHm1mGCs,2603
@@ -124,19 +124,19 @@ epyt_flow/simulation/events/system_event.py,sha256=IGypfTL-miosmwKd4DGTYvByyBcl8
124
124
  epyt_flow/simulation/scada/__init__.py,sha256=pfJhg-tM5DaiZTXs0_1qJsY2R6Py_LwSz6BUFJexfQM,150
125
125
  epyt_flow/simulation/scada/complex_control.py,sha256=p2ehLyScJbj2jbyevXf7T3Ot8jnBYWgRR3Cz-m3boB0,22107
126
126
  epyt_flow/simulation/scada/custom_control.py,sha256=QzkKcn7t53GcTaRofRDbY-HErcDReMF4utWkck6bN60,4681
127
- epyt_flow/simulation/scada/scada_data.py,sha256=vzhTUoUIdmqTj9rP2w00q1dAaT90rY5vY2S6Sgo04jg,203434
127
+ epyt_flow/simulation/scada/scada_data.py,sha256=fzcy35rSwoTcIsZpda9JOU17SpR-cV4DZ1tb6qFGpEc,203267
128
128
  epyt_flow/simulation/scada/scada_data_export.py,sha256=WNAFn_WNfzYAEFbl2Al-cOIx-A0ozY4AI60-i_qEHdc,11643
129
129
  epyt_flow/simulation/scada/simple_control.py,sha256=wUsxsrgqmYxXR93_atcKbV9E-t3hgqj60ZeTvoBBnoQ,12215
130
130
  epyt_flow/uncertainty/__init__.py,sha256=ZRjuJL9rDpWVSdPwObPxFpEmMTcgAl3VmPOsS6cIyGg,89
131
131
  epyt_flow/uncertainty/model_uncertainty.py,sha256=Bfc9OgecJbX2qOiANhTSKdK_m95aRuXkIGyc22b8d30,41183
132
132
  epyt_flow/uncertainty/sensor_noise.py,sha256=-AnBfuW1VAx7Ya-q_gJ9bAr7Kx6pzP_y0PvNeuRjXIg,6477
133
- epyt_flow/uncertainty/uncertainties.py,sha256=5bZu1Zx8RZ6ESzJSvjtPogNkft3eemTojRsYdi_i1Qo,20495
133
+ epyt_flow/uncertainty/uncertainties.py,sha256=QBRbI3zIzkeFScyYD5Dy0TBxuL9jPV4SnVU8QwOrJq8,20495
134
134
  epyt_flow/uncertainty/utils.py,sha256=K-ZhyO6Bg7UmNPgpfND0JLa_wRwyrtUUgGTWyWwy-fo,8029
135
135
  epyt_flow/visualization/__init__.py,sha256=uQ7lO6AsgLc88X48Te3QhBQY-iDKGvdPtxKCl4b-Mfw,70
136
136
  epyt_flow/visualization/scenario_visualizer.py,sha256=vkP0J_OfjvePUz_HehXJkH4CGKR3chAHe_UTx4hQ6XI,57860
137
137
  epyt_flow/visualization/visualization_utils.py,sha256=l-EQ9UlY9MDBcOhK-uUZ9c-tX9emVqlo4xSqR-Sg17M,27525
138
- epyt_flow-0.13.0.dist-info/licenses/LICENSE,sha256=YRJC2kcAhMlpEeHwUF0lzE-GRXLFyAjXNErI8L5UwYk,1071
139
- epyt_flow-0.13.0.dist-info/METADATA,sha256=M-XauQzx0P0u0_vvNEMgsXybG5V64q2pLZTOkhfnzYU,9713
140
- epyt_flow-0.13.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
141
- epyt_flow-0.13.0.dist-info/top_level.txt,sha256=Wh_kd7TRL8ownCw3Y3dxx-9C0iTSk6wNauv_NX9JcrY,10
142
- epyt_flow-0.13.0.dist-info/RECORD,,
138
+ epyt_flow-0.14.0.dist-info/licenses/LICENSE,sha256=YRJC2kcAhMlpEeHwUF0lzE-GRXLFyAjXNErI8L5UwYk,1071
139
+ epyt_flow-0.14.0.dist-info/METADATA,sha256=M7BM9VPwrVrFRzuBSVAFnimiOoPsCgjbiGiAjeSFw-A,9713
140
+ epyt_flow-0.14.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
141
+ epyt_flow-0.14.0.dist-info/top_level.txt,sha256=Wh_kd7TRL8ownCw3Y3dxx-9C0iTSk6wNauv_NX9JcrY,10
142
+ epyt_flow-0.14.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5