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.
- epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +21 -0
- epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +18 -0
- epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +134 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +5578 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +865 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +131 -0
- epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +73 -0
- epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +193 -0
- epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +1000 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +1151 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +1117 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +720 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +476 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +431 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +1786 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +468 -0
- epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +810 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input1.c +707 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input2.c +864 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input3.c +2170 -0
- epyt_flow/EPANET/EPANET/SRC_engines/main.c +93 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +142 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +24 -0
- epyt_flow/EPANET/EPANET/SRC_engines/output.c +852 -0
- epyt_flow/EPANET/EPANET/SRC_engines/project.c +1359 -0
- epyt_flow/EPANET/EPANET/SRC_engines/quality.c +685 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +743 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +694 -0
- epyt_flow/EPANET/EPANET/SRC_engines/report.c +1489 -0
- epyt_flow/EPANET/EPANET/SRC_engines/rules.c +1362 -0
- epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +871 -0
- epyt_flow/EPANET/EPANET/SRC_engines/text.h +497 -0
- epyt_flow/EPANET/EPANET/SRC_engines/types.h +874 -0
- epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +53 -0
- epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +27 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.c +107 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.h +28 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +102 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +937 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +204 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +24 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +1285 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +368 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +586 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +116 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +260 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +175 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +1504 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +401 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +791 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +2010 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +400 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +422 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +1164 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +551 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +524 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +56 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.c +158 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.h +34 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +287 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +293 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +816 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +29 -0
- epyt_flow/EPANET/EPANET-MSX/readme.txt +14 -0
- epyt_flow/EPANET/compile.sh +4 -0
- epyt_flow/VERSION +1 -0
- epyt_flow/__init__.py +24 -0
- epyt_flow/data/__init__.py +0 -0
- epyt_flow/data/benchmarks/__init__.py +11 -0
- epyt_flow/data/benchmarks/batadal.py +257 -0
- epyt_flow/data/benchmarks/batadal_data.py +28 -0
- epyt_flow/data/benchmarks/battledim.py +473 -0
- epyt_flow/data/benchmarks/battledim_data.py +51 -0
- epyt_flow/data/benchmarks/gecco_water_quality.py +267 -0
- epyt_flow/data/benchmarks/leakdb.py +592 -0
- epyt_flow/data/benchmarks/leakdb_data.py +18923 -0
- epyt_flow/data/benchmarks/water_usage.py +123 -0
- epyt_flow/data/networks.py +650 -0
- epyt_flow/gym/__init__.py +4 -0
- epyt_flow/gym/control_gyms.py +47 -0
- epyt_flow/gym/scenario_control_env.py +101 -0
- epyt_flow/metrics.py +404 -0
- epyt_flow/models/__init__.py +2 -0
- epyt_flow/models/event_detector.py +31 -0
- epyt_flow/models/sensor_interpolation_detector.py +118 -0
- epyt_flow/rest_api/__init__.py +4 -0
- epyt_flow/rest_api/base_handler.py +70 -0
- epyt_flow/rest_api/res_manager.py +95 -0
- epyt_flow/rest_api/scada_data_handler.py +476 -0
- epyt_flow/rest_api/scenario_handler.py +352 -0
- epyt_flow/rest_api/server.py +106 -0
- epyt_flow/serialization.py +438 -0
- epyt_flow/simulation/__init__.py +5 -0
- epyt_flow/simulation/events/__init__.py +6 -0
- epyt_flow/simulation/events/actuator_events.py +259 -0
- epyt_flow/simulation/events/event.py +81 -0
- epyt_flow/simulation/events/leakages.py +404 -0
- epyt_flow/simulation/events/sensor_faults.py +267 -0
- epyt_flow/simulation/events/sensor_reading_attack.py +185 -0
- epyt_flow/simulation/events/sensor_reading_event.py +170 -0
- epyt_flow/simulation/events/system_event.py +88 -0
- epyt_flow/simulation/parallel_simulation.py +147 -0
- epyt_flow/simulation/scada/__init__.py +3 -0
- epyt_flow/simulation/scada/advanced_control.py +134 -0
- epyt_flow/simulation/scada/scada_data.py +1589 -0
- epyt_flow/simulation/scada/scada_data_export.py +255 -0
- epyt_flow/simulation/scenario_config.py +608 -0
- epyt_flow/simulation/scenario_simulator.py +1897 -0
- epyt_flow/simulation/scenario_visualizer.py +61 -0
- epyt_flow/simulation/sensor_config.py +1289 -0
- epyt_flow/topology.py +290 -0
- epyt_flow/uncertainty/__init__.py +3 -0
- epyt_flow/uncertainty/model_uncertainty.py +302 -0
- epyt_flow/uncertainty/sensor_noise.py +73 -0
- epyt_flow/uncertainty/uncertainties.py +555 -0
- epyt_flow/uncertainty/utils.py +206 -0
- epyt_flow/utils.py +306 -0
- epyt_flow-0.1.0.dist-info/LICENSE +21 -0
- epyt_flow-0.1.0.dist-info/METADATA +139 -0
- epyt_flow-0.1.0.dist-info/RECORD +131 -0
- epyt_flow-0.1.0.dist-info/WHEEL +5 -0
- 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)
|