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
@@ -119,6 +119,15 @@ class ModelUncertainty(JsonSerializable):
119
119
  Seed for the random number generator.
120
120
 
121
121
  Thed default is None.
122
+ cache_original : `bool`, optional
123
+ If True, all original properties are cached before the uncertainties are applied.
124
+ This is necessary if you have multiple simulation runs and you want to reset/re-apply
125
+ the uncertainties before each run.
126
+
127
+ You can set it to False if you do not and want to re-apply the uncertainties and
128
+ save some working memory.
129
+
130
+ The default is True.
122
131
  """
123
132
  def __init__(self, global_pipe_length_uncertainty: Optional[Uncertainty] = None,
124
133
  global_pipe_roughness_uncertainty: Optional[Uncertainty] = None,
@@ -138,7 +147,7 @@ class ModelUncertainty(JsonSerializable):
138
147
  local_parameters_uncertainty: Optional[dict[str, int, Uncertainty]] = None,
139
148
  local_patterns_uncertainty: Optional[dict[str, Uncertainty]] = None,
140
149
  local_msx_patterns_uncertainty: Optional[dict[str, Uncertainty]] = None,
141
- seed: Optional[int] = None,
150
+ seed: Optional[int] = None, cache_original: Optional[bool] = True,
142
151
  **kwds):
143
152
  if global_pipe_length_uncertainty is not None:
144
153
  if not isinstance(global_pipe_length_uncertainty, Uncertainty):
@@ -283,26 +292,42 @@ class ModelUncertainty(JsonSerializable):
283
292
  raise TypeError("'local_msx_patterns_uncertainty': " +
284
293
  "All keys must be instances of 'str' and all values must be " +
285
294
  "instances of 'epyt_flow.uncertainty.Uncertainty'")
286
-
287
- self.__global_pipe_length = global_pipe_length_uncertainty
288
- self.__global_pipe_roughness = global_pipe_roughness_uncertainty
289
- self.__global_pipe_diameter = global_pipe_diameter_uncertainty
290
- self.__global_base_demand = global_base_demand_uncertainty
291
- self.__global_demand_pattern = global_demand_pattern_uncertainty
292
- self.__global_elevation = global_elevation_uncertainty
293
- self.__global_constants = global_constants_uncertainty
294
- self.__global_parameters = global_parameters_uncertainty
295
- self.__local_pipe_length = local_pipe_length_uncertainty
296
- self.__local_pipe_roughness = local_pipe_roughness_uncertainty
297
- self.__local_pipe_diameter = local_pipe_diameter_uncertainty
298
- self.__local_base_demand = local_base_demand_uncertainty
299
- self.__local_demand_pattern = local_demand_pattern_uncertainty
300
- self.__local_elevation = local_elevation_uncertainty
301
- self.__local_constants = local_constants_uncertainty
302
- self.__local_parameters = local_parameters_uncertainty
303
- self.__local_patterns = local_patterns_uncertainty
304
- self.__local_msx_patterns = local_msx_patterns_uncertainty
295
+ if not isinstance(cache_original, bool):
296
+ raise TypeError("'cache_original' must be an instance of 'bool' " +
297
+ f"but not of '{type(cache_original)}'")
298
+
299
+ self._global_pipe_length = global_pipe_length_uncertainty
300
+ self._global_pipe_roughness = global_pipe_roughness_uncertainty
301
+ self._global_pipe_diameter = global_pipe_diameter_uncertainty
302
+ self._global_base_demand = global_base_demand_uncertainty
303
+ self._global_demand_pattern = global_demand_pattern_uncertainty
304
+ self._global_elevation = global_elevation_uncertainty
305
+ self._global_constants = global_constants_uncertainty
306
+ self._global_parameters = global_parameters_uncertainty
307
+ self._local_pipe_length = local_pipe_length_uncertainty
308
+ self._local_pipe_roughness = local_pipe_roughness_uncertainty
309
+ self._local_pipe_diameter = local_pipe_diameter_uncertainty
310
+ self._local_base_demand = local_base_demand_uncertainty
311
+ self._local_demand_pattern = local_demand_pattern_uncertainty
312
+ self._local_elevation = local_elevation_uncertainty
313
+ self._local_constants = local_constants_uncertainty
314
+ self._local_parameters = local_parameters_uncertainty
315
+ self._local_patterns = local_patterns_uncertainty
316
+ self._local_msx_patterns = local_msx_patterns_uncertainty
305
317
  self.__seed = seed
318
+ self.__cache_original = cache_original
319
+
320
+ self._cache_links_length = None
321
+ self._cache_links_diameter = None
322
+ self._cache_links_roughness_coeff = None
323
+ self._cache_nodes_base_demand = None
324
+ self._cache_nodes_demand_pattern = None
325
+ self._cache_nodes_elevation = None
326
+ self._cache_patterns = None
327
+ self._cache_msx_constants = None
328
+ self._cache_msx_links_parameters = None
329
+ self._cache_msx_tanks_parameters = None
330
+ self._cache_msx_patterns = None
306
331
 
307
332
  super().__init__(**kwds)
308
333
 
@@ -328,7 +353,7 @@ class ModelUncertainty(JsonSerializable):
328
353
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
329
354
  Global pipe length uncertainty.
330
355
  """
331
- return deepcopy(self.__global_pipe_length)
356
+ return deepcopy(self._global_pipe_length)
332
357
 
333
358
  @property
334
359
  def global_pipe_roughness(self) -> Uncertainty:
@@ -340,7 +365,7 @@ class ModelUncertainty(JsonSerializable):
340
365
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
341
366
  Global pipe roughness uncertainty.
342
367
  """
343
- return deepcopy(self.__global_pipe_roughness)
368
+ return deepcopy(self._global_pipe_roughness)
344
369
 
345
370
  @property
346
371
  def global_pipe_diameter(self) -> Uncertainty:
@@ -352,7 +377,7 @@ class ModelUncertainty(JsonSerializable):
352
377
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
353
378
  Global pipe diameter uncertainty.
354
379
  """
355
- return deepcopy(self.__global_pipe_diameter)
380
+ return deepcopy(self._global_pipe_diameter)
356
381
 
357
382
  @property
358
383
  def global_base_demand(self) -> Uncertainty:
@@ -364,7 +389,7 @@ class ModelUncertainty(JsonSerializable):
364
389
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
365
390
  Global base demand uncertainty.
366
391
  """
367
- return deepcopy(self.__global_base_demand)
392
+ return deepcopy(self._global_base_demand)
368
393
 
369
394
  @property
370
395
  def global_demand_pattern(self) -> Uncertainty:
@@ -376,7 +401,7 @@ class ModelUncertainty(JsonSerializable):
376
401
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
377
402
  Global demand pattern uncertainty.
378
403
  """
379
- return deepcopy(self.__global_demand_pattern)
404
+ return deepcopy(self._global_demand_pattern)
380
405
 
381
406
  @property
382
407
  def global_elevation(self) -> Uncertainty:
@@ -388,7 +413,7 @@ class ModelUncertainty(JsonSerializable):
388
413
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
389
414
  Global node elevation uncertainty.
390
415
  """
391
- return deepcopy(self.__global_elevation)
416
+ return deepcopy(self._global_elevation)
392
417
 
393
418
  @property
394
419
  def global_constants(self) -> Uncertainty:
@@ -400,7 +425,7 @@ class ModelUncertainty(JsonSerializable):
400
425
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
401
426
  Global MSX constant uncertainty.
402
427
  """
403
- return deepcopy(self.__global_constants)
428
+ return deepcopy(self._global_constants)
404
429
 
405
430
  @property
406
431
  def global_parameters(self) -> Uncertainty:
@@ -412,7 +437,7 @@ class ModelUncertainty(JsonSerializable):
412
437
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
413
438
  Global MSX parameter uncertainty.
414
439
  """
415
- return deepcopy(self.__global_parameters)
440
+ return deepcopy(self._global_parameters)
416
441
 
417
442
  @property
418
443
  def local_pipe_length(self) -> dict[str, Uncertainty]:
@@ -424,7 +449,7 @@ class ModelUncertainty(JsonSerializable):
424
449
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
425
450
  Local pipe length uncertainty.
426
451
  """
427
- return deepcopy(self.__local_pipe_length)
452
+ return deepcopy(self._local_pipe_length)
428
453
 
429
454
  @property
430
455
  def local_pipe_roughness(self) -> dict[str, Uncertainty]:
@@ -436,7 +461,7 @@ class ModelUncertainty(JsonSerializable):
436
461
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
437
462
  Local pipe roughness uncertainty.
438
463
  """
439
- return deepcopy(self.__local_pipe_roughness)
464
+ return deepcopy(self._local_pipe_roughness)
440
465
 
441
466
  @property
442
467
  def local_pipe_diameter(self) -> dict[str, Uncertainty]:
@@ -448,7 +473,7 @@ class ModelUncertainty(JsonSerializable):
448
473
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
449
474
  Local pipe diameter uncertainty.
450
475
  """
451
- return deepcopy(self.__local_pipe_diameter)
476
+ return deepcopy(self._local_pipe_diameter)
452
477
 
453
478
  @property
454
479
  def local_base_demand(self) -> dict[str, Uncertainty]:
@@ -460,7 +485,7 @@ class ModelUncertainty(JsonSerializable):
460
485
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
461
486
  Local base demand uncertainty.
462
487
  """
463
- return deepcopy(self.__local_base_demand)
488
+ return deepcopy(self._local_base_demand)
464
489
 
465
490
  @property
466
491
  def local_demand_pattern(self) -> dict[str, Uncertainty]:
@@ -472,7 +497,7 @@ class ModelUncertainty(JsonSerializable):
472
497
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
473
498
  Local demand pattern uncertainty.
474
499
  """
475
- return deepcopy(self.__local_demand_pattern)
500
+ return deepcopy(self._local_demand_pattern)
476
501
 
477
502
  @property
478
503
  def local_elevation(self) -> dict[str, Uncertainty]:
@@ -484,7 +509,7 @@ class ModelUncertainty(JsonSerializable):
484
509
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
485
510
  Local node elevation uncertainty.
486
511
  """
487
- return deepcopy(self.__local_elevation)
512
+ return deepcopy(self._local_elevation)
488
513
 
489
514
  @property
490
515
  def local_constants(self) -> dict[str, Uncertainty]:
@@ -496,7 +521,7 @@ class ModelUncertainty(JsonSerializable):
496
521
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
497
522
  Local MSX constant uncertainty.
498
523
  """
499
- return deepcopy(self.__local_constants)
524
+ return deepcopy(self._local_constants)
500
525
 
501
526
  @property
502
527
  def local_parameters(self) -> dict[tuple[str, int, str], Uncertainty]:
@@ -508,7 +533,7 @@ class ModelUncertainty(JsonSerializable):
508
533
  dict[tuple[str, int, str], :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
509
534
  Local MSX parameter uncertainty.
510
535
  """
511
- return deepcopy(self.__local_parameters)
536
+ return deepcopy(self._local_parameters)
512
537
 
513
538
  @property
514
539
  def local_patterns(self) -> dict[str, Uncertainty]:
@@ -520,7 +545,7 @@ class ModelUncertainty(JsonSerializable):
520
545
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
521
546
  Local EPANET patterns uncertainty.
522
547
  """
523
- return deepcopy(self.__local_patterns)
548
+ return deepcopy(self._local_patterns)
524
549
 
525
550
  @property
526
551
  def local_msx_patterns(self) -> dict[str, Uncertainty]:
@@ -532,27 +557,27 @@ class ModelUncertainty(JsonSerializable):
532
557
  dict[str, :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
533
558
  Local EPANET-MSX patterns uncertainty.
534
559
  """
535
- return deepcopy(self.__local_msx_patterns)
560
+ return deepcopy(self._local_msx_patterns)
536
561
 
537
562
  def get_attributes(self) -> dict:
538
- attribs = {"global_pipe_length_uncertainty": self.__global_pipe_length,
539
- "global_pipe_roughness_uncertainty": self.__global_pipe_roughness,
540
- "global_pipe_diameter_uncertainty": self.__global_pipe_diameter,
541
- "global_base_demand_uncertainty": self.__global_base_demand,
542
- "global_demand_pattern_uncertainty": self.__global_demand_pattern,
543
- "global_elevation_uncertainty": self.__global_elevation,
544
- "global_constants_uncertainty": self.__global_constants,
545
- "global_parameters_uncertainty": self.__global_parameters,
546
- "local_pipe_length_uncertainty": self.__local_pipe_length,
547
- "local_pipe_roughness_uncertainty": self.__local_pipe_roughness,
548
- "local_pipe_diameter_uncertainty": self.__local_pipe_diameter,
549
- "local_base_demand_uncertainty": self.__local_base_demand,
550
- "local_demand_pattern_uncertainty": self.__local_demand_pattern,
551
- "local_elevation_uncertainty": self.__local_elevation,
552
- "local_constants_uncertainty": self.__local_constants,
553
- "local_parameters_uncertainty": self.__local_parameters,
554
- "local_patterns_uncertainty": self.__local_patterns,
555
- "local_msx_patterns_uncertainty": self.__local_msx_patterns,
563
+ attribs = {"global_pipe_length_uncertainty": self._global_pipe_length,
564
+ "global_pipe_roughness_uncertainty": self._global_pipe_roughness,
565
+ "global_pipe_diameter_uncertainty": self._global_pipe_diameter,
566
+ "global_base_demand_uncertainty": self._global_base_demand,
567
+ "global_demand_pattern_uncertainty": self._global_demand_pattern,
568
+ "global_elevation_uncertainty": self._global_elevation,
569
+ "global_constants_uncertainty": self._global_constants,
570
+ "global_parameters_uncertainty": self._global_parameters,
571
+ "local_pipe_length_uncertainty": self._local_pipe_length,
572
+ "local_pipe_roughness_uncertainty": self._local_pipe_roughness,
573
+ "local_pipe_diameter_uncertainty": self._local_pipe_diameter,
574
+ "local_base_demand_uncertainty": self._local_base_demand,
575
+ "local_demand_pattern_uncertainty": self._local_demand_pattern,
576
+ "local_elevation_uncertainty": self._local_elevation,
577
+ "local_constants_uncertainty": self._local_constants,
578
+ "local_parameters_uncertainty": self._local_parameters,
579
+ "local_patterns_uncertainty": self._local_patterns,
580
+ "local_msx_patterns_uncertainty": self._local_msx_patterns,
556
581
  "seed": self.__seed}
557
582
 
558
583
  return super().get_attributes() | attribs
@@ -562,45 +587,101 @@ class ModelUncertainty(JsonSerializable):
562
587
  raise TypeError("Can not compare 'ModelUncertainty' instance " +
563
588
  f"with '{type(other)}' instance")
564
589
 
565
- return self.__global_pipe_length == other.global_pipe_length \
566
- and self.__global_pipe_roughness == other.global_pipe_roughness \
567
- and self.__global_pipe_diameter == other.global_pipe_diameter \
568
- and self.__global_base_demand == other.global_base_demand \
569
- and self.__global_demand_pattern == other.global_demand_pattern \
570
- and self.__global_elevation == other.global_elevation \
571
- and self.__global_parameters == other.global_parameters \
572
- and self.__global_constants == other.global_constants \
573
- and self.__local_pipe_length == other.local_pipe_length \
574
- and self.__local_pipe_roughness == other.local_pipe_roughness \
575
- and self.__local_pipe_diameter == other.local_pipe_diameter \
576
- and self.__local_base_demand == other.local_base_demand \
577
- and self.__local_demand_pattern == other.local_demand_pattern \
578
- and self.__local_elevation == other.local_elevation \
579
- and self.__local_parameters == other.local_parameters \
580
- and self.__local_constants == other.local_constants \
581
- and self.__local_patterns == other.local_patterns \
582
- and self.__local_msx_patterns == other.local_msx_patterns \
590
+ return self._global_pipe_length == other.global_pipe_length \
591
+ and self._global_pipe_roughness == other.global_pipe_roughness \
592
+ and self._global_pipe_diameter == other.global_pipe_diameter \
593
+ and self._global_base_demand == other.global_base_demand \
594
+ and self._global_demand_pattern == other.global_demand_pattern \
595
+ and self._global_elevation == other.global_elevation \
596
+ and self._global_parameters == other.global_parameters \
597
+ and self._global_constants == other.global_constants \
598
+ and self._local_pipe_length == other.local_pipe_length \
599
+ and self._local_pipe_roughness == other.local_pipe_roughness \
600
+ and self._local_pipe_diameter == other.local_pipe_diameter \
601
+ and self._local_base_demand == other.local_base_demand \
602
+ and self._local_demand_pattern == other.local_demand_pattern \
603
+ and self._local_elevation == other.local_elevation \
604
+ and self._local_parameters == other.local_parameters \
605
+ and self._local_constants == other.local_constants \
606
+ and self._local_patterns == other.local_patterns \
607
+ and self._local_msx_patterns == other.local_msx_patterns \
583
608
  and self.__seed == other.seed
584
609
 
585
610
  def __str__(self) -> str:
586
- return f"global_pipe_length: {self.__global_pipe_length} " +\
587
- f"global_pipe_roughness: {self.__global_pipe_roughness} " + \
588
- f"global_pipe_diameter: {self.__global_pipe_diameter} " + \
589
- f"global_demand_base: {self.__global_base_demand} " + \
590
- f"global_demand_pattern: {self.__global_demand_pattern} " + \
591
- f"global_elevation: {self.__global_elevation} " + \
592
- f"global_constants: {self.__global_constants} " + \
593
- f"global_parameters: {self.__global_parameters}" + \
594
- f"local_pipe_length: {self.__local_pipe_length} " +\
595
- f"local_pipe_roughness: {self.__local_pipe_roughness} " + \
596
- f"local_pipe_diameter: {self.__local_pipe_diameter} " + \
597
- f"local_demand_base: {self.__local_base_demand} " + \
598
- f"local_demand_pattern: {self.__local_demand_pattern} " + \
599
- f"local_elevation: {self.__local_elevation} " + \
600
- f"local_constants: {self.__local_constants} " + \
601
- f"local_parameters: {self.__local_parameters} " + \
602
- f"local_patterns: {self.__local_patterns} " + \
603
- f"local_msx_patterns: {self.__local_msx_patterns} + seed: {self.__seed}"
611
+ return f"global_pipe_length: {self._global_pipe_length} " +\
612
+ f"global_pipe_roughness: {self._global_pipe_roughness} " + \
613
+ f"global_pipe_diameter: {self._global_pipe_diameter} " + \
614
+ f"global_demand_base: {self._global_base_demand} " + \
615
+ f"global_demand_pattern: {self._global_demand_pattern} " + \
616
+ f"global_elevation: {self._global_elevation} " + \
617
+ f"global_constants: {self._global_constants} " + \
618
+ f"global_parameters: {self._global_parameters}" + \
619
+ f"local_pipe_length: {self._local_pipe_length} " +\
620
+ f"local_pipe_roughness: {self._local_pipe_roughness} " + \
621
+ f"local_pipe_diameter: {self._local_pipe_diameter} " + \
622
+ f"local_demand_base: {self._local_base_demand} " + \
623
+ f"local_demand_pattern: {self._local_demand_pattern} " + \
624
+ f"local_elevation: {self._local_elevation} " + \
625
+ f"local_constants: {self._local_constants} " + \
626
+ f"local_parameters: {self._local_parameters} " + \
627
+ f"local_patterns: {self._local_patterns} " + \
628
+ f"local_msx_patterns: {self._local_msx_patterns} + seed: {self.__seed}"
629
+
630
+ def undo(self, epanet_api: epyt.epanet) -> None:
631
+ """
632
+ Undo all applied uncertainties -- i.e, resets the properties to their original value.
633
+
634
+ Note that this function can only be used if `cache_original` (of the constructor)
635
+ was set to True (default).
636
+
637
+ Parameters
638
+ ----------
639
+ epanet_api : `epyt.epanet <https://epanet-python-toolkit-epyt.readthedocs.io/en/stable/api.html#epyt.epanet.epanet>`_
640
+ Interface to EPANET and EPANET-MSX
641
+ """
642
+ if self.__cache_original is False:
643
+ raise ValueError("Caching was disabled by the user")
644
+
645
+ if self._cache_links_length is not None:
646
+ epanet_api.setLinkLength(self._cache_links_length)
647
+
648
+ if self._cache_links_diameter is not None:
649
+ epanet_api.setLinkDiameter(self._cache_links_diameter)
650
+
651
+ if self._cache_links_roughness_coeff is not None:
652
+ epanet_api.setLinkRoughnessCoeff(self._cache_links_roughness_coeff)
653
+
654
+ if self._cache_nodes_base_demand is not None:
655
+ for node_idx in self._cache_nodes_base_demand.keys():
656
+ for demand_category, base_demand in self._cache_nodes_base_demand[node_idx].items():
657
+ epanet_api.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
658
+
659
+ if self._cache_nodes_demand_pattern is not None:
660
+ for pattern_id, demand_pattern in self._cache_nodes_demand_pattern.items():
661
+ for t, v in enumerate(demand_pattern):
662
+ epanet_api.setPatternValue(pattern_id, t+1, v)
663
+
664
+ if self._cache_nodes_elevation is not None:
665
+ epanet_api.setNodeElevations(self._cache_nodes_elevation)
666
+
667
+ if self._cache_patterns is not None:
668
+ for pattern_idx, pattern in self._cache_patterns.items():
669
+ epanet_api.setPattern(pattern_idx, pattern)
670
+
671
+ if self._cache_msx_constants is not None:
672
+ epanet_api.setMSXConstantsValue(self._cache_msx_constants)
673
+
674
+ if self._cache_msx_links_parameters is not None:
675
+ for pipe_idx, parameters_pipes_val in self._cache_msx_links_parameters.items():
676
+ epanet_api.setMSXParametersPipesValue(pipe_idx, parameters_pipes_val)
677
+
678
+ if self._cache_msx_tanks_parameters is not None:
679
+ for tank_idx, parameters_tanks_val in self._cache_msx_tanks_parameters.items():
680
+ epanet_api.setMSXParametersTanksValue(tank_idx, parameters_tanks_val)
681
+
682
+ if self._cache_msx_patterns is not None:
683
+ for pattern_idx, pattern in self._cache_msx_patterns:
684
+ epanet_api.setMSXPattern(pattern_idx, pattern)
604
685
 
605
686
  def apply(self, epanet_api: epyt.epanet) -> None:
606
687
  """
@@ -613,174 +694,227 @@ class ModelUncertainty(JsonSerializable):
613
694
  """
614
695
  np_rand_gen = np.random.default_rng(seed=self.__seed)
615
696
 
616
- if self.__global_pipe_length is not None:
617
- self.__global_pipe_length.set_random_generator(np_rand_gen)
697
+ if self._global_pipe_length is not None:
698
+ self._global_pipe_length.set_random_generator(np_rand_gen)
618
699
 
619
700
  link_length = epanet_api.getLinkLength()
620
- link_length = self.__global_pipe_length.apply_batch(link_length)
701
+ self._cache_links_length = np.copy(link_length)
702
+
703
+ link_length = self._global_pipe_length.apply_batch(link_length)
621
704
  epanet_api.setLinkLength(link_length)
622
705
 
623
- if self.__local_pipe_length is not None:
624
- self.__local_pipe_length.set_random_generator(np_rand_gen)
706
+ if self._local_pipe_length is not None:
707
+ self._local_pipe_length.set_random_generator(np_rand_gen)
708
+ self._cache_links_length = epanet_api.getLinkLength()
625
709
 
626
- for pipe_id, uncertainty in self.__local_pipe_length.items():
710
+ for pipe_id, uncertainty in self._local_pipe_length.items():
627
711
  link_idx = epanet_api.getLinkIndex(pipe_id)
628
712
  link_length = epanet_api.getLinkLength(link_idx)
713
+
629
714
  link_length = uncertainty.apply(link_length)
630
715
  epanet_api.setLinkLength(link_idx, link_length)
631
716
 
632
- if self.__global_pipe_diameter is not None:
633
- self.__global_pipe_diameter.set_random_generator(np_rand_gen)
717
+ if self._global_pipe_diameter is not None:
718
+ self._global_pipe_diameter.set_random_generator(np_rand_gen)
634
719
 
635
720
  link_diameters = epanet_api.getLinkDiameter()
636
- link_diameters = self.__global_pipe_diameter.apply_batch(link_diameters)
721
+ self._cache_links_diameter = np.copy(link_diameters)
722
+
723
+ link_diameters = self._global_pipe_diameter.apply_batch(link_diameters)
637
724
  epanet_api.setLinkDiameter(link_diameters)
638
725
 
639
- if self.__local_pipe_diameter is not None:
640
- self.__local_pipe_diameter.set_random_generator(np_rand_gen)
726
+ if self._local_pipe_diameter is not None:
727
+ self._local_pipe_diameter.set_random_generator(np_rand_gen)
728
+ self._cache_links_diameter = epanet_api.getLinkDiameter()
641
729
 
642
- for pipe_id, uncertainty in self.__local_pipe_diameter.items():
730
+ for pipe_id, uncertainty in self._local_pipe_diameter.items():
643
731
  link_idx = epanet_api.getLinkIndex(pipe_id)
644
732
  link_diameter = epanet_api.getLinkDiameter(link_idx)
733
+
645
734
  link_diameter = uncertainty.apply(link_diameter)
646
735
  epanet_api.setLinkDiameter(link_idx, link_diameter)
647
736
 
648
- if self.__global_pipe_roughness is not None:
649
- self.__global_pipe_roughness.set_random_generator(np_rand_gen)
737
+ if self._global_pipe_roughness is not None:
738
+ self._global_pipe_roughness.set_random_generator(np_rand_gen)
650
739
 
651
740
  coeffs = epanet_api.getLinkRoughnessCoeff()
652
- coeffs = self.__global_pipe_roughness.apply_batch(coeffs)
741
+ self._cache_links_roughness_coeff = np.copy(coeffs)
742
+
743
+ coeffs = self._global_pipe_roughness.apply_batch(coeffs)
653
744
  epanet_api.setLinkRoughnessCoeff(coeffs)
654
745
 
655
- if self.__local_pipe_roughness is not None:
656
- self.__local_pipe_roughness.set_random_generator(np_rand_gen)
746
+ if self._local_pipe_roughness is not None:
747
+ self._local_pipe_roughness.set_random_generator(np_rand_gen)
748
+ self._cache_links_roughness_coeff = epanet_api.getLinkRoughnessCoeff()
657
749
 
658
- for pipe_id, uncertainty in self.__local_pipe_roughness.items():
750
+ for pipe_id, uncertainty in self._local_pipe_roughness.items():
659
751
  link_idx = epanet_api.getLinkIndex(pipe_id)
660
752
  link_roughness_coeff = epanet_api.getLinkRoughnessCoeff(link_idx)
753
+
661
754
  link_roughness_coeff = uncertainty.apply(link_roughness_coeff)
662
755
  epanet_api.setLinkRoughnessCoeff(link_idx, link_roughness_coeff)
663
756
 
664
- if self.__global_base_demand is not None:
665
- self.__global_base_demand.set_random_generator(np_rand_gen)
757
+ if self._global_base_demand is not None:
758
+ self._global_base_demand.set_random_generator(np_rand_gen)
666
759
 
760
+ self._cache_nodes_base_demand = {}
667
761
  all_nodes_idx = epanet_api.getNodeIndex()
668
762
  for node_idx in all_nodes_idx:
763
+ self._cache_nodes_base_demand[node_idx] = {}
669
764
  n_demand_categories = epanet_api.getNodeDemandCategoriesNumber(node_idx)
670
765
  for demand_category in range(n_demand_categories):
671
766
  base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1]
672
- base_demand = self.__global_base_demand.apply(base_demand)
767
+ self._cache_nodes_base_demand[node_idx][demand_category] = base_demand
768
+
769
+ base_demand = self._global_base_demand.apply(base_demand)
673
770
  epanet_api.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
674
771
 
675
- if self.__local_base_demand is not None:
676
- self.__local_base_demand.set_random_generator(np_rand_gen)
772
+ if self._local_base_demand is not None:
773
+ self._local_base_demand.set_random_generator(np_rand_gen)
677
774
 
678
- for node_id, uncertainty in self.__local_base_demand.items():
775
+ self._cache_nodes_base_demand = {}
776
+ for node_id, uncertainty in self._local_base_demand.items():
679
777
  node_idx = epanet_api.getNodeIndex(node_id)
778
+ self._cache_nodes_base_demand[node_idx] = {}
680
779
  n_demand_categories = epanet_api.getNodeDemandCategoriesNumber(node_idx)
681
780
  for demand_category in range(n_demand_categories):
682
781
  base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1]
782
+ self._cache_nodes_base_demand[node_idx][demand_category] = base_demand
783
+
683
784
  base_demand = uncertainty.apply(base_demand)
684
785
  epanet_api.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
685
786
 
686
- if self.__global_demand_pattern is not None:
687
- self.__global_demand_pattern.set_random_generator(np_rand_gen)
787
+ if self._global_demand_pattern is not None:
788
+ self._global_demand_pattern.set_random_generator(np_rand_gen)
688
789
 
790
+ self._cache_nodes_demand_pattern = {}
689
791
  demand_patterns_idx = epanet_api.getNodeDemandPatternIndex()
690
792
  demand_patterns_id = np.unique([demand_patterns_idx[k]
691
793
  for k in demand_patterns_idx.keys()])
692
794
  for pattern_id in demand_patterns_id:
693
795
  if pattern_id == 0:
694
796
  continue
797
+
695
798
  pattern_length = epanet_api.getPatternLengths(pattern_id)
799
+ demand_pattern = np.zeros(pattern_length)
800
+
696
801
  for t in range(pattern_length):
697
802
  v = epanet_api.getPatternValue(pattern_id, t+1)
698
- v_ = self.__global_demand_pattern.apply(v)
803
+ demand_pattern[t] = v
804
+
805
+ v_ = self._global_demand_pattern.apply(v)
699
806
  epanet_api.setPatternValue(pattern_id, t+1, v_)
700
807
 
701
- if self.__local_demand_pattern is not None:
702
- self.__local_demand_pattern.set_random_generator(np_rand_gen)
808
+ self._cache_nodes_demand_pattern[pattern_id] = demand_pattern
703
809
 
810
+ if self._local_demand_pattern is not None:
811
+ self._local_demand_pattern.set_random_generator(np_rand_gen)
812
+
813
+ self._cache_nodes_demand_pattern = {}
704
814
  patterns_id = epanet_api.getPatternNameID()
705
815
  paterns_idx = epanet_api.getPatternIndex()
706
816
 
707
- for pattern_id, uncertainty in self.__local_demand_pattern.items():
817
+ for pattern_id, uncertainty in self._local_demand_pattern.items():
708
818
  pattern_idx = paterns_idx[patterns_id.index(pattern_id)]
709
819
  pattern_length, = epanet_api.getPatternLengths(pattern_id)
820
+ demand_pattern = np.zeros(pattern_length)
821
+
710
822
  for t in range(pattern_length):
711
823
  v = epanet_api.getPatternValue(pattern_idx, t+1)
824
+ demand_pattern[t] = v
825
+
712
826
  v_ = uncertainty.apply(v)
713
827
  epanet_api.setPatternValue(pattern_idx, t+1, v_)
714
828
 
715
- if self.__global_elevation is not None:
716
- self.__global_elevation.set_random_generator(np_rand_gen)
829
+ self._cache_nodes_demand_pattern[pattern_id] = demand_pattern
830
+
831
+ if self._global_elevation is not None:
832
+ self._global_elevation.set_random_generator(np_rand_gen)
717
833
 
718
834
  elevations = epanet_api.getNodeElevations()
719
- elevations = self.__global_elevation.apply_batch(elevations)
835
+ self._cache_nodes_elevation = np.copy(elevations)
836
+
837
+ elevations = self._global_elevation.apply_batch(elevations)
720
838
  epanet_api.setNodeElevations(elevations)
721
839
 
722
- if self.__local_elevation is not None:
723
- self.__local_elevation.set_random_generator(np_rand_gen)
840
+ if self._local_elevation is not None:
841
+ self._local_elevation.set_random_generator(np_rand_gen)
842
+ self._cache_nodes_elevation = epanet_api.getNodeElevations()
724
843
 
725
- for node_id, uncertainty in self.__local_elevation.items():
844
+ for node_id, uncertainty in self._local_elevation.items():
726
845
  node_idx = epanet_api.getNodeIndex(node_id)
727
846
  elevation = epanet_api.getNodeElevations(node_idx)
847
+
728
848
  elevation = uncertainty.apply(elevation)
729
849
  epanet_api.setNodeElevations(node_idx, elevation)
730
850
 
731
- if self.__local_patterns is not None:
732
- self.__local_patterns.set_random_generator(np_rand_gen)
851
+ if self._local_patterns is not None:
852
+ self._local_patterns.set_random_generator(np_rand_gen)
853
+ self._cache_patterns = {}
733
854
 
734
- for pattern_id, uncertainty in self.__local_patterns.items():
855
+ for pattern_id, uncertainty in self._local_patterns.items():
735
856
  pattern_idx = epanet_api.getPatternIndex(pattern_id)
736
857
  pattern_length = epanet_api.getPatternLengths(pattern_idx)
737
858
  pattern = np.array([epanet_api.getPatternValue(pattern_idx, t+1)
738
859
  for t in range(pattern_length)])
860
+ self._cache_patterns[pattern_idx] = pattern
861
+
739
862
  pattern = uncertainty.apply_batch(pattern)
740
863
  epanet_api.setPattern(pattern_idx, pattern)
741
864
 
742
865
  if epanet_api.MSXFile is not None:
743
- if self.__global_constants is not None:
744
- self.__global_constants.set_random_generator(np_rand_gen)
866
+ if self._global_constants is not None:
867
+ self._global_constants.set_random_generator(np_rand_gen)
745
868
 
746
869
  constants = np.array(epanet_api.getMSXConstantsValue())
747
- constants = self.__global_constants.apply_batch(constants)
870
+ self._cache_msx_patterns = np.copy(constants)
871
+
872
+ constants = self._global_constants.apply_batch(constants)
748
873
  epanet_api.setMSXConstantsValue(constants)
749
874
 
750
- if self.__local_constants:
751
- self.__local_constants.set_random_generator(np_rand_gen)
875
+ if self._local_constants:
876
+ self._local_constants.set_random_generator(np_rand_gen)
877
+
878
+ self._cache_msx_patterns = np.array(epanet_api.getMSXConstantsValue())
752
879
 
753
- for constant_id, uncertainty in self.__local_constants.items():
880
+ for constant_id, uncertainty in self._local_constants.items():
754
881
  idx = epanet_api.MSXgetindex(ToolkitConstants.MSX_CONSTANT, constant_id)
755
882
  constant = epanet_api.msx.MSXgetconstant(idx)
883
+
756
884
  constant = uncertainty.apply(constant)
757
885
  epanet_api.msx.MSXsetconstant(idx, constant)
758
886
 
759
- if self.__global_parameters is not None:
760
- self.__global_parameters.set_random_generator(np_rand_gen)
887
+ if self._global_parameters is not None:
888
+ self._global_parameters.set_random_generator(np_rand_gen)
761
889
 
890
+ self._cache_msx_links_parameters = {}
762
891
  parameters_pipes = epanet_api.getMSXParametersPipesValue()
763
892
  for i, pipe_idx in enumerate(epanet_api.getLinkPipeIndex()):
764
893
  if len(parameters_pipes[i]) == 0:
765
894
  continue
766
895
 
767
- parameters_pipes_val = self.__global_parameters.apply_batch(
896
+ self._cache_msx_links_parameters[pipe_idx] = np.array(parameters_pipes[i])
897
+ parameters_pipes_val = self._global_parameters.apply_batch(
768
898
  np.array(parameters_pipes[i]))
769
899
  epanet_api.setMSXParametersPipesValue(pipe_idx, parameters_pipes_val)
770
900
 
901
+ self._cache_msx_tanks_parameters = {}
771
902
  parameters_tanks = epanet_api.getMSXParametersTanksValue()
772
903
  for i, tank_idx in enumerate(epanet_api.getNodeTankIndex()):
773
904
  if parameters_tanks[i] is None or len(parameters_tanks[i]) == 0:
774
905
  continue
775
906
 
776
- parameters_tanks_val = self.__global_parameters.apply_batch(
907
+ self._cache_msx_tanks_parameters[tank_idx] = np.array(parameters_tanks[i])
908
+ parameters_tanks_val = self._global_parameters.apply_batch(
777
909
  np.array(parameters_tanks[i]))
778
910
  epanet_api.setMSXParametersTanksValue(tank_idx, parameters_tanks_val)
779
911
 
780
- if self.__local_parameters is not None:
781
- self.__local_parameters.set_random_generator(np_rand_gen)
912
+ if self._local_parameters is not None:
913
+ self._local_parameters.set_random_generator(np_rand_gen)
914
+ self._cache_msx_links_parameters = {}
915
+ self._cache_msx_tanks_parameters = {}
782
916
 
783
- for (param_id, item_type, item_id), uncertainty in self.__local_parameters.items():
917
+ for (param_id, item_type, item_id), uncertainty in self._local_parameters.items():
784
918
  idx, = epanet_api.getMSXParametersIndex([param_id])
785
919
 
786
920
  if item_type == ToolkitConstants.MSX_NODE:
@@ -792,19 +926,27 @@ class ModelUncertainty(JsonSerializable):
792
926
  "ToolkitConstants.MSX_NODE or ToolkitConstants.MSX_LINK")
793
927
 
794
928
  parameter = epanet_api.msx.MSXgetparameter(item_type, item_idx, idx)
929
+ if item_type == ToolkitConstants.MSX_NODE:
930
+ self._cache_msx_tanks_parameters[item_idx] = parameter
931
+ elif item_type == ToolkitConstants.MSX_LINK:
932
+ self._cache_msx_links_parameters[item_idx] = parameter
933
+
795
934
  parameter = uncertainty.apply(parameter)
796
935
  epanet_api.msx.MSXsetparameter(item_type, item_idx, idx, parameter)
797
936
 
798
- if self.__local_msx_patterns is not None:
799
- self.__local_msx_patterns.set_random_generator(np_rand_gen)
937
+ if self._local_msx_patterns is not None:
938
+ self._local_msx_patterns.set_random_generator(np_rand_gen)
939
+ self._cache_msx_patterns = {}
800
940
 
801
- for pattern_id, uncertainty in self.__local_msx_patterns.items():
941
+ for pattern_id, uncertainty in self._local_msx_patterns.items():
802
942
  pattern_idx, = epanet_api.getMSXPatternsIndex([pattern_id])
803
943
  pattern = epanet_api.getMSXConstantsValue([pattern_idx])
944
+ self._cache_msx_patterns[pattern_idx] = np.copy(pattern)
945
+
804
946
  pattern = uncertainty.apply_batch(pattern)
805
947
  epanet_api.setMSXPattern(pattern_idx, pattern)
806
948
  else:
807
- if self.__local_msx_patterns is not None or self.__local_parameters is not None or \
808
- self.__local_constants is not None or self.__global_constants is not None or \
809
- self.__global_parameters is not None:
949
+ if self._local_msx_patterns is not None or self._local_parameters is not None or \
950
+ self._local_constants is not None or self._global_constants is not None or \
951
+ self._global_parameters is not None:
810
952
  warnings.warn("Ignoring EPANET-MSX uncertainties because not .msx file was loaded")