epyt-flow 0.1.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.
Files changed (131) hide show
  1. epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +28 -0
  2. epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +21 -0
  3. epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +18 -0
  4. epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +134 -0
  5. epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +5578 -0
  6. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +865 -0
  7. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +131 -0
  8. epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +73 -0
  9. epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +193 -0
  10. epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +1000 -0
  11. epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -0
  12. epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -0
  13. epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +1151 -0
  14. epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +1117 -0
  15. epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +720 -0
  16. epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +476 -0
  17. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +431 -0
  18. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +1786 -0
  19. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +468 -0
  20. epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +810 -0
  21. epyt_flow/EPANET/EPANET/SRC_engines/input1.c +707 -0
  22. epyt_flow/EPANET/EPANET/SRC_engines/input2.c +864 -0
  23. epyt_flow/EPANET/EPANET/SRC_engines/input3.c +2170 -0
  24. epyt_flow/EPANET/EPANET/SRC_engines/main.c +93 -0
  25. epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +142 -0
  26. epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +24 -0
  27. epyt_flow/EPANET/EPANET/SRC_engines/output.c +852 -0
  28. epyt_flow/EPANET/EPANET/SRC_engines/project.c +1359 -0
  29. epyt_flow/EPANET/EPANET/SRC_engines/quality.c +685 -0
  30. epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +743 -0
  31. epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +694 -0
  32. epyt_flow/EPANET/EPANET/SRC_engines/report.c +1489 -0
  33. epyt_flow/EPANET/EPANET/SRC_engines/rules.c +1362 -0
  34. epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +871 -0
  35. epyt_flow/EPANET/EPANET/SRC_engines/text.h +497 -0
  36. epyt_flow/EPANET/EPANET/SRC_engines/types.h +874 -0
  37. epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +53 -0
  38. epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +27 -0
  39. epyt_flow/EPANET/EPANET-MSX/Src/hash.c +107 -0
  40. epyt_flow/EPANET/EPANET-MSX/Src/hash.h +28 -0
  41. epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +102 -0
  42. epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +42 -0
  43. epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +937 -0
  44. epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +39 -0
  45. epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +204 -0
  46. epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +24 -0
  47. epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +1285 -0
  48. epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +368 -0
  49. epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +42 -0
  50. epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +586 -0
  51. epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +116 -0
  52. epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +260 -0
  53. epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +175 -0
  54. epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +35 -0
  55. epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +1504 -0
  56. epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +401 -0
  57. epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +791 -0
  58. epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +2010 -0
  59. epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +400 -0
  60. epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +422 -0
  61. epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +1164 -0
  62. epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +551 -0
  63. epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +524 -0
  64. epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +56 -0
  65. epyt_flow/EPANET/EPANET-MSX/Src/newton.c +158 -0
  66. epyt_flow/EPANET/EPANET-MSX/Src/newton.h +34 -0
  67. epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +287 -0
  68. epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +39 -0
  69. epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +293 -0
  70. epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +35 -0
  71. epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +816 -0
  72. epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +29 -0
  73. epyt_flow/EPANET/EPANET-MSX/readme.txt +14 -0
  74. epyt_flow/EPANET/compile.sh +4 -0
  75. epyt_flow/VERSION +1 -0
  76. epyt_flow/__init__.py +24 -0
  77. epyt_flow/data/__init__.py +0 -0
  78. epyt_flow/data/benchmarks/__init__.py +11 -0
  79. epyt_flow/data/benchmarks/batadal.py +257 -0
  80. epyt_flow/data/benchmarks/batadal_data.py +28 -0
  81. epyt_flow/data/benchmarks/battledim.py +473 -0
  82. epyt_flow/data/benchmarks/battledim_data.py +51 -0
  83. epyt_flow/data/benchmarks/gecco_water_quality.py +267 -0
  84. epyt_flow/data/benchmarks/leakdb.py +592 -0
  85. epyt_flow/data/benchmarks/leakdb_data.py +18923 -0
  86. epyt_flow/data/benchmarks/water_usage.py +123 -0
  87. epyt_flow/data/networks.py +650 -0
  88. epyt_flow/gym/__init__.py +4 -0
  89. epyt_flow/gym/control_gyms.py +47 -0
  90. epyt_flow/gym/scenario_control_env.py +101 -0
  91. epyt_flow/metrics.py +404 -0
  92. epyt_flow/models/__init__.py +2 -0
  93. epyt_flow/models/event_detector.py +31 -0
  94. epyt_flow/models/sensor_interpolation_detector.py +118 -0
  95. epyt_flow/rest_api/__init__.py +4 -0
  96. epyt_flow/rest_api/base_handler.py +70 -0
  97. epyt_flow/rest_api/res_manager.py +95 -0
  98. epyt_flow/rest_api/scada_data_handler.py +476 -0
  99. epyt_flow/rest_api/scenario_handler.py +352 -0
  100. epyt_flow/rest_api/server.py +106 -0
  101. epyt_flow/serialization.py +438 -0
  102. epyt_flow/simulation/__init__.py +5 -0
  103. epyt_flow/simulation/events/__init__.py +6 -0
  104. epyt_flow/simulation/events/actuator_events.py +259 -0
  105. epyt_flow/simulation/events/event.py +81 -0
  106. epyt_flow/simulation/events/leakages.py +404 -0
  107. epyt_flow/simulation/events/sensor_faults.py +267 -0
  108. epyt_flow/simulation/events/sensor_reading_attack.py +185 -0
  109. epyt_flow/simulation/events/sensor_reading_event.py +170 -0
  110. epyt_flow/simulation/events/system_event.py +88 -0
  111. epyt_flow/simulation/parallel_simulation.py +147 -0
  112. epyt_flow/simulation/scada/__init__.py +3 -0
  113. epyt_flow/simulation/scada/advanced_control.py +134 -0
  114. epyt_flow/simulation/scada/scada_data.py +1589 -0
  115. epyt_flow/simulation/scada/scada_data_export.py +255 -0
  116. epyt_flow/simulation/scenario_config.py +608 -0
  117. epyt_flow/simulation/scenario_simulator.py +1897 -0
  118. epyt_flow/simulation/scenario_visualizer.py +61 -0
  119. epyt_flow/simulation/sensor_config.py +1289 -0
  120. epyt_flow/topology.py +290 -0
  121. epyt_flow/uncertainty/__init__.py +3 -0
  122. epyt_flow/uncertainty/model_uncertainty.py +302 -0
  123. epyt_flow/uncertainty/sensor_noise.py +73 -0
  124. epyt_flow/uncertainty/uncertainties.py +555 -0
  125. epyt_flow/uncertainty/utils.py +206 -0
  126. epyt_flow/utils.py +306 -0
  127. epyt_flow-0.1.0.dist-info/LICENSE +21 -0
  128. epyt_flow-0.1.0.dist-info/METADATA +139 -0
  129. epyt_flow-0.1.0.dist-info/RECORD +131 -0
  130. epyt_flow-0.1.0.dist-info/WHEEL +5 -0
  131. epyt_flow-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,608 @@
1
+ """
2
+ Module provides a class for specifying scenario configurations.
3
+ """
4
+ from typing import Any
5
+ from copy import deepcopy
6
+ import json
7
+
8
+ from ..uncertainty import AbsoluteGaussianUncertainty, RelativeGaussianUncertainty, \
9
+ AbsoluteUniformUncertainty, RelativeUniformUncertainty, ModelUncertainty, \
10
+ SensorNoise, Uncertainty
11
+ from .sensor_config import SensorConfig
12
+ from .scada import AdvancedControlModule
13
+ from .events import SystemEvent, SensorReadingEvent
14
+ from .events.sensor_faults import SensorFaultConstant, SensorFaultDrift, SensorFaultGaussian, \
15
+ SensorFaultPercentage, SensorFaultStuckZero
16
+ from .events.leakages import AbruptLeakage, IncipientLeakage
17
+ from ..serialization import serializable, Serializable, SCENARIO_CONFIG_ID
18
+
19
+
20
+ @serializable(SCENARIO_CONFIG_ID, ".epytflow_scenario_config")
21
+ class ScenarioConfig(Serializable):
22
+ """
23
+ Configuration of a scenario.
24
+
25
+ Parameters
26
+ ----------
27
+ scenario_config : :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig`, optional
28
+ Uses the given scenario configuration to create this instance --
29
+ other attributes passed to this constructor override the attributes in 'scenario_config'.
30
+
31
+ Note that if 'scenario_config' is None then 'f_inp_in' can not be None --
32
+ i.e. either 'scenario_config' or 'f_inp_in' must be given.
33
+
34
+ The default is None.
35
+ f_inp_in : `str`, optional
36
+ Path to the .inp file.
37
+
38
+ Note that if 'f_inp_in' is None then 'scenario_config' can not be None --
39
+ i.e. either 'scenario_config' or 'f_inp_in' must be given.
40
+
41
+ The default is None.
42
+ f_msx_in : `str`, optional
43
+ Path to the .msx file -- optional, only necessary if EPANET-MSX is used.
44
+
45
+ The default is None
46
+ general_params : `dict`, optional
47
+ General parameters such as the demand model, hydraulic time steps, etc.
48
+
49
+ The default is None
50
+ sensor_config : :class:`~epyt_flow.simulation.sensor_config.SensorConfig`, optional
51
+ Specification of all sensors.
52
+
53
+ The default is None
54
+ memory_consumption_estimate : float, optional
55
+ Estimated memory consumption of this scenario in MB -- i.e. the amount of memory that is
56
+ needed on the hard disk as well as in RAM.
57
+
58
+ The default is None.
59
+ sensor_noise : :class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise`, optional
60
+ Speciation of sensor noise -- i.e. noise/uncertainty affecting the sensor readings.
61
+
62
+ The default is None
63
+ controls : list[:class:`~epyt_flow.simulation.scada.advanced_control.AdvancedControlModule`], optional
64
+ List of control modules that are active during the simulation.
65
+
66
+ The default is an empty list.
67
+ model_uncertainty : :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`, optional
68
+ Specification of model uncertainty.
69
+ system_events : list[:class:`~epyt_flow.simulation.events.system_event.SystemEvent`], optional
70
+ List of system events -- i.e. events that directly affect the simulation (e.g. leakages).
71
+
72
+ The default is an empty list.
73
+ sensor_reading_events : list[:class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`], optional
74
+ List of sensor reading events -- i.e. events that affect the readings of sensors.
75
+
76
+ The default is an empty list.
77
+ """
78
+
79
+ def __init__(self, scenario_config: Any = None, f_inp_in: str = None, f_msx_in: str = None,
80
+ general_params: dict = None, sensor_config: SensorConfig = None,
81
+ memory_consumption_estimate: float = None,
82
+ controls: list[AdvancedControlModule] = [],
83
+ sensor_noise: SensorNoise = None,
84
+ model_uncertainty: ModelUncertainty = None,
85
+ system_events: list[SystemEvent] = [],
86
+ sensor_reading_events: list[SensorReadingEvent] = [], **kwds):
87
+ if f_inp_in is None and scenario_config is None:
88
+ raise ValueError("Either 'f_inp_in' or 'scenario_config' must be given")
89
+ if scenario_config is not None:
90
+ if not isinstance(scenario_config, ScenarioConfig):
91
+ raise TypeError("'scenario_config' must be an instance of " +
92
+ "'epyt_flow.simulation.ScenarioConfig' but not of " +
93
+ f"'{type(scenario_config)}'")
94
+ if f_inp_in is not None:
95
+ if not isinstance(f_inp_in, str):
96
+ raise TypeError("'f_inp_in' must be an instance of 'str' " +
97
+ f"but no of '{type(f_inp_in)}'")
98
+ if f_msx_in is not None:
99
+ if not isinstance(f_msx_in, str):
100
+ raise TypeError("'f_msx_in' must be an instance of 'str' " +
101
+ f"but no of '{type(f_msx_in)}'")
102
+ if general_params is not None:
103
+ if not isinstance(general_params, dict):
104
+ raise TypeError("'general_params' must be an instance of 'dict' " +
105
+ f"but not of '{type(general_params)}'")
106
+ if sensor_config is not None:
107
+ if not isinstance(sensor_config, SensorConfig):
108
+ raise TypeError("'sensor_config' must be an instance of " +
109
+ "'epyt_flow.simulation.SensorConfig' but not of " +
110
+ f"'{type(sensor_config)}'")
111
+ if memory_consumption_estimate is not None:
112
+ if not isinstance(memory_consumption_estimate, float) or \
113
+ memory_consumption_estimate <= 0:
114
+ raise ValueError("'memory_consumption_estimate' must be a positive integer")
115
+ if not isinstance(controls, list):
116
+ raise TypeError("'controls' must be an instance of " +
117
+ "'list[epyt_flow.simulation.scada.AdvancedControlModule]' but no of " +
118
+ f"'{type(controls)}'")
119
+ if len(controls) != 0:
120
+ if any(not isinstance(c, AdvancedControlModule) for c in controls):
121
+ raise TypeError("Each item in 'controls' must be an instance of " +
122
+ "'epyt_flow.simulation.scada.AdvancedControlModule'")
123
+ if sensor_noise is not None:
124
+ if not isinstance(sensor_noise, SensorNoise):
125
+ raise TypeError("'sensor_noise' must be an instance of " +
126
+ "'epyt_flow.uncertainty.SensorNoise' but not of " +
127
+ f"'{type(sensor_noise)}'")
128
+ if model_uncertainty is not None:
129
+ if not isinstance(model_uncertainty, ModelUncertainty):
130
+ raise TypeError("'model_uncertainty' must be an instance of " +
131
+ "'epyt_flow.uncertainty.ModelUncertainty' but not of " +
132
+ f"'{type(model_uncertainty)}'")
133
+ if not isinstance(system_events, list):
134
+ raise TypeError("'system_events' must be an instance of " +
135
+ "'list[epyt_flow.simulation.events.SystemEvent]' but no of " +
136
+ f"'{type(system_events)}'")
137
+ if len(system_events) != 0:
138
+ if any(not isinstance(c, SystemEvent) for c in system_events):
139
+ raise TypeError("Each item in 'system_events' must be an instance of " +
140
+ "'epyt_flow.simulation.events.SystemEvent'")
141
+ if not isinstance(sensor_reading_events, list):
142
+ raise TypeError("'sensor_reading_events' must be an instance of " +
143
+ "'list[epyt_flow.simulation.events.SensorReadingEvent]' but not of " +
144
+ f"'{type(sensor_reading_events)}'")
145
+ if len(sensor_reading_events) != 0:
146
+ if any(not isinstance(c, SensorReadingEvent) for c in sensor_reading_events):
147
+ raise TypeError("Each item in 'sensor_reading_events' must be an instance of " +
148
+ "'epyt_flow.simulation.events.SensorReadingEvent'")
149
+
150
+ if scenario_config is not None:
151
+ self.__f_inp_in = scenario_config.f_inp_in
152
+ self.__f_msx_in = scenario_config.f_msx_in if f_msx_in is None else f_msx_in
153
+
154
+ if general_params is None:
155
+ self.__general_params = scenario_config.general_params
156
+ else:
157
+ self.__general_params = general_params
158
+
159
+ if sensor_config is None:
160
+ self.__sensor_config = scenario_config.sensor_config
161
+ else:
162
+ self.__sensor_config = sensor_config
163
+
164
+ if memory_consumption_estimate is None:
165
+ self.__memory_consumption_estimate = scenario_config.memory_consumption_estimate
166
+ else:
167
+ self.__memory_consumption_estimate = memory_consumption_estimate
168
+
169
+ if len(controls) == 0:
170
+ self.__controls = scenario_config.controls
171
+ else:
172
+ self.__controls = controls
173
+
174
+ if sensor_noise is None:
175
+ self.__sensor_noise = scenario_config.sensor_noise
176
+ else:
177
+ self.__sensor_noise = sensor_noise
178
+
179
+ if model_uncertainty is None:
180
+ self.__model_uncertainty = scenario_config.model_uncertainty
181
+ else:
182
+ self.__model_uncertainty = model_uncertainty
183
+
184
+ if len(system_events) == 0:
185
+ self.__system_events = scenario_config.system_events
186
+ else:
187
+ self.__system_events = system_events
188
+
189
+ if len(sensor_reading_events) == 0:
190
+ self.__sensor_reading_events = scenario_config.sensor_reading_events
191
+ else:
192
+ self.__sensor_reading_events = sensor_reading_events
193
+ else:
194
+ self.__f_inp_in = f_inp_in
195
+ self.__f_msx_in = f_msx_in
196
+ self.__general_params = general_params
197
+ self.__sensor_config = sensor_config
198
+ self.__memory_consumption_estimate = memory_consumption_estimate
199
+ self.__controls = controls
200
+ self.__sensor_noise = sensor_noise
201
+ self.__system_events = system_events
202
+ self.__sensor_reading_events = sensor_reading_events
203
+
204
+ if model_uncertainty is not None:
205
+ self.__model_uncertainty = model_uncertainty
206
+ else:
207
+ self.__model_uncertainty = ModelUncertainty()
208
+
209
+ super().__init__(**kwds)
210
+
211
+ @property
212
+ def f_inp_in(self) -> str:
213
+ """
214
+ Gets the path to the .inp file.
215
+
216
+ Returns
217
+ -------
218
+ `str`
219
+ Path to the .inp file.
220
+ """
221
+ return self.__f_inp_in
222
+
223
+ @property
224
+ def f_msx_in(self) -> str:
225
+ """
226
+ Gets the path to the .msx file.
227
+
228
+ Returns
229
+ -------
230
+ `str`
231
+ Path to the .msx file.
232
+ """
233
+ return self.__f_msx_in
234
+
235
+ @property
236
+ def general_params(self) -> dict:
237
+ """
238
+ Gets general parameters such as hydraulic time step, etc.
239
+
240
+ Returns
241
+ -------
242
+ `dict`
243
+ All general parameters as dictionary -- the parameter name serves as a key.
244
+ """
245
+ return deepcopy(self.__general_params)
246
+
247
+ @property
248
+ def sensor_config(self) -> SensorConfig:
249
+ """
250
+ Gets the sensor configuration.
251
+
252
+ Returns
253
+ -------
254
+ :class:`~epyt_flow.simulation.sensor_config.SensorConfig`
255
+ Sensor configuration.
256
+ """
257
+ return deepcopy(self.__sensor_config)
258
+
259
+ @property
260
+ def memory_consumption_estimate(self) -> float:
261
+ """
262
+ Gets the estimated memory consumption of this scenario -- i.e. the amount of memory that is
263
+ needed on the hard disk as well as in RAM.
264
+
265
+ Returns
266
+ -------
267
+ `float`
268
+ Estimated memory consumption in MB.
269
+ """
270
+ return self.__memory_consumption_estimate
271
+
272
+ @property
273
+ def controls(self) -> list[AdvancedControlModule]:
274
+ """
275
+ Gets the list of all control modules that are active during the simulation.
276
+
277
+ Returns
278
+ -------
279
+ list[:class:`~epyt_flow.simulation.scada.advanced_control.AdvancedControlModule`]
280
+ List of all control modules that are active during the simulation.
281
+ """
282
+ return deepcopy(self.__controls)
283
+
284
+ @property
285
+ def sensor_noise(self) -> SensorNoise:
286
+ """
287
+ Gets the sensor noise/uncertainty specification.
288
+
289
+ Returns
290
+ -------
291
+ :class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise`
292
+ Sensor noise/uncertainty.
293
+ """
294
+ return deepcopy(self.__sensor_noise)
295
+
296
+ @property
297
+ def model_uncertainty(self) -> ModelUncertainty:
298
+ """
299
+ Gets the model uncertainty specification.
300
+
301
+ Returns
302
+ -------
303
+ :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`
304
+ Model uncertainty specification.
305
+ """
306
+ return deepcopy(self.__model_uncertainty)
307
+
308
+ @property
309
+ def system_events(self) -> list[SystemEvent]:
310
+ """
311
+ Gets all system events.
312
+
313
+ Returns
314
+ -------
315
+ list[:class:`~epyt_flow.simulation.events.system_event.SystemEvent`]
316
+ All system events.
317
+ """
318
+ return deepcopy(self.__system_events)
319
+
320
+ @property
321
+ def sensor_reading_events(self) -> list[SensorReadingEvent]:
322
+ """
323
+ Gets all sensor reading events.
324
+
325
+ Returns
326
+ -------
327
+ list[:class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`]
328
+ All sensor reading events.
329
+ """
330
+ return deepcopy(self.__sensor_reading_events)
331
+
332
+ def get_attributes(self) -> dict:
333
+ my_attributes = {"f_inp_in": self.__f_inp_in, "f_msx_in": self.__f_msx_in,
334
+ "general_params": self.__general_params,
335
+ "sensor_config": self.__sensor_config,
336
+ "memory_consumption_estimate": self.__memory_consumption_estimate,
337
+ "controls": self.__controls,
338
+ "sensor_noise": self.__sensor_noise,
339
+ "model_uncertainty": self.__model_uncertainty,
340
+ "system_events": self.__system_events,
341
+ "sensor_reading_events": self.__sensor_reading_events}
342
+
343
+ return super().get_attributes() | my_attributes
344
+
345
+ def __eq__(self, other) -> bool:
346
+ if not isinstance(other, ScenarioConfig):
347
+ raise TypeError("Can not compare 'ScenarioConfig' instance " +
348
+ f"with '{type(other)}' instance")
349
+
350
+ return self.__f_inp_in == other.f_inp_in and self.__f_msx_in == other.f_msx_in \
351
+ and self.__general_params == other.general_params \
352
+ and self.__memory_consumption_estimate == other.memory_consumption_estimate \
353
+ and self.__sensor_config == other.sensor_config and self.__controls == other.controls \
354
+ and self.__model_uncertainty == other.model_uncertainty \
355
+ and self.__system_events == other.system_events \
356
+ and self.__sensor_reading_events == other.sensor_reading_events
357
+
358
+ def __str__(self) -> str:
359
+ return f"f_inp_in: {self.f_inp_in} f_msx_in: {self.f_msx_in} " + \
360
+ f"general_params: {self.general_params} sensor_config: {self.sensor_config} " + \
361
+ f"memory_consumption_estimate: {self.memory_consumption_estimate} " + \
362
+ f"controls: {self.controls} sensor_noise: {self.sensor_noise} " + \
363
+ f"model_uncertainty: {self.model_uncertainty} " + \
364
+ f"system_events: {','.join(map(str, self.system_events))} " + \
365
+ f"sensor_reading_events: {','.join(map(str, self.sensor_reading_events))}"
366
+
367
+ @staticmethod
368
+ def load_from_json_file(f_json_in: str) -> Any:
369
+ """
370
+ Loads a scenario configuration from a given JSON file.
371
+
372
+ Parameters
373
+ ----------
374
+ f_json_in : `str`
375
+ Path to JSON configuration file.
376
+
377
+ Returns
378
+ -------
379
+ :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig`
380
+ Loaded scenario configuration.
381
+ """
382
+ with open(f_json_in, "r", encoding="utf-8") as f:
383
+ return ScenarioConfig.load_from_json(f.read())
384
+
385
+ @staticmethod
386
+ def load_from_json(config_data: str) -> Any:
387
+ """
388
+ Loads a scenario configuration from a given JSON string.
389
+
390
+ Parameters
391
+ ----------
392
+ config_data : `str`
393
+ JSON data.
394
+
395
+ Returns
396
+ -------
397
+ :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig`
398
+ Loaded scenario configuration.
399
+ """
400
+ data = json.loads(config_data)
401
+
402
+ # General parameters and sensor configuration
403
+ general_settings = data["general"]
404
+ f_inp_in = general_settings["file_inp"]
405
+ f_msx_in = general_settings["file_msx"] if "file_msx" in general_settings.keys() else None
406
+
407
+ general_params = {"simulation_duration": general_settings["simulation_duration"],
408
+ "hydraulic_time_step": general_settings["hydraulic_time_step"],
409
+ "quality_time_step": general_settings["quality_time_step"]}
410
+ if "reporting_time_step" in general_settings.keys():
411
+ general_params["reporting_time_step"] = general_settings["reporting_time_step"]
412
+ if "reporting_time_start" in general_settings.keys():
413
+ general_params["reporting_time_start"] = general_settings["reporting_time_start"]
414
+ if "demand_model" in general_settings.keys():
415
+ general_params["demand_model"] = general_settings["demand_model"]
416
+ if "quality_model" in general_settings.keys():
417
+ general_params["quality_model"] = general_settings["quality_model"]
418
+ if "flow_units" in general_settings.keys():
419
+ general_params["flow_units"] = general_settings["flow_units"]
420
+
421
+ sensor_config = data["sensors"]
422
+
423
+ if "pressure_sensors" in sensor_config.keys():
424
+ pressure_sensors = sensor_config["pressure_sensors"]
425
+ else:
426
+ pressure_sensors = []
427
+
428
+ if "flow_sensors" in sensor_config.keys():
429
+ flow_sensors = sensor_config["flow_sensors"]
430
+ else:
431
+ flow_sensors = []
432
+
433
+ if "demand_sensors" in sensor_config.keys():
434
+ demand_sensors = sensor_config["demand_sensors"]
435
+ else:
436
+ demand_sensors = []
437
+
438
+ if "node_quality_sensors" in sensor_config.keys():
439
+ node_quality_sensors = sensor_config["node_quality_sensors"]
440
+ else:
441
+ node_quality_sensors = []
442
+
443
+ if "link_quality_sensors" in sensor_config.keys():
444
+ link_quality_sensors = sensor_config["link_quality_sensors"]
445
+ else:
446
+ link_quality_sensors = []
447
+
448
+ if "tank_volume_sensors" in sensor_config.keys():
449
+ tank_volume_sensors = sensor_config["tank_volume_sensors"]
450
+ else:
451
+ tank_volume_sensors = []
452
+
453
+ if "valve_state_sensors" in sensor_config.keys():
454
+ valve_state_sensors = sensor_config["valve_state_sensors"]
455
+ else:
456
+ valve_state_sensors = []
457
+
458
+ if "pump_state_sensors" in sensor_config.keys():
459
+ pump_state_sensors = sensor_config["pump_state_sensors"]
460
+ else:
461
+ pump_state_sensors = []
462
+
463
+ if "bulk_species_node_sensors" in sensor_config.keys():
464
+ bulk_species_node_sensors = sensor_config["bulk_species_node_sensors"]
465
+ else:
466
+ bulk_species_node_sensors = {}
467
+
468
+ if "bulk_species_link_sensors" in sensor_config.keys():
469
+ bulk_species_link_sensors = sensor_config["bulk_species_link_sensors"]
470
+ else:
471
+ bulk_species_link_sensors = {}
472
+
473
+ if "surface_species_sensors" in sensor_config.keys():
474
+ surface_species_sensors = sensor_config["surface_species_sensors"]
475
+ else:
476
+ surface_species_sensors = {}
477
+
478
+ # Uncertainties
479
+ if "uncertainties" in data.keys():
480
+ def parse_uncertantiy(uncertainty_desc: dict) -> Uncertainty:
481
+ uncertainty_type = uncertainty_desc["type"]
482
+ del uncertainty_desc["type"]
483
+
484
+ if uncertainty_type == "absolute_gaussian":
485
+ return AbsoluteGaussianUncertainty(**uncertainty_desc)
486
+ elif uncertainty_type == "relative_gaussian":
487
+ return RelativeGaussianUncertainty(**uncertainty_desc)
488
+ elif uncertainty_type == "absolute_uniform":
489
+ return AbsoluteUniformUncertainty(**uncertainty_desc)
490
+ elif uncertainty_type == "relative_uniform":
491
+ return RelativeUniformUncertainty(**uncertainty_desc)
492
+ else:
493
+ raise ValueError(f"Unknown uncertainty '{uncertainty_type}'")
494
+
495
+ uncertanties = data["uncertainties"]
496
+ if "pipe_length_uncertainty" in uncertanties.keys():
497
+ pipe_length_uncertainty = parse_uncertantiy(uncertanties["pipe_length_uncertainty"])
498
+ else:
499
+ pipe_length_uncertainty = None
500
+ if "pipe_roughness_uncertainty" in uncertanties.keys():
501
+ pipe_roughness_uncertainty = parse_uncertantiy(
502
+ uncertanties["pipe_roughness_uncertainty"])
503
+ else:
504
+ pipe_roughness_uncertainty = None
505
+ if "pipe_diameter_uncertainty" in uncertanties.keys():
506
+ pipe_diameter_uncertainty = parse_uncertantiy(
507
+ uncertanties["pipe_diameter_uncertainty"])
508
+ else:
509
+ pipe_diameter_uncertainty = None
510
+ if "demand_base_uncertainty" in uncertanties.keys():
511
+ demand_base_uncertainty = parse_uncertantiy(uncertanties["demand_base_uncertainty"])
512
+ else:
513
+ demand_base_uncertainty = None
514
+ if "demand_pattern_uncertainty" in uncertanties.keys():
515
+ demand_pattern_uncertainty = parse_uncertantiy(
516
+ uncertanties["demand_pattern_uncertainty"])
517
+ else:
518
+ demand_pattern_uncertainty = None
519
+ if "elevation_uncertainty" in uncertanties.keys():
520
+ elevation_uncertainty = parse_uncertantiy(uncertanties["elevation_uncertainty"])
521
+ else:
522
+ elevation_uncertainty = None
523
+ if "constants_uncertainty" in uncertanties.keys():
524
+ constants_uncertainty = parse_uncertantiy(uncertanties["constants_uncertainty"])
525
+ else:
526
+ constants_uncertainty = None
527
+ if "parameters_uncertainty" in uncertanties.keys():
528
+ parameters_uncertainty = parse_uncertantiy(uncertanties["parameters_uncertainty"])
529
+ else:
530
+ parameters_uncertainty = None
531
+
532
+ model_uncertainty = ModelUncertainty(pipe_length_uncertainty,
533
+ pipe_roughness_uncertainty,
534
+ pipe_diameter_uncertainty, demand_base_uncertainty,
535
+ demand_pattern_uncertainty, elevation_uncertainty,
536
+ constants_uncertainty, parameters_uncertainty)
537
+
538
+ if "sensor_noise" in uncertanties.keys():
539
+ sensor_noise = SensorNoise(parse_uncertantiy(uncertanties["sensor_noise"]))
540
+ else:
541
+ sensor_noise = None
542
+
543
+ # Events
544
+ leakages = []
545
+ if "leakages" in data.keys():
546
+ def parse_leak(leak_desc):
547
+ leak_type = leak_desc["type"]
548
+ del leak_desc["type"]
549
+
550
+ if leak_type == "abrupt":
551
+ return AbruptLeakage(**leak_desc)
552
+ elif leak_type == "incipient":
553
+ return IncipientLeakage(**leak_desc)
554
+ else:
555
+ raise ValueError(f"Unknown leakage type '{leak_type}'")
556
+
557
+ leakages = [parse_leak(leak) for leak in data["leakages"]]
558
+
559
+ sensor_faults = []
560
+ if "sensor_faults" in data.keys():
561
+ def parse_sensor_fault(sensor_fault_desc):
562
+ fault_type = sensor_fault_desc["type"]
563
+ del sensor_fault_desc["type"]
564
+
565
+ if fault_type == "constant":
566
+ return SensorFaultConstant(**sensor_fault_desc)
567
+ elif fault_type == "drift":
568
+ return SensorFaultDrift(**sensor_fault_desc)
569
+ elif fault_type == "gaussian":
570
+ return SensorFaultGaussian(**sensor_fault_desc)
571
+ elif fault_type == "percentage":
572
+ return SensorFaultPercentage(**sensor_fault_desc)
573
+ elif fault_type == "stuckatzero":
574
+ return SensorFaultStuckZero(**sensor_fault_desc)
575
+ else:
576
+ raise ValueError(f"Unknown sensor fault '{fault_type}'")
577
+
578
+ sensor_faults = [parse_sensor_fault(sensor_fault)
579
+ for sensor_fault in data["sensor_faults"]]
580
+
581
+ # Load .inp file to get a list of all nodes and links/pipes
582
+ sensor_config = None
583
+ from .scenario_simulator import ScenarioSimulator
584
+ with ScenarioSimulator(f_inp_in) as scenario:
585
+ sensor_config = SensorConfig(nodes=scenario.sensor_config.nodes,
586
+ links=scenario.sensor_config.links,
587
+ valves=scenario.sensor_config.valves,
588
+ pumps=scenario.sensor_config.pumps,
589
+ tanks=scenario.sensor_config.tanks,
590
+ bulk_species=scenario.sensor_config.bulk_species,
591
+ surface_species=scenario.sensor_config.surface_species,
592
+ pressure_sensors=pressure_sensors,
593
+ flow_sensors=flow_sensors,
594
+ demand_sensors=demand_sensors,
595
+ quality_node_sensors=node_quality_sensors,
596
+ quality_link_sensors=link_quality_sensors,
597
+ valve_state_sensors=valve_state_sensors,
598
+ pump_state_sensors=pump_state_sensors,
599
+ tank_volume_sensors=tank_volume_sensors,
600
+ bulk_species_node_sensors=bulk_species_node_sensors,
601
+ bulk_species_link_sensors=bulk_species_link_sensors,
602
+ surface_species_sensors=surface_species_sensors)
603
+
604
+ # Create final scenario configuration
605
+ return ScenarioConfig(f_inp_in=f_inp_in, f_msx_in=f_msx_in, general_params=general_params,
606
+ sensor_config=sensor_config, controls=[], sensor_noise=sensor_noise,
607
+ model_uncertainty=model_uncertainty, system_events=leakages,
608
+ sensor_reading_events=sensor_faults)