epyt-flow 0.8.1__py3-none-any.whl → 0.10.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/VERSION +1 -1
- epyt_flow/__init__.py +1 -0
- epyt_flow/data/benchmarks/batadal.py +1 -1
- epyt_flow/data/benchmarks/battledim.py +4 -3
- epyt_flow/data/benchmarks/gecco_water_quality.py +4 -4
- epyt_flow/data/benchmarks/leakdb.py +7 -7
- epyt_flow/data/benchmarks/water_usage.py +2 -2
- epyt_flow/data/networks.py +1 -1
- epyt_flow/gym/control_gyms.py +2 -2
- epyt_flow/gym/scenario_control_env.py +9 -1
- epyt_flow/metrics.py +28 -28
- epyt_flow/models/sensor_interpolation_detector.py +3 -3
- epyt_flow/rest_api/base_handler.py +4 -4
- epyt_flow/rest_api/scada_data/data_handlers.py +11 -11
- epyt_flow/rest_api/scada_data/export_handlers.py +2 -2
- epyt_flow/rest_api/scada_data/handlers.py +9 -9
- epyt_flow/rest_api/scenario/event_handlers.py +6 -6
- epyt_flow/rest_api/scenario/handlers.py +15 -15
- epyt_flow/rest_api/scenario/simulation_handlers.py +7 -7
- epyt_flow/rest_api/scenario/uncertainty_handlers.py +6 -6
- epyt_flow/serialization.py +8 -2
- epyt_flow/simulation/events/actuator_events.py +1 -1
- epyt_flow/simulation/events/leakages.py +1 -1
- epyt_flow/simulation/events/quality_events.py +16 -5
- epyt_flow/simulation/events/sensor_reading_attack.py +17 -4
- epyt_flow/simulation/events/sensor_reading_event.py +21 -6
- epyt_flow/simulation/events/system_event.py +1 -1
- epyt_flow/simulation/parallel_simulation.py +1 -1
- epyt_flow/simulation/scada/__init__.py +3 -1
- epyt_flow/simulation/scada/advanced_control.py +8 -4
- epyt_flow/simulation/scada/complex_control.py +625 -0
- epyt_flow/simulation/scada/custom_control.py +134 -0
- epyt_flow/simulation/scada/scada_data.py +133 -130
- epyt_flow/simulation/scada/scada_data_export.py +1 -1
- epyt_flow/simulation/scada/simple_control.py +317 -0
- epyt_flow/simulation/scenario_config.py +124 -24
- epyt_flow/simulation/scenario_simulator.py +514 -49
- epyt_flow/simulation/scenario_visualizer.py +9 -9
- epyt_flow/simulation/sensor_config.py +38 -28
- epyt_flow/topology.py +2 -2
- epyt_flow/uncertainty/model_uncertainty.py +624 -147
- epyt_flow/uncertainty/sensor_noise.py +94 -19
- epyt_flow/uncertainty/uncertainties.py +4 -4
- epyt_flow/uncertainty/utils.py +7 -7
- epyt_flow/utils.py +9 -8
- {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/LICENSE +1 -1
- {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/METADATA +7 -6
- {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/RECORD +50 -47
- {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/top_level.txt +0 -0
|
@@ -102,7 +102,7 @@ class ScadaDataExport():
|
|
|
102
102
|
|
|
103
103
|
Returns
|
|
104
104
|
-------
|
|
105
|
-
`numpy.ndarray
|
|
105
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
106
106
|
3-dimensional array describing all columns of the sensor readings:
|
|
107
107
|
The first dimension describes the sensor type, the second dimension
|
|
108
108
|
describes the sensor location, and the third one describes the measurement units.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The module contains classes for representing simple control rules as used in EPANET.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Union
|
|
5
|
+
from epyt.epanet import ToolkitConstants
|
|
6
|
+
|
|
7
|
+
from ..events import ActuatorConstants
|
|
8
|
+
from ...serialization import JsonSerializable, SIMPLE_CONTROL_ID, serializable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@serializable(SIMPLE_CONTROL_ID, ".epytflow_simple_control")
|
|
12
|
+
class SimpleControlModule(JsonSerializable):
|
|
13
|
+
"""
|
|
14
|
+
A class for representing a simple EPANET control rule.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
link_id : `str`
|
|
19
|
+
Link ID.
|
|
20
|
+
link_status : `int` or `float`
|
|
21
|
+
Status of the link that is set when the condition is fullfilled.
|
|
22
|
+
|
|
23
|
+
Instance of `float` if the link constitutes a pump -- in this case,
|
|
24
|
+
the argument corresponds to the pump speed.
|
|
25
|
+
|
|
26
|
+
Instance of `int` if the link constitutes a valve -- in this case,
|
|
27
|
+
must be one of the followig costants defined in
|
|
28
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
29
|
+
|
|
30
|
+
- EN_CLOSED = 0
|
|
31
|
+
- EN_OPEN = 1
|
|
32
|
+
cond_type : `int`
|
|
33
|
+
Condition/Rule type.
|
|
34
|
+
|
|
35
|
+
Must be one of the following EPANET constants:
|
|
36
|
+
|
|
37
|
+
- EN_LOWLEVEL = 0
|
|
38
|
+
- EN_HILEVEL = 1
|
|
39
|
+
- EN_TIMER = 2
|
|
40
|
+
- EN_TIMEOFDAY = 3
|
|
41
|
+
cond_var_value : `str` or `int`
|
|
42
|
+
Condition/Rule variable value.
|
|
43
|
+
|
|
44
|
+
Node ID in the cases of EN_LOWLEVEL or EN_HILEVEL.
|
|
45
|
+
Time of the day (in AM/PM format) in the case of EN_TIMEOFDAY.
|
|
46
|
+
Number of hours (as an integer) since simulation start in the case of EN_TIMER.
|
|
47
|
+
cond_comp_value : `float`
|
|
48
|
+
The condition/rule comparison value at which this control rule is triggered.
|
|
49
|
+
|
|
50
|
+
Lower or upper value on the pressure (or tank level) in the cases of
|
|
51
|
+
EN_LOWLEVEL and EN_HILEVEL.
|
|
52
|
+
|
|
53
|
+
Will be ignored in all other cases -- i.e. should be set to None.
|
|
54
|
+
"""
|
|
55
|
+
def __init__(self, link_id: str, link_status: Union[int, float], cond_type: int,
|
|
56
|
+
cond_var_value: Union[str, int], cond_comp_value: float,
|
|
57
|
+
**kwds):
|
|
58
|
+
if not isinstance(link_id, str):
|
|
59
|
+
raise TypeError(f"'link_id' must be an instance of 'str' but not of '{type(link_id)}'")
|
|
60
|
+
if isinstance(link_status, int):
|
|
61
|
+
if link_status not in [ActuatorConstants.EN_OPEN, ActuatorConstants.EN_CLOSED]:
|
|
62
|
+
raise ValueError(f"Invalid link status {link_status} in 'link_status'")
|
|
63
|
+
elif isinstance(link_status, float):
|
|
64
|
+
if link_status < 0:
|
|
65
|
+
raise TypeError("'link_status' can not be negative")
|
|
66
|
+
else:
|
|
67
|
+
raise TypeError("'link_status' must be an instance of 'int' or 'float' but not " +
|
|
68
|
+
f"of '{type(link_status)}'")
|
|
69
|
+
if cond_type not in [ToolkitConstants.EN_TIMEOFDAY, ToolkitConstants.EN_TIMER,
|
|
70
|
+
ToolkitConstants.EN_LOWLEVEL, ToolkitConstants.EN_HILEVEL]:
|
|
71
|
+
raise ValueError(f"Invalid control type '{cond_type}' in 'cond_type'")
|
|
72
|
+
|
|
73
|
+
if cond_type == ToolkitConstants.EN_TIMEOFDAY:
|
|
74
|
+
if not isinstance(cond_var_value, str):
|
|
75
|
+
raise TypeError("EN_TIMEOFDAY requires that 'cond_var_value' must be an instance " +
|
|
76
|
+
f"of 'str' but not of '{type(cond_var_value)}'")
|
|
77
|
+
if not cond_var_value.endswith("AM") and not cond_var_value.endswith("PM"):
|
|
78
|
+
raise ValueError(f"Invalid time of day format '{cond_var_value}' in " +
|
|
79
|
+
"'cond_var_value'")
|
|
80
|
+
elif cond_type == ToolkitConstants.EN_TIMER:
|
|
81
|
+
if not isinstance(cond_var_value, int):
|
|
82
|
+
raise TypeError("EN_TIMER requires that 'cond_var_value' must be an instance " +
|
|
83
|
+
f"of 'int' but not of '{type(cond_var_value)}'")
|
|
84
|
+
if cond_var_value < 0:
|
|
85
|
+
raise ValueError("'cond_var_value' can not be negative")
|
|
86
|
+
else:
|
|
87
|
+
if not isinstance(cond_var_value, str):
|
|
88
|
+
raise TypeError("'cond_var_value' must be an instance of 'str' but " +
|
|
89
|
+
f"not of '{type(cond_var_value)}'")
|
|
90
|
+
if not isinstance(cond_comp_value, float):
|
|
91
|
+
raise TypeError("'cond_comp_value' must be an instance of 'float' " +
|
|
92
|
+
f"but not of '{type(cond_comp_value)}'")
|
|
93
|
+
if cond_comp_value < 0:
|
|
94
|
+
raise ValueError("'cond_comp_value' can not be negative")
|
|
95
|
+
|
|
96
|
+
self.__link_id = link_id
|
|
97
|
+
self.__link_status = link_status
|
|
98
|
+
self.__cond_type = cond_type
|
|
99
|
+
self.__cond_var_value = cond_var_value
|
|
100
|
+
self.__cond_comp_value = cond_comp_value
|
|
101
|
+
|
|
102
|
+
super().__init__(**kwds)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def link_id(self) -> str:
|
|
106
|
+
"""
|
|
107
|
+
Returns the link ID.
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
`str`
|
|
112
|
+
Link ID.
|
|
113
|
+
"""
|
|
114
|
+
return self.__link_id
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def link_status(self) -> Union[int, float]:
|
|
118
|
+
"""
|
|
119
|
+
Returns the link status that is set when the condition is fullfilled.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
`int` or `float`
|
|
124
|
+
Pump speed if the link is a pump, otherwise one of the followig costants defined in
|
|
125
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
126
|
+
|
|
127
|
+
- EN_CLOSED = 0
|
|
128
|
+
- EN_OPEN = 1
|
|
129
|
+
"""
|
|
130
|
+
return self.__link_status
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def cond_type(self) -> int:
|
|
134
|
+
"""
|
|
135
|
+
Returns the condition/rule type.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
`int`
|
|
140
|
+
Condition/Rule type -- will be one of the following EPANET constants:
|
|
141
|
+
|
|
142
|
+
- EN_LOWLEVEL = 0
|
|
143
|
+
- EN_HILEVEL = 1
|
|
144
|
+
- EN_TIMER = 2
|
|
145
|
+
- EN_TIMEOFDAY = 3
|
|
146
|
+
"""
|
|
147
|
+
return self.__cond_type
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def cond_var_value(self) -> Union[str, int]:
|
|
151
|
+
"""
|
|
152
|
+
Return the condition/rule variable value.
|
|
153
|
+
|
|
154
|
+
Node ID in the cases of EN_LOWLEVEL or EN_HILEVEL.
|
|
155
|
+
Time of the day (in AM/PM format) in the case of EN_TIMEOFDAY.
|
|
156
|
+
Number of hours (as an integer) since simulation start in the case of EN_TIMER.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
`str` or `int`
|
|
161
|
+
Condition/rule variable value.
|
|
162
|
+
"""
|
|
163
|
+
return self.__cond_var_value
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def cond_comp_value(self) -> float:
|
|
167
|
+
"""
|
|
168
|
+
Returns the condition/rule comparison value -- might be None if not needed.
|
|
169
|
+
|
|
170
|
+
Lower or upper value on the pressure (or tank level) in the cases of
|
|
171
|
+
EN_LOWLEVEL and EN_HILEVEL.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
`float`
|
|
176
|
+
Condition/Rule comparison value.
|
|
177
|
+
"""
|
|
178
|
+
return self.__cond_comp_value
|
|
179
|
+
|
|
180
|
+
def get_attributes(self) -> dict:
|
|
181
|
+
return super().get_attributes() | {"link_id": self.__link_id,
|
|
182
|
+
"link_status": self.__link_status,
|
|
183
|
+
"cond_type": self.__cond_type,
|
|
184
|
+
"cond_var_value": self.__cond_var_value,
|
|
185
|
+
"cond_comp_value": self.__cond_comp_value}
|
|
186
|
+
|
|
187
|
+
def __eq__(self, other) -> bool:
|
|
188
|
+
return super().__eq__(other) and self.__link_id == other.link_id and \
|
|
189
|
+
self.__link_status == other.link_status and self.__cond_type == other.cond_type and \
|
|
190
|
+
self.__cond_var_value == other.cond_var_value and \
|
|
191
|
+
self.__cond_comp_value == other.cond_comp_value
|
|
192
|
+
|
|
193
|
+
def __str__(self) -> str:
|
|
194
|
+
control_rule_str = f"LINK {self.__link_id} "
|
|
195
|
+
if isinstance(self.__link_status, int):
|
|
196
|
+
control_rule_str += "OPEN " if self.__link_status == ActuatorConstants.EN_OPEN \
|
|
197
|
+
else "CLOSED "
|
|
198
|
+
else:
|
|
199
|
+
control_rule_str += f"{self.__link_status} "
|
|
200
|
+
|
|
201
|
+
if self.__cond_type == ToolkitConstants.EN_TIMER:
|
|
202
|
+
control_rule_str += f"AT TIME {self.__cond_var_value}"
|
|
203
|
+
elif self.__cond_type == ToolkitConstants.EN_TIMEOFDAY:
|
|
204
|
+
control_rule_str += f"AT CLOCKTIME {self.__cond_var_value}"
|
|
205
|
+
elif self.__cond_type == ToolkitConstants.EN_LOWLEVEL:
|
|
206
|
+
control_rule_str += f"IF NODE {self.__cond_var_value} BELOW {self.__cond_comp_value}"
|
|
207
|
+
elif self.__cond_type == ToolkitConstants.EN_HILEVEL:
|
|
208
|
+
control_rule_str += f"IF NODE {self.__cond_var_value} ABOVE {self.__cond_comp_value}"
|
|
209
|
+
|
|
210
|
+
return control_rule_str
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class SimplePumpSpeedTimeControl(SimpleControlModule):
|
|
214
|
+
"""
|
|
215
|
+
A class for representing a simple control rule for setting the pump speed at some point in time.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
pump_id : `str`
|
|
220
|
+
Pump ID.
|
|
221
|
+
pump_speed : `float`
|
|
222
|
+
Pump speed.
|
|
223
|
+
time : `str` or `int`
|
|
224
|
+
Time of the day (in AM/PM format) in the case or
|
|
225
|
+
number of hours (as an integer) since simulation start.
|
|
226
|
+
"""
|
|
227
|
+
def __init__(self, pump_id: str, pump_speed: float, time: Union[str, int]):
|
|
228
|
+
super().__init__(link_id=pump_id, link_status=pump_speed,
|
|
229
|
+
cond_type=ToolkitConstants.EN_TIMER if isinstance(time, int)
|
|
230
|
+
else ToolkitConstants.EN_TIMEOFDAY,
|
|
231
|
+
cond_var_value=time, cond_comp_value=None)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class SimplePumpSpeedConditionControl(SimpleControlModule):
|
|
235
|
+
"""
|
|
236
|
+
A class for representing a simple IF-THEN control rule for setting the pump speed.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
pump_id : `str`
|
|
243
|
+
Pump ID.
|
|
244
|
+
pump_speed : `float`
|
|
245
|
+
Pump speed.
|
|
246
|
+
node_id : `str`
|
|
247
|
+
Node ID.
|
|
248
|
+
comp_type : `int`
|
|
249
|
+
Comparison type -- must be one of the following EPANET constants:
|
|
250
|
+
|
|
251
|
+
- EN_LOWLEVEL = 0
|
|
252
|
+
- EN_HILEVEL = 1
|
|
253
|
+
comp_value : `float`:
|
|
254
|
+
Lower or upper value on the pressure (or tank level) at which this
|
|
255
|
+
control rule is triggered.
|
|
256
|
+
"""
|
|
257
|
+
def __init__(self, pump_id: str, pump_speed: float, node_id: str, comp_type: int,
|
|
258
|
+
comp_value: float):
|
|
259
|
+
super().__init__(link_id=pump_id, link_status=pump_speed, cond_type=comp_type,
|
|
260
|
+
cond_var_value=node_id, cond_comp_value=comp_value)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class SimpleValveTimeControl(SimpleControlModule):
|
|
264
|
+
"""
|
|
265
|
+
A class for representing a simple control rule for setting the valve status
|
|
266
|
+
at some point in time.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
valve_id : `str`
|
|
271
|
+
valve ID.
|
|
272
|
+
valve_status : `int`
|
|
273
|
+
Valve status -- must be one of the followig costants defined in
|
|
274
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
275
|
+
|
|
276
|
+
- EN_CLOSED = 0
|
|
277
|
+
- EN_OPEN = 1
|
|
278
|
+
time : `str` or `int`
|
|
279
|
+
Time of the day (in AM/PM format) in the case or
|
|
280
|
+
number of hours (as an integer) since simulation start.
|
|
281
|
+
"""
|
|
282
|
+
def __init__(self, valve_id: str, valve_status: int, time: Union[str, int]):
|
|
283
|
+
super().__init__(link_id=valve_id, link_status=valve_status,
|
|
284
|
+
cond_type=ToolkitConstants.EN_TIMER if isinstance(time, int)
|
|
285
|
+
else ToolkitConstants.EN_TIMEOFDAY,
|
|
286
|
+
cond_var_value=time, cond_comp_value=None)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class SimpleValveConditionControl(SimpleControlModule):
|
|
290
|
+
"""
|
|
291
|
+
A class for representing a simple IF-THEN control rule for setting the valve status.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
valve_id : `str`
|
|
296
|
+
valve ID.
|
|
297
|
+
valve_status : `int`
|
|
298
|
+
Valve status -- must be one of the followig costants defined in
|
|
299
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
300
|
+
|
|
301
|
+
- EN_CLOSED = 0
|
|
302
|
+
- EN_OPEN = 1
|
|
303
|
+
node_id : `str`
|
|
304
|
+
Node ID.
|
|
305
|
+
comp_type : `int`
|
|
306
|
+
Comparison type -- must be one of the following EPANET constants:
|
|
307
|
+
|
|
308
|
+
- EN_LOWLEVEL = 0
|
|
309
|
+
- EN_HILEVEL = 1
|
|
310
|
+
comp_value : `float`:
|
|
311
|
+
Lower or upper value on the pressure (or tank level) at which this
|
|
312
|
+
control rule is triggered.
|
|
313
|
+
"""
|
|
314
|
+
def __init__(self, valve_id: str, valve_status: int, node_id: str, comp_type: int,
|
|
315
|
+
comp_value: float):
|
|
316
|
+
super().__init__(link_id=valve_id, link_status=valve_status, cond_type=comp_type,
|
|
317
|
+
cond_var_value=node_id, cond_comp_value=comp_value)
|
|
@@ -3,16 +3,17 @@ Module provides a class for specifying scenario configurations.
|
|
|
3
3
|
"""
|
|
4
4
|
from typing import Any
|
|
5
5
|
from copy import deepcopy
|
|
6
|
+
import warnings
|
|
6
7
|
import os
|
|
7
8
|
import json
|
|
8
|
-
import numpy as np
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
import numpy as np
|
|
10
11
|
|
|
11
12
|
from ..uncertainty import AbsoluteGaussianUncertainty, RelativeGaussianUncertainty, \
|
|
12
13
|
AbsoluteUniformUncertainty, RelativeUniformUncertainty, ModelUncertainty, \
|
|
13
14
|
SensorNoise, Uncertainty
|
|
14
15
|
from .sensor_config import SensorConfig
|
|
15
|
-
from .scada import
|
|
16
|
+
from .scada import CustomControlModule, SimpleControlModule, ComplexControlModule
|
|
16
17
|
from .events import SystemEvent, SensorReadingEvent
|
|
17
18
|
from .events.sensor_faults import SensorFaultConstant, SensorFaultDrift, SensorFaultGaussian, \
|
|
18
19
|
SensorFaultPercentage, SensorFaultStuckZero
|
|
@@ -63,8 +64,16 @@ class ScenarioConfig(Serializable):
|
|
|
63
64
|
Speciation of sensor noise -- i.e. noise/uncertainty affecting the sensor readings.
|
|
64
65
|
|
|
65
66
|
The default is None
|
|
66
|
-
|
|
67
|
-
List of control modules that are active during the simulation.
|
|
67
|
+
csutom_controls : list[:class:`~epyt_flow.simulation.scada.custom_control.CustomControlModule`], optional
|
|
68
|
+
List of custom control modules that are active during the simulation.
|
|
69
|
+
|
|
70
|
+
The default is an empty list.
|
|
71
|
+
simple_controls : list[:class:`~epyt_flow.simulation.scada.simple_control.SimpleControlModule`], optional
|
|
72
|
+
List of EPANET control rules that are active during the simulation.
|
|
73
|
+
|
|
74
|
+
The default is an empty list.
|
|
75
|
+
complex_controls : list[:class:`~epyt_flow.simulation.scada.complex_control.ComplexControlModule`], optional
|
|
76
|
+
List of complex (i.e. IF-THEN-ELSE) EPANET control rules that are active during the simulation.
|
|
68
77
|
|
|
69
78
|
The default is an empty list.
|
|
70
79
|
model_uncertainty : :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`, optional
|
|
@@ -82,11 +91,22 @@ class ScenarioConfig(Serializable):
|
|
|
82
91
|
def __init__(self, scenario_config: Any = None, f_inp_in: str = None, f_msx_in: str = None,
|
|
83
92
|
general_params: dict = None, sensor_config: SensorConfig = None,
|
|
84
93
|
memory_consumption_estimate: float = None,
|
|
85
|
-
controls: list
|
|
94
|
+
controls: list = None,
|
|
95
|
+
advanced_controls: list = None,
|
|
96
|
+
custom_controls: list[CustomControlModule] = [],
|
|
97
|
+
simple_controls: list[SimpleControlModule] = [],
|
|
98
|
+
complex_controls: list[ComplexControlModule] = [],
|
|
86
99
|
sensor_noise: SensorNoise = None,
|
|
87
100
|
model_uncertainty: ModelUncertainty = None,
|
|
88
101
|
system_events: list[SystemEvent] = [],
|
|
89
102
|
sensor_reading_events: list[SensorReadingEvent] = [], **kwds):
|
|
103
|
+
if controls is not None:
|
|
104
|
+
warnings.warn("'controls' is deprecated and will be removed in future releases")
|
|
105
|
+
advanced_controls = controls
|
|
106
|
+
if advanced_controls is not None:
|
|
107
|
+
warnings.warn("'advanced_controls' is deprecated and will be removed in " +
|
|
108
|
+
"future releases -- use 'custom_controls' instead")
|
|
109
|
+
|
|
90
110
|
if f_inp_in is None and scenario_config is None:
|
|
91
111
|
raise ValueError("Either 'f_inp_in' or 'scenario_config' must be given")
|
|
92
112
|
if scenario_config is not None:
|
|
@@ -115,14 +135,32 @@ class ScenarioConfig(Serializable):
|
|
|
115
135
|
if not isinstance(memory_consumption_estimate, float) or \
|
|
116
136
|
memory_consumption_estimate <= 0:
|
|
117
137
|
raise ValueError("'memory_consumption_estimate' must be a positive integer")
|
|
118
|
-
if not
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
if advanced_controls is not None:
|
|
139
|
+
if not isinstance(advanced_controls, list):
|
|
140
|
+
raise TypeError("'advanced_controls' must be an instance of " +
|
|
141
|
+
"'list[epyt_flow.simulation.scada.AdvancedControlModule]' but no of " +
|
|
142
|
+
f"'{type(advanced_controls)}'")
|
|
143
|
+
|
|
144
|
+
from .scada.advanced_control import AdvancedControlModule
|
|
145
|
+
if any(not isinstance(c, AdvancedControlModule) for c in advanced_controls):
|
|
146
|
+
raise TypeError("Each item in 'advanced_controls' must be an instance of " +
|
|
125
147
|
"'epyt_flow.simulation.scada.AdvancedControlModule'")
|
|
148
|
+
if len(custom_controls) != 0:
|
|
149
|
+
if any(not isinstance(c, CustomControlModule) for c in custom_controls):
|
|
150
|
+
raise TypeError("Each item in 'custom_controls' must be an instance of " +
|
|
151
|
+
"'epyt_flow.simulation.scada.CustomControlModule'")
|
|
152
|
+
if not isinstance(simple_controls, list):
|
|
153
|
+
raise TypeError("'simple_controls' must be an instance of " +
|
|
154
|
+
"'list[epyt_flow.simulation.scada.SimpleControlModule]' but no of " +
|
|
155
|
+
f"'{type(simple_controls)}'")
|
|
156
|
+
if len(simple_controls) != 0:
|
|
157
|
+
if any(not isinstance(c, SimpleControlModule) for c in simple_controls):
|
|
158
|
+
raise TypeError("Each item in 'simple_controls' must be an instance of " +
|
|
159
|
+
"'epyt_flow.simulation.scada.SimppleControlModule'")
|
|
160
|
+
if len(complex_controls) != 0:
|
|
161
|
+
if any(not isinstance(c, ComplexControlModule) for c in complex_controls):
|
|
162
|
+
raise TypeError("Each item in 'complex_controls' must be an instance of " +
|
|
163
|
+
"'epyt_flow.simulation.scada.ComplexControlModule'")
|
|
126
164
|
if sensor_noise is not None:
|
|
127
165
|
if not isinstance(sensor_noise, SensorNoise):
|
|
128
166
|
raise TypeError("'sensor_noise' must be an instance of " +
|
|
@@ -169,10 +207,25 @@ class ScenarioConfig(Serializable):
|
|
|
169
207
|
else:
|
|
170
208
|
self.__memory_consumption_estimate = memory_consumption_estimate
|
|
171
209
|
|
|
172
|
-
|
|
173
|
-
|
|
210
|
+
self.__advanced_controls = advanced_controls
|
|
211
|
+
if advanced_controls is not None:
|
|
212
|
+
if len(advanced_controls) == 0:
|
|
213
|
+
self.__advanced_controls = scenario_config.advanced_controls
|
|
214
|
+
|
|
215
|
+
if len(custom_controls) == 0:
|
|
216
|
+
self.__custom_controls = scenario_config.custom_controls
|
|
174
217
|
else:
|
|
175
|
-
self.
|
|
218
|
+
self.__custom_controls = custom_controls
|
|
219
|
+
|
|
220
|
+
if len(simple_controls) == 0:
|
|
221
|
+
self.__simple_controls = scenario_config.simple_controls
|
|
222
|
+
else:
|
|
223
|
+
self.__simple_controls = simple_controls
|
|
224
|
+
|
|
225
|
+
if len(complex_controls) == 0:
|
|
226
|
+
self.__complex_controls = scenario_config.complex_controls
|
|
227
|
+
else:
|
|
228
|
+
self.__complex_controls = complex_controls
|
|
176
229
|
|
|
177
230
|
if sensor_noise is None:
|
|
178
231
|
self.__sensor_noise = scenario_config.sensor_noise
|
|
@@ -199,7 +252,10 @@ class ScenarioConfig(Serializable):
|
|
|
199
252
|
self.__general_params = general_params
|
|
200
253
|
self.__sensor_config = sensor_config
|
|
201
254
|
self.__memory_consumption_estimate = memory_consumption_estimate
|
|
202
|
-
self.
|
|
255
|
+
self.__advanced_controls = advanced_controls
|
|
256
|
+
self.__custom_controls = custom_controls
|
|
257
|
+
self.__simple_controls = simple_controls
|
|
258
|
+
self.__complex_controls = complex_controls
|
|
203
259
|
self.__sensor_noise = sensor_noise
|
|
204
260
|
self.__system_events = system_events
|
|
205
261
|
self.__sensor_reading_events = sensor_reading_events
|
|
@@ -286,16 +342,53 @@ class ScenarioConfig(Serializable):
|
|
|
286
342
|
return self.__memory_consumption_estimate
|
|
287
343
|
|
|
288
344
|
@property
|
|
289
|
-
def
|
|
345
|
+
def advanced_controls(self) -> list:
|
|
290
346
|
"""
|
|
291
|
-
Gets the list of all control modules that are active during the simulation.
|
|
347
|
+
Gets the list of all (advanced) control modules that are active during the simulation.
|
|
292
348
|
|
|
293
349
|
Returns
|
|
294
350
|
-------
|
|
295
351
|
list[:class:`~epyt_flow.simulation.scada.advanced_control.AdvancedControlModule`]
|
|
296
352
|
List of all control modules that are active during the simulation.
|
|
297
353
|
"""
|
|
298
|
-
return deepcopy(self.
|
|
354
|
+
return deepcopy(self.__advanced_controls)
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def custom_controls(self) -> list[CustomControlModule]:
|
|
358
|
+
"""
|
|
359
|
+
Returns the list of all custom control modules that are active during the simulation.
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
list[:class:`~epyt_flow.simulation.scada.custom_control.CustomControlModule`]
|
|
364
|
+
List of all custom control modules that are active during the simulation.
|
|
365
|
+
"""
|
|
366
|
+
return deepcopy(self.__custom_controls)
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def simple_controls(self) -> list[SimpleControlModule]:
|
|
370
|
+
"""
|
|
371
|
+
Gets the list of all EPANET control rules that are active during the simulation.
|
|
372
|
+
|
|
373
|
+
Returns
|
|
374
|
+
-------
|
|
375
|
+
list[:class:`~epyt_flow.simulation.scada.simple_control.SimpleControlModule`]
|
|
376
|
+
List of all EPANET control rules that are active during the simulation.
|
|
377
|
+
"""
|
|
378
|
+
return deepcopy(self.__simple_controls)
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def complex_controls(self) -> list[ComplexControlModule]:
|
|
382
|
+
"""
|
|
383
|
+
Gets the list of all complex (i.e. IF-THEN-ELSE) EPANET control rules
|
|
384
|
+
that are active during the simulation.
|
|
385
|
+
|
|
386
|
+
Returns
|
|
387
|
+
-------
|
|
388
|
+
list[:class:`~epyt_flow.simulation.scada.complex_control.ComplexControlModule`]
|
|
389
|
+
List of all complex EPANET control rules that are active during the simulation.
|
|
390
|
+
"""
|
|
391
|
+
return deepcopy(self.__complex_controls)
|
|
299
392
|
|
|
300
393
|
@property
|
|
301
394
|
def sensor_noise(self) -> SensorNoise:
|
|
@@ -350,7 +443,9 @@ class ScenarioConfig(Serializable):
|
|
|
350
443
|
"general_params": self.__general_params,
|
|
351
444
|
"sensor_config": self.__sensor_config,
|
|
352
445
|
"memory_consumption_estimate": self.__memory_consumption_estimate,
|
|
353
|
-
"
|
|
446
|
+
"custom_controls": self.__custom_controls,
|
|
447
|
+
"simple_controls": self.__simple_controls,
|
|
448
|
+
"complex_controls": self.__complex_controls,
|
|
354
449
|
"sensor_noise": self.__sensor_noise,
|
|
355
450
|
"model_uncertainty": self.__model_uncertainty,
|
|
356
451
|
"system_events": self.__system_events,
|
|
@@ -367,7 +462,10 @@ class ScenarioConfig(Serializable):
|
|
|
367
462
|
and self.__general_params == other.general_params \
|
|
368
463
|
and self.__memory_consumption_estimate == other.memory_consumption_estimate \
|
|
369
464
|
and self.__sensor_config == other.sensor_config \
|
|
370
|
-
and np.all(self.
|
|
465
|
+
and np.all(self.__advanced_controls == other.advanced_controls) \
|
|
466
|
+
and np.all(self.__custom_controls == other.custom_controls) \
|
|
467
|
+
and np.all(self.__simple_controls == other.simple_controls) \
|
|
468
|
+
and np.all(self.__complex_controls == other.complex_controls) \
|
|
371
469
|
and self.__model_uncertainty == other.model_uncertainty \
|
|
372
470
|
and np.all(self.__system_events == other.system_events) \
|
|
373
471
|
and np.all(self.__sensor_reading_events == other.sensor_reading_events)
|
|
@@ -376,8 +474,10 @@ class ScenarioConfig(Serializable):
|
|
|
376
474
|
return f"f_inp_in: {self.f_inp_in} f_msx_in: {self.f_msx_in} " + \
|
|
377
475
|
f"general_params: {self.general_params} sensor_config: {self.sensor_config} " + \
|
|
378
476
|
f"memory_consumption_estimate: {self.memory_consumption_estimate} " + \
|
|
379
|
-
f"
|
|
380
|
-
f"
|
|
477
|
+
f"advanced_controls: {self.advanced_controls} simple_controls: {self.simple_controls} " + \
|
|
478
|
+
f"complex_controls: {self.__complex_controls} " + \
|
|
479
|
+
f"custom_controls: {self.__custom_controls}" + \
|
|
480
|
+
f"sensor_noise: {self.sensor_noise} model_uncertainty: {self.model_uncertainty} " + \
|
|
381
481
|
f"system_events: {','.join(map(str, self.system_events))} " + \
|
|
382
482
|
f"sensor_reading_events: {','.join(map(str, self.sensor_reading_events))}"
|
|
383
483
|
|
|
@@ -614,6 +714,6 @@ class ScenarioConfig(Serializable):
|
|
|
614
714
|
|
|
615
715
|
# Create final scenario configuration
|
|
616
716
|
return ScenarioConfig(f_inp_in=f_inp_in, f_msx_in=f_msx_in, general_params=general_params,
|
|
617
|
-
sensor_config=sensor_config,
|
|
717
|
+
sensor_config=sensor_config, sensor_noise=sensor_noise,
|
|
618
718
|
model_uncertainty=model_uncertainty, system_events=leakages,
|
|
619
719
|
sensor_reading_events=sensor_faults)
|