epyt-flow 0.13.1__py3-none-any.whl → 0.14.1__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.
Files changed (63) hide show
  1. epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +40 -8
  2. epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +3 -3
  3. epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +24 -7
  4. epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +726 -374
  5. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +128 -32
  6. epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +7 -1
  7. epyt_flow/EPANET/EPANET/SRC_engines/flowbalance.c +186 -0
  8. epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +40 -14
  9. epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -177
  10. epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -28
  11. epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +192 -40
  12. epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +101 -46
  13. epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +85 -24
  14. epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +29 -63
  15. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +70 -37
  16. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +408 -234
  17. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +87 -37
  18. epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +153 -79
  19. epyt_flow/EPANET/EPANET/SRC_engines/input1.c +59 -94
  20. epyt_flow/EPANET/EPANET/SRC_engines/input2.c +73 -202
  21. epyt_flow/EPANET/EPANET/SRC_engines/input3.c +446 -351
  22. epyt_flow/EPANET/EPANET/SRC_engines/leakage.c +527 -0
  23. epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +8 -4
  24. epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +23 -23
  25. epyt_flow/EPANET/EPANET/SRC_engines/output.c +5 -4
  26. epyt_flow/EPANET/EPANET/SRC_engines/project.c +407 -75
  27. epyt_flow/EPANET/EPANET/SRC_engines/quality.c +12 -2
  28. epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +70 -13
  29. epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +7 -5
  30. epyt_flow/EPANET/EPANET/SRC_engines/report.c +88 -20
  31. epyt_flow/EPANET/EPANET/SRC_engines/rules.c +144 -6
  32. epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +19 -19
  33. epyt_flow/EPANET/EPANET/SRC_engines/text.h +16 -5
  34. epyt_flow/EPANET/EPANET/SRC_engines/types.h +73 -19
  35. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.c +59 -0
  36. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.h +38 -0
  37. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.c +92 -0
  38. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.h +39 -0
  39. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.c +212 -0
  40. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.h +81 -0
  41. epyt_flow/EPANET/EPANET/SRC_engines/validate.c +408 -0
  42. epyt_flow/EPANET/compile_linux.sh +1 -1
  43. epyt_flow/EPANET/compile_macos.sh +2 -2
  44. epyt_flow/VERSION +1 -1
  45. epyt_flow/__init__.py +1 -1
  46. epyt_flow/gym/scenario_control_env.py +26 -3
  47. epyt_flow/simulation/backend/my_epyt.py +58 -13
  48. epyt_flow/simulation/events/quality_events.py +6 -6
  49. epyt_flow/simulation/events/sensor_faults.py +24 -24
  50. epyt_flow/simulation/events/system_event.py +3 -3
  51. epyt_flow/simulation/scada/scada_data.py +10 -14
  52. epyt_flow/simulation/scenario_simulator.py +100 -20
  53. epyt_flow/topology.py +8 -1
  54. epyt_flow/uncertainty/model_uncertainty.py +292 -150
  55. epyt_flow/uncertainty/uncertainties.py +2 -2
  56. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/METADATA +4 -4
  57. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/RECORD +60 -54
  58. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/WHEEL +1 -1
  59. epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +0 -18
  60. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +0 -131
  61. epyt_flow/EPANET/EPANET/SRC_engines/main.c +0 -93
  62. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/licenses/LICENSE +0 -0
  63. {epyt_flow-0.13.1.dist-info → epyt_flow-0.14.1.dist-info}/top_level.txt +0 -0
@@ -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,16 @@ 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 False:
1970
+ self._model_uncertainty.apply(self.epanet_api)
1971
+ self.__uncertainties_applied = True
1972
+ elif self.__uncertainties_applied is True and reapply_uncertainties is True:
1973
+ self._model_uncertainty.undo(self.epanet_api)
1974
+ self._model_uncertainty.apply(self.epanet_api)
1951
1975
 
1952
1976
  for event in self._system_events:
1953
1977
  event.reset()
@@ -1958,7 +1982,8 @@ class ScenarioSimulator():
1958
1982
 
1959
1983
  def run_advanced_quality_simulation(self, hyd_file_in: str, verbose: bool = False,
1960
1984
  frozen_sensor_config: bool = False,
1961
- use_quality_time_step_as_reporting_time_step: bool = False
1985
+ use_quality_time_step_as_reporting_time_step: bool = False,
1986
+ reapply_uncertainties: bool = False
1962
1987
  ) -> ScadaData:
1963
1988
  """
1964
1989
  Runs an advanced quality analysis using EPANET-MSX.
@@ -1983,6 +2008,10 @@ class ScenarioSimulator():
1983
2008
  As a consequence, the simualtion results can not be merged
1984
2009
  with the hydraulic simulation.
1985
2010
 
2011
+ The default is False.
2012
+ reapply_uncertainties: `bool`, optional
2013
+ If True, the uncertainties are re-applied on the original properties.
2014
+
1986
2015
  The default is False.
1987
2016
 
1988
2017
  Returns
@@ -2004,7 +2033,8 @@ class ScenarioSimulator():
2004
2033
  return_as_dict=True,
2005
2034
  frozen_sensor_config=frozen_sensor_config,
2006
2035
  use_quality_time_step_as_reporting_time_step=
2007
- use_quality_time_step_as_reporting_time_step):
2036
+ use_quality_time_step_as_reporting_time_step,
2037
+ reapply_uncertainties=reapply_uncertainties):
2008
2038
  if result is None:
2009
2039
  result = {}
2010
2040
  for data_type, data in scada_data.items():
@@ -2032,6 +2062,7 @@ class ScenarioSimulator():
2032
2062
  return_as_dict: bool = False,
2033
2063
  frozen_sensor_config: bool = False,
2034
2064
  use_quality_time_step_as_reporting_time_step: bool = False,
2065
+ reapply_uncertainties: bool = False
2035
2066
  ) -> Generator[Union[tuple[ScadaData, bool], tuple[dict, bool]], bool, None]:
2036
2067
  """
2037
2068
  Runs an advanced quality analysis using EPANET-MSX.
@@ -2060,6 +2091,10 @@ class ScenarioSimulator():
2060
2091
  As a consequence, the simualtion results can not be merged
2061
2092
  with the hydraulic simulation.
2062
2093
 
2094
+ The default is False.
2095
+ reapply_uncertainties : `bool`, optional
2096
+ If True, the uncertainties are re-applied on the original properties.
2097
+
2063
2098
  The default is False.
2064
2099
 
2065
2100
  Returns
@@ -2074,6 +2109,8 @@ class ScenarioSimulator():
2074
2109
  if self.__f_msx_in is None:
2075
2110
  raise ValueError("No .msx file specified")
2076
2111
 
2112
+ self._prepare_simulation(reapply_uncertainties)
2113
+
2077
2114
  # Load pre-computed hydraulics
2078
2115
  self.epanet_api.useMSXHydraulicFile(hyd_file_in)
2079
2116
 
@@ -2171,12 +2208,14 @@ class ScenarioSimulator():
2171
2208
  pass
2172
2209
 
2173
2210
  if reporting_time_start == 0:
2211
+ msx_error_code = self.epanet_api.msx.get_last_error_code()
2212
+
2174
2213
  if return_as_dict is True:
2175
2214
  data = {"bulk_species_node_concentration_raw": bulk_species_node_concentrations,
2176
2215
  "bulk_species_link_concentration_raw": bulk_species_link_concentrations,
2177
2216
  "surface_species_concentration_raw": surface_species_concentrations,
2178
2217
  "sensor_readings_time": np.array([0]),
2179
- "warnings_code": np.array([0]) # TODO: Replace with MSX error
2218
+ "warnings_code": np.array([msx_error_code])
2180
2219
  }
2181
2220
  else:
2182
2221
  data = ScadaData(network_topo=network_topo, sensor_config=self._sensor_config,
@@ -2184,7 +2223,7 @@ class ScenarioSimulator():
2184
2223
  bulk_species_link_concentration_raw=bulk_species_link_concentrations,
2185
2224
  surface_species_concentration_raw=surface_species_concentrations,
2186
2225
  sensor_readings_time=np.array([0]),
2187
- warnings_code=np.array([0]), # TODO: Replace with MSX error
2226
+ warnings_code=np.array([msx_error_code]),
2188
2227
  sensor_reading_events=self._sensor_reading_events,
2189
2228
  sensor_noise=self._sensor_noise,
2190
2229
  frozen_sensor_config=frozen_sensor_config)
@@ -2199,9 +2238,13 @@ class ScenarioSimulator():
2199
2238
  # Run step-by-step simulation
2200
2239
  tleft = 1
2201
2240
  total_time = 0
2241
+ last_msx_error_code = 0
2202
2242
  while tleft > 0:
2203
2243
  # Compute current time step
2204
2244
  total_time, tleft = self.epanet_api.stepMSXQualityAnalysisTimeLeft()
2245
+ msx_error_code = self.epanet_api.msx.get_last_error_code()
2246
+ if last_msx_error_code == 0:
2247
+ last_msx_error_code = msx_error_code
2205
2248
 
2206
2249
  # Fetch data at regular time intervals
2207
2250
  if total_time % hyd_time_step == 0:
@@ -2223,7 +2266,7 @@ class ScenarioSimulator():
2223
2266
  bulk_species_link_concentrations,
2224
2267
  "surface_species_concentration_raw": surface_species_concentrations,
2225
2268
  "sensor_readings_time": np.array([total_time]),
2226
- "warnings_code": np.array([0]), # TODO: Replace with MSX error
2269
+ "warnings_code": np.array([last_msx_error_code]),
2227
2270
  }
2228
2271
  else:
2229
2272
  data = ScadaData(network_topo=network_topo,
@@ -2235,7 +2278,7 @@ class ScenarioSimulator():
2235
2278
  surface_species_concentration_raw=
2236
2279
  surface_species_concentrations,
2237
2280
  sensor_readings_time=np.array([total_time]),
2238
- warnings_code=np.array([0]), # TODO: Replace with MSX error
2281
+ warnings_code=np.array([last_msx_error_code]),
2239
2282
  sensor_reading_events=self._sensor_reading_events,
2240
2283
  sensor_noise=self._sensor_noise,
2241
2284
  frozen_sensor_config=frozen_sensor_config)
@@ -2388,6 +2431,7 @@ class ScenarioSimulator():
2388
2431
  total_time = 0
2389
2432
  tstep = 1
2390
2433
  first_itr = True
2434
+ last_error_code = 0
2391
2435
  while tstep > 0:
2392
2436
  if first_itr is True: # Fix current time in the first iteration
2393
2437
  tstep = 0
@@ -2406,6 +2450,8 @@ class ScenarioSimulator():
2406
2450
 
2407
2451
  # Fetch data
2408
2452
  error_code = self.epanet_api.get_last_error_code()
2453
+ if last_error_code == 0:
2454
+ last_error_code = error_code
2409
2455
  quality_node_data = self.epanet_api.getNodeActualQuality().reshape(1, -1)
2410
2456
  quality_link_data = self.epanet_api.getLinkActualQuality().reshape(1, -1)
2411
2457
 
@@ -2415,14 +2461,14 @@ class ScenarioSimulator():
2415
2461
  data = {"node_quality_data_raw": quality_node_data,
2416
2462
  "link_quality_data_raw": quality_link_data,
2417
2463
  "sensor_readings_time": np.array([total_time]),
2418
- "warnings_code": np.array([error_code])}
2464
+ "warnings_code": np.array([last_error_code])}
2419
2465
  else:
2420
2466
  data = ScadaData(network_topo=network_topo,
2421
2467
  sensor_config=self._sensor_config,
2422
2468
  node_quality_data_raw=quality_node_data,
2423
2469
  link_quality_data_raw=quality_link_data,
2424
2470
  sensor_readings_time=np.array([total_time]),
2425
- warnings_code=np.array([error_code]),
2471
+ warnings_code=np.array([last_error_code]),
2426
2472
  sensor_reading_events=self._sensor_reading_events,
2427
2473
  sensor_noise=self._sensor_noise,
2428
2474
  frozen_sensor_config=frozen_sensor_config)
@@ -2436,11 +2482,15 @@ class ScenarioSimulator():
2436
2482
 
2437
2483
  # Next
2438
2484
  tstep = self.epanet_api.api.ENstepQ()
2485
+ error_code = self.epanet_api.get_last_error_code()
2486
+ if last_error_code == 0:
2487
+ last_error_code = error_code
2439
2488
 
2440
2489
  self.epanet_api.closeQualityAnalysis()
2441
2490
 
2442
2491
  def run_hydraulic_simulation(self, hyd_export: str = None, verbose: bool = False,
2443
- frozen_sensor_config: bool = False) -> ScadaData:
2492
+ frozen_sensor_config: bool = False,
2493
+ reapply_uncertainties: bool = False) -> ScadaData:
2444
2494
  """
2445
2495
  Runs the hydraulic simulation of this scenario (incl. basic quality if set).
2446
2496
 
@@ -2463,6 +2513,10 @@ class ScenarioSimulator():
2463
2513
  If True, the sensor config can not be changed and only the required sensor nodes/links
2464
2514
  will be stored -- this usually leads to a significant reduction in memory consumption.
2465
2515
 
2516
+ The default is False.
2517
+ reapply_uncertainties : `bool`, optional
2518
+ If True, the uncertainties are re-applied on the original properties.
2519
+
2466
2520
  The default is False.
2467
2521
 
2468
2522
  Returns
@@ -2482,7 +2536,8 @@ class ScenarioSimulator():
2482
2536
  for scada_data, _ in gen(hyd_export=hyd_export,
2483
2537
  verbose=verbose,
2484
2538
  return_as_dict=True,
2485
- frozen_sensor_config=frozen_sensor_config):
2539
+ frozen_sensor_config=frozen_sensor_config,
2540
+ reapply_uncertainties=reapply_uncertainties):
2486
2541
  if result is None:
2487
2542
  result = {}
2488
2543
  for data_type, data in scada_data.items():
@@ -2507,6 +2562,7 @@ class ScenarioSimulator():
2507
2562
  support_abort: bool = False,
2508
2563
  return_as_dict: bool = False,
2509
2564
  frozen_sensor_config: bool = False,
2565
+ reapply_uncertainties: bool = False
2510
2566
  ) -> Generator[Union[tuple[ScadaData, bool], tuple[dict, bool]], bool, None]:
2511
2567
  """
2512
2568
  Runs the hydraulic simulation of this scenario (incl. basic quality if set) and
@@ -2542,6 +2598,10 @@ class ScenarioSimulator():
2542
2598
  If True, the sensor config can not be changed and only the required sensor nodes/links
2543
2599
  will be stored -- this usually leads to a significant reduction in memory consumption.
2544
2600
 
2601
+ The default is False.
2602
+ reapply_uncertainties : `bool`, optional
2603
+ If True, the uncertainties are re-applied on the original properties.
2604
+
2545
2605
  The default is False.
2546
2606
 
2547
2607
  Returns
@@ -2555,7 +2615,7 @@ class ScenarioSimulator():
2555
2615
 
2556
2616
  self._adapt_to_network_changes()
2557
2617
 
2558
- self._prepare_simulation()
2618
+ self._prepare_simulation(reapply_uncertainties)
2559
2619
 
2560
2620
  self.__running_simulation = True
2561
2621
 
@@ -2582,6 +2642,7 @@ class ScenarioSimulator():
2582
2642
  total_time = 0
2583
2643
  tstep = 1
2584
2644
  first_itr = True
2645
+ last_error_code = 0
2585
2646
  while tstep > 0:
2586
2647
  if first_itr is True: # Fix current time in the first iteration
2587
2648
  tstep = 0
@@ -2606,6 +2667,8 @@ class ScenarioSimulator():
2606
2667
  if error_code == 0:
2607
2668
  error_code = self.epanet_api.get_last_error_code()
2608
2669
  total_time = t
2670
+ if last_error_code == 0:
2671
+ last_error_code = error_code
2609
2672
 
2610
2673
  # Fetch data
2611
2674
  pressure_data = self.epanet_api.getNodePressure().reshape(1, -1)
@@ -2636,7 +2699,7 @@ class ScenarioSimulator():
2636
2699
  pumps_energy_usage_data_raw=pumps_energy_usage_data,
2637
2700
  pumps_efficiency_data_raw=pumps_efficiency_data,
2638
2701
  sensor_readings_time=np.array([total_time]),
2639
- warnings_code=np.array([error_code]),
2702
+ warnings_code=np.array([last_error_code]),
2640
2703
  sensor_reading_events=self._sensor_reading_events,
2641
2704
  sensor_noise=self._sensor_noise,
2642
2705
  frozen_sensor_config=frozen_sensor_config)
@@ -2655,10 +2718,12 @@ class ScenarioSimulator():
2655
2718
  "pumps_energy_usage_data_raw": pumps_energy_usage_data,
2656
2719
  "pumps_efficiency_data_raw": pumps_efficiency_data,
2657
2720
  "sensor_readings_time": np.array([total_time]),
2658
- "warnings_code": np.array([error_code])}
2721
+ "warnings_code": np.array([last_error_code])}
2659
2722
  else:
2660
2723
  data = scada_data
2661
2724
 
2725
+ last_error_code = 0
2726
+
2662
2727
  if support_abort is True: # Can the simulation be aborted? If so, handle it.
2663
2728
  abort = yield
2664
2729
  if abort is True:
@@ -2672,7 +2737,14 @@ class ScenarioSimulator():
2672
2737
 
2673
2738
  # Next
2674
2739
  tstep = self.epanet_api.api.ENnextH()
2740
+ error_code = self.epanet_api.get_last_error_code()
2741
+ if last_error_code == 0:
2742
+ last_error_code = error_code
2743
+
2675
2744
  self.epanet_api.api.ENnextQ()
2745
+ error_code = self.epanet_api.get_last_error_code()
2746
+ if last_error_code == 0:
2747
+ last_error_code = error_code
2676
2748
 
2677
2749
  self.epanet_api.api.ENcloseQ()
2678
2750
  self.epanet_api.api.ENcloseH()
@@ -2686,7 +2758,8 @@ class ScenarioSimulator():
2686
2758
  raise ex
2687
2759
 
2688
2760
  def run_simulation(self, hyd_export: str = None, verbose: bool = False,
2689
- frozen_sensor_config: bool = False) -> ScadaData:
2761
+ frozen_sensor_config: bool = False,
2762
+ reapply_uncertainties: bool = False) -> ScadaData:
2690
2763
  """
2691
2764
  Runs the simulation of this scenario.
2692
2765
 
@@ -2707,6 +2780,10 @@ class ScenarioSimulator():
2707
2780
  If True, the sensor config can not be changed and only the required sensor nodes/links
2708
2781
  will be stored -- this usually leads to a significant reduction in memory consumption.
2709
2782
 
2783
+ The default is False.
2784
+ reapply_uncertainties: `bool`, optional
2785
+ If True, the uncertainties are re-applied on the original properties.
2786
+
2710
2787
  The default is False.
2711
2788
 
2712
2789
  Returns
@@ -2727,14 +2804,16 @@ class ScenarioSimulator():
2727
2804
 
2728
2805
  # Run hydraulic simulation step-by-step
2729
2806
  result = self.run_hydraulic_simulation(hyd_export=hyd_export, verbose=verbose,
2730
- frozen_sensor_config=frozen_sensor_config)
2807
+ frozen_sensor_config=frozen_sensor_config,
2808
+ reapply_uncertainties=reapply_uncertainties)
2731
2809
 
2732
2810
  # If necessary, run advanced quality simulation utilizing the computed hydraulics
2733
2811
  if self.f_msx_in is not None:
2734
2812
  gen = self.run_advanced_quality_simulation
2735
2813
  result_msx = gen(hyd_file_in=hyd_export,
2736
2814
  verbose=verbose,
2737
- frozen_sensor_config=frozen_sensor_config)
2815
+ frozen_sensor_config=frozen_sensor_config,
2816
+ reapply_uncertainties=reapply_uncertainties)
2738
2817
  result.join(result_msx)
2739
2818
 
2740
2819
  if hyd_export_old is not None:
@@ -2768,6 +2847,7 @@ class ScenarioSimulator():
2768
2847
  f"'{type(model_uncertainty)}'")
2769
2848
 
2770
2849
  self._model_uncertainty = model_uncertainty
2850
+ self.__uncertainties_applied = False
2771
2851
 
2772
2852
  def set_sensor_noise(self, sensor_noise: SensorNoise) -> None:
2773
2853
  """
epyt_flow/topology.py CHANGED
@@ -500,8 +500,15 @@ class NetworkTopology(nx.Graph, JsonSerializable):
500
500
  raise TypeError("Can not compare 'NetworkTopology' instance to " +
501
501
  f"'{type(other)}' instance")
502
502
 
503
+ adj_matrix = self.get_adj_matrix()
504
+ other_adj_matrix = other.get_adj_matrix()
505
+
503
506
  return super().__eq__(other) and \
504
- self.get_all_nodes() == other.get_all_nodes() \
507
+ self.name == other.name \
508
+ and not np.any(adj_matrix.data != other_adj_matrix.data) \
509
+ and not np.any(adj_matrix.indices != other_adj_matrix.indices) \
510
+ and not np.any(adj_matrix.indptr != other_adj_matrix.indptr) \
511
+ and self.get_all_nodes() == other.get_all_nodes() \
505
512
  and all(link_a[0] == link_b[0] and link_a[1] == link_b[1]
506
513
  for link_a, link_b in zip(self.get_all_links(), other.get_all_links())) \
507
514
  and self.__units == other.units \