epyt-flow 0.9.0__py3-none-any.whl → 0.11.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 (35) hide show
  1. epyt_flow/VERSION +1 -1
  2. epyt_flow/data/networks.py +27 -14
  3. epyt_flow/gym/control_gyms.py +8 -0
  4. epyt_flow/gym/scenario_control_env.py +17 -4
  5. epyt_flow/metrics.py +5 -0
  6. epyt_flow/models/event_detector.py +5 -0
  7. epyt_flow/models/sensor_interpolation_detector.py +5 -0
  8. epyt_flow/serialization.py +5 -0
  9. epyt_flow/simulation/__init__.py +0 -1
  10. epyt_flow/simulation/events/actuator_events.py +7 -1
  11. epyt_flow/simulation/events/sensor_reading_attack.py +16 -3
  12. epyt_flow/simulation/events/sensor_reading_event.py +18 -3
  13. epyt_flow/simulation/scada/__init__.py +3 -1
  14. epyt_flow/simulation/scada/advanced_control.py +6 -2
  15. epyt_flow/simulation/scada/complex_control.py +625 -0
  16. epyt_flow/simulation/scada/custom_control.py +134 -0
  17. epyt_flow/simulation/scada/scada_data.py +547 -8
  18. epyt_flow/simulation/scada/simple_control.py +317 -0
  19. epyt_flow/simulation/scenario_config.py +87 -26
  20. epyt_flow/simulation/scenario_simulator.py +865 -51
  21. epyt_flow/simulation/sensor_config.py +34 -2
  22. epyt_flow/topology.py +16 -0
  23. epyt_flow/uncertainty/model_uncertainty.py +80 -62
  24. epyt_flow/uncertainty/sensor_noise.py +15 -4
  25. epyt_flow/uncertainty/uncertainties.py +71 -18
  26. epyt_flow/uncertainty/utils.py +40 -13
  27. epyt_flow/utils.py +15 -1
  28. epyt_flow/visualization/__init__.py +2 -0
  29. epyt_flow/{simulation → visualization}/scenario_visualizer.py +429 -586
  30. epyt_flow/visualization/visualization_utils.py +611 -0
  31. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/LICENSE +1 -1
  32. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/METADATA +18 -6
  33. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/RECORD +35 -30
  34. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/WHEEL +1 -1
  35. {epyt_flow-0.9.0.dist-info → epyt_flow-0.11.0.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -12,7 +12,7 @@ from ..uncertainty import AbsoluteGaussianUncertainty, RelativeGaussianUncertain
12
12
  AbsoluteUniformUncertainty, RelativeUniformUncertainty, ModelUncertainty, \
13
13
  SensorNoise, Uncertainty
14
14
  from .sensor_config import SensorConfig
15
- from .scada import AdvancedControlModule
15
+ from .scada import CustomControlModule, SimpleControlModule, ComplexControlModule
16
16
  from .events import SystemEvent, SensorReadingEvent
17
17
  from .events.sensor_faults import SensorFaultConstant, SensorFaultDrift, SensorFaultGaussian, \
18
18
  SensorFaultPercentage, SensorFaultStuckZero
@@ -63,8 +63,16 @@ class ScenarioConfig(Serializable):
63
63
  Speciation of sensor noise -- i.e. noise/uncertainty affecting the sensor readings.
64
64
 
65
65
  The default is None
66
- controls : list[:class:`~epyt_flow.simulation.scada.advanced_control.AdvancedControlModule`], optional
67
- List of control modules that are active during the simulation.
66
+ csutom_controls : list[:class:`~epyt_flow.simulation.scada.custom_control.CustomControlModule`], optional
67
+ List of custom control modules that are active during the simulation.
68
+
69
+ The default is an empty list.
70
+ simple_controls : list[:class:`~epyt_flow.simulation.scada.simple_control.SimpleControlModule`], optional
71
+ List of EPANET control rules that are active during the simulation.
72
+
73
+ The default is an empty list.
74
+ complex_controls : list[:class:`~epyt_flow.simulation.scada.complex_control.ComplexControlModule`], optional
75
+ List of complex (i.e. IF-THEN-ELSE) EPANET control rules that are active during the simulation.
68
76
 
69
77
  The default is an empty list.
70
78
  model_uncertainty : :class:`~epyt_flow.uncertainty.model_uncertainty.ModelUncertainty`, optional
@@ -82,7 +90,9 @@ class ScenarioConfig(Serializable):
82
90
  def __init__(self, scenario_config: Any = None, f_inp_in: str = None, f_msx_in: str = None,
83
91
  general_params: dict = None, sensor_config: SensorConfig = None,
84
92
  memory_consumption_estimate: float = None,
85
- controls: list[AdvancedControlModule] = [],
93
+ custom_controls: list[CustomControlModule] = [],
94
+ simple_controls: list[SimpleControlModule] = [],
95
+ complex_controls: list[ComplexControlModule] = [],
86
96
  sensor_noise: SensorNoise = None,
87
97
  model_uncertainty: ModelUncertainty = None,
88
98
  system_events: list[SystemEvent] = [],
@@ -115,14 +125,22 @@ class ScenarioConfig(Serializable):
115
125
  if not isinstance(memory_consumption_estimate, float) or \
116
126
  memory_consumption_estimate <= 0:
117
127
  raise ValueError("'memory_consumption_estimate' must be a positive integer")
118
- if not isinstance(controls, list):
119
- raise TypeError("'controls' must be an instance of " +
120
- "'list[epyt_flow.simulation.scada.AdvancedControlModule]' but no of " +
121
- f"'{type(controls)}'")
122
- if len(controls) != 0:
123
- if any(not isinstance(c, AdvancedControlModule) for c in controls):
124
- raise TypeError("Each item in 'controls' must be an instance of " +
125
- "'epyt_flow.simulation.scada.AdvancedControlModule'")
128
+ if len(custom_controls) != 0:
129
+ if any(not isinstance(c, CustomControlModule) for c in custom_controls):
130
+ raise TypeError("Each item in 'custom_controls' must be an instance of " +
131
+ "'epyt_flow.simulation.scada.CustomControlModule'")
132
+ if not isinstance(simple_controls, list):
133
+ raise TypeError("'simple_controls' must be an instance of " +
134
+ "'list[epyt_flow.simulation.scada.SimpleControlModule]' but no of " +
135
+ f"'{type(simple_controls)}'")
136
+ if len(simple_controls) != 0:
137
+ if any(not isinstance(c, SimpleControlModule) for c in simple_controls):
138
+ raise TypeError("Each item in 'simple_controls' must be an instance of " +
139
+ "'epyt_flow.simulation.scada.SimppleControlModule'")
140
+ if len(complex_controls) != 0:
141
+ if any(not isinstance(c, ComplexControlModule) for c in complex_controls):
142
+ raise TypeError("Each item in 'complex_controls' must be an instance of " +
143
+ "'epyt_flow.simulation.scada.ComplexControlModule'")
126
144
  if sensor_noise is not None:
127
145
  if not isinstance(sensor_noise, SensorNoise):
128
146
  raise TypeError("'sensor_noise' must be an instance of " +
@@ -169,10 +187,20 @@ class ScenarioConfig(Serializable):
169
187
  else:
170
188
  self.__memory_consumption_estimate = memory_consumption_estimate
171
189
 
172
- if len(controls) == 0:
173
- self.__controls = scenario_config.controls
190
+ if len(custom_controls) == 0:
191
+ self.__custom_controls = scenario_config.custom_controls
192
+ else:
193
+ self.__custom_controls = custom_controls
194
+
195
+ if len(simple_controls) == 0:
196
+ self.__simple_controls = scenario_config.simple_controls
174
197
  else:
175
- self.__controls = controls
198
+ self.__simple_controls = simple_controls
199
+
200
+ if len(complex_controls) == 0:
201
+ self.__complex_controls = scenario_config.complex_controls
202
+ else:
203
+ self.__complex_controls = complex_controls
176
204
 
177
205
  if sensor_noise is None:
178
206
  self.__sensor_noise = scenario_config.sensor_noise
@@ -199,7 +227,9 @@ class ScenarioConfig(Serializable):
199
227
  self.__general_params = general_params
200
228
  self.__sensor_config = sensor_config
201
229
  self.__memory_consumption_estimate = memory_consumption_estimate
202
- self.__controls = controls
230
+ self.__custom_controls = custom_controls
231
+ self.__simple_controls = simple_controls
232
+ self.__complex_controls = complex_controls
203
233
  self.__sensor_noise = sensor_noise
204
234
  self.__system_events = system_events
205
235
  self.__sensor_reading_events = sensor_reading_events
@@ -286,16 +316,41 @@ class ScenarioConfig(Serializable):
286
316
  return self.__memory_consumption_estimate
287
317
 
288
318
  @property
289
- def controls(self) -> list[AdvancedControlModule]:
319
+ def custom_controls(self) -> list[CustomControlModule]:
320
+ """
321
+ Returns the list of all custom control modules that are active during the simulation.
322
+
323
+ Returns
324
+ -------
325
+ list[:class:`~epyt_flow.simulation.scada.custom_control.CustomControlModule`]
326
+ List of all custom control modules that are active during the simulation.
327
+ """
328
+ return deepcopy(self.__custom_controls)
329
+
330
+ @property
331
+ def simple_controls(self) -> list[SimpleControlModule]:
332
+ """
333
+ Gets the list of all EPANET control rules that are active during the simulation.
334
+
335
+ Returns
336
+ -------
337
+ list[:class:`~epyt_flow.simulation.scada.simple_control.SimpleControlModule`]
338
+ List of all EPANET control rules that are active during the simulation.
339
+ """
340
+ return deepcopy(self.__simple_controls)
341
+
342
+ @property
343
+ def complex_controls(self) -> list[ComplexControlModule]:
290
344
  """
291
- Gets the list of all control modules that are active during the simulation.
345
+ Gets the list of all complex (i.e. IF-THEN-ELSE) EPANET control rules
346
+ that are active during the simulation.
292
347
 
293
348
  Returns
294
349
  -------
295
- list[:class:`~epyt_flow.simulation.scada.advanced_control.AdvancedControlModule`]
296
- List of all control modules that are active during the simulation.
350
+ list[:class:`~epyt_flow.simulation.scada.complex_control.ComplexControlModule`]
351
+ List of all complex EPANET control rules that are active during the simulation.
297
352
  """
298
- return deepcopy(self.__controls)
353
+ return deepcopy(self.__complex_controls)
299
354
 
300
355
  @property
301
356
  def sensor_noise(self) -> SensorNoise:
@@ -350,7 +405,9 @@ class ScenarioConfig(Serializable):
350
405
  "general_params": self.__general_params,
351
406
  "sensor_config": self.__sensor_config,
352
407
  "memory_consumption_estimate": self.__memory_consumption_estimate,
353
- "controls": self.__controls,
408
+ "custom_controls": self.__custom_controls,
409
+ "simple_controls": self.__simple_controls,
410
+ "complex_controls": self.__complex_controls,
354
411
  "sensor_noise": self.__sensor_noise,
355
412
  "model_uncertainty": self.__model_uncertainty,
356
413
  "system_events": self.__system_events,
@@ -367,7 +424,9 @@ class ScenarioConfig(Serializable):
367
424
  and self.__general_params == other.general_params \
368
425
  and self.__memory_consumption_estimate == other.memory_consumption_estimate \
369
426
  and self.__sensor_config == other.sensor_config \
370
- and np.all(self.__controls == other.controls) \
427
+ and np.all(self.__custom_controls == other.custom_controls) \
428
+ and np.all(self.__simple_controls == other.simple_controls) \
429
+ and np.all(self.__complex_controls == other.complex_controls) \
371
430
  and self.__model_uncertainty == other.model_uncertainty \
372
431
  and np.all(self.__system_events == other.system_events) \
373
432
  and np.all(self.__sensor_reading_events == other.sensor_reading_events)
@@ -376,8 +435,10 @@ class ScenarioConfig(Serializable):
376
435
  return f"f_inp_in: {self.f_inp_in} f_msx_in: {self.f_msx_in} " + \
377
436
  f"general_params: {self.general_params} sensor_config: {self.sensor_config} " + \
378
437
  f"memory_consumption_estimate: {self.memory_consumption_estimate} " + \
379
- f"controls: {self.controls} sensor_noise: {self.sensor_noise} " + \
380
- f"model_uncertainty: {self.model_uncertainty} " + \
438
+ f"simple_controls: {self.simple_controls} " + \
439
+ f"complex_controls: {self.__complex_controls} " + \
440
+ f"custom_controls: {self.__custom_controls}" + \
441
+ f"sensor_noise: {self.sensor_noise} model_uncertainty: {self.model_uncertainty} " + \
381
442
  f"system_events: {','.join(map(str, self.system_events))} " + \
382
443
  f"sensor_reading_events: {','.join(map(str, self.sensor_reading_events))}"
383
444
 
@@ -614,6 +675,6 @@ class ScenarioConfig(Serializable):
614
675
 
615
676
  # Create final scenario configuration
616
677
  return ScenarioConfig(f_inp_in=f_inp_in, f_msx_in=f_msx_in, general_params=general_params,
617
- sensor_config=sensor_config, controls=[], sensor_noise=sensor_noise,
678
+ sensor_config=sensor_config, sensor_noise=sensor_noise,
618
679
  model_uncertainty=model_uncertainty, system_events=leakages,
619
680
  sensor_reading_events=sensor_faults)