epyt-flow 0.15.0b1__py3-none-any.whl → 0.16.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/data/benchmarks/batadal.py +1 -1
- epyt_flow/gym/scenario_control_env.py +6 -12
- epyt_flow/serialization.py +19 -3
- epyt_flow/simulation/events/actuator_events.py +24 -24
- epyt_flow/simulation/events/leakages.py +45 -45
- epyt_flow/simulation/events/quality_events.py +23 -23
- epyt_flow/simulation/events/sensor_reading_attack.py +27 -27
- epyt_flow/simulation/events/sensor_reading_event.py +33 -33
- epyt_flow/simulation/scada/complex_control.py +103 -103
- epyt_flow/simulation/scada/scada_data.py +58 -30
- epyt_flow/simulation/scada/simple_control.py +33 -33
- epyt_flow/simulation/scenario_config.py +31 -0
- epyt_flow/simulation/scenario_simulator.py +218 -82
- epyt_flow/simulation/sensor_config.py +220 -108
- epyt_flow/topology.py +197 -6
- epyt_flow/uncertainty/model_uncertainty.py +23 -20
- epyt_flow/uncertainty/sensor_noise.py +16 -16
- epyt_flow/uncertainty/uncertainties.py +6 -4
- epyt_flow/utils.py +240 -15
- epyt_flow/visualization/scenario_visualizer.py +14 -5
- epyt_flow/visualization/visualization_utils.py +22 -18
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/METADATA +8 -5
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/RECORD +27 -27
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {epyt_flow-0.15.0b1.dist-info → epyt_flow-0.16.0.dist-info}/top_level.txt +0 -0
|
@@ -38,8 +38,8 @@ class SensorOverrideAttack(SensorReadingAttack, JsonSerializable):
|
|
|
38
38
|
if len(new_sensor_values) == 0:
|
|
39
39
|
raise ValueError("'new_sensor_values' can not be empty")
|
|
40
40
|
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
41
|
+
self._new_sensor_values = new_sensor_values
|
|
42
|
+
self._cur_replay_idx = 0
|
|
43
43
|
|
|
44
44
|
super().__init__(**kwds)
|
|
45
45
|
|
|
@@ -54,21 +54,21 @@ class SensorOverrideAttack(SensorReadingAttack, JsonSerializable):
|
|
|
54
54
|
`np.ndarray`
|
|
55
55
|
New sensor readings.
|
|
56
56
|
"""
|
|
57
|
-
return deepcopy(self.
|
|
57
|
+
return deepcopy(self._new_sensor_values)
|
|
58
58
|
|
|
59
59
|
def get_attributes(self) -> dict:
|
|
60
|
-
return super().get_attributes() | {"new_sensor_values": self.
|
|
60
|
+
return super().get_attributes() | {"new_sensor_values": self._new_sensor_values}
|
|
61
61
|
|
|
62
62
|
def __eq__(self, other) -> bool:
|
|
63
63
|
if not isinstance(other, SensorOverrideAttack):
|
|
64
64
|
raise TypeError("Can not compare 'SensorOverrideAttack' instance " +
|
|
65
65
|
f"with '{type(other)}' instance")
|
|
66
66
|
|
|
67
|
-
return super().__eq__(other) and np.all(self.
|
|
67
|
+
return super().__eq__(other) and np.all(self._new_sensor_values == other.new_sensor_values)
|
|
68
68
|
|
|
69
69
|
def __str__(self) -> str:
|
|
70
70
|
return f"{type(self).__name__} {super().__str__()} " +\
|
|
71
|
-
f"new_sensor_values: {self.
|
|
71
|
+
f"new_sensor_values: {self._new_sensor_values}"
|
|
72
72
|
|
|
73
73
|
def apply(self, sensor_readings: np.ndarray,
|
|
74
74
|
sensor_readings_time: np.ndarray) -> np.ndarray:
|
|
@@ -76,8 +76,8 @@ class SensorOverrideAttack(SensorReadingAttack, JsonSerializable):
|
|
|
76
76
|
t = sensor_readings_time[i]
|
|
77
77
|
|
|
78
78
|
if self.start_time <= t <= self.end_time:
|
|
79
|
-
sensor_readings[i] = self.
|
|
80
|
-
self.
|
|
79
|
+
sensor_readings[i] = self._new_sensor_values[self._cur_replay_idx]
|
|
80
|
+
self._cur_replay_idx = (self._cur_replay_idx + 1) % len(self._new_sensor_values)
|
|
81
81
|
|
|
82
82
|
return sensor_readings
|
|
83
83
|
|
|
@@ -113,16 +113,16 @@ class SensorReplayAttack(SensorReadingAttack, JsonSerializable):
|
|
|
113
113
|
raise ValueError("Invalid values for 'replay_data_time_window_start' and/or " +
|
|
114
114
|
"'replay_data_time_window_end' detected.")
|
|
115
115
|
|
|
116
|
-
self.
|
|
116
|
+
self._new_sensor_values = np.zeros(replay_data_time_window_end -
|
|
117
117
|
replay_data_time_window_start)
|
|
118
|
-
self.
|
|
119
|
-
self.
|
|
120
|
-
self.
|
|
121
|
-
self.
|
|
118
|
+
self._sensor_data_time_window_start = replay_data_time_window_start
|
|
119
|
+
self._sensor_data_time_window_end = replay_data_time_window_end
|
|
120
|
+
self._cur_hist_idx = 0
|
|
121
|
+
self._cur_replay_idx = 0
|
|
122
122
|
|
|
123
123
|
super().__init__(**kwds)
|
|
124
124
|
|
|
125
|
-
if self.
|
|
125
|
+
if self._sensor_data_time_window_start > self.start_time:
|
|
126
126
|
raise ValueError("'replay_data_time_window_start' must be less than 'start_time'")
|
|
127
127
|
|
|
128
128
|
@property
|
|
@@ -136,7 +136,7 @@ class SensorReplayAttack(SensorReadingAttack, JsonSerializable):
|
|
|
136
136
|
`int`
|
|
137
137
|
Start time.
|
|
138
138
|
"""
|
|
139
|
-
return self.
|
|
139
|
+
return self._sensor_data_time_window_start
|
|
140
140
|
|
|
141
141
|
@property
|
|
142
142
|
def sensor_data_time_window_end(self) -> int:
|
|
@@ -149,7 +149,7 @@ class SensorReplayAttack(SensorReadingAttack, JsonSerializable):
|
|
|
149
149
|
`int`
|
|
150
150
|
End time.
|
|
151
151
|
"""
|
|
152
|
-
return self.
|
|
152
|
+
return self._sensor_data_time_window_end
|
|
153
153
|
|
|
154
154
|
@property
|
|
155
155
|
def new_sensor_values(self) -> np.ndarray:
|
|
@@ -162,12 +162,12 @@ class SensorReplayAttack(SensorReadingAttack, JsonSerializable):
|
|
|
162
162
|
`np.ndarray`
|
|
163
163
|
New sensor readings.
|
|
164
164
|
"""
|
|
165
|
-
return deepcopy(self.
|
|
165
|
+
return deepcopy(self._new_sensor_values)
|
|
166
166
|
|
|
167
167
|
def get_attributes(self) -> dict:
|
|
168
|
-
my_attributes = {"new_sensor_values": self.
|
|
169
|
-
"replay_data_time_window_start": self.
|
|
170
|
-
"replay_data_time_window_end": self.
|
|
168
|
+
my_attributes = {"new_sensor_values": self._new_sensor_values,
|
|
169
|
+
"replay_data_time_window_start": self._sensor_data_time_window_start,
|
|
170
|
+
"replay_data_time_window_end": self._sensor_data_time_window_end}
|
|
171
171
|
|
|
172
172
|
return super().get_attributes() | my_attributes
|
|
173
173
|
|
|
@@ -176,23 +176,23 @@ class SensorReplayAttack(SensorReadingAttack, JsonSerializable):
|
|
|
176
176
|
raise TypeError("Can not compare 'SensorReplayAttack' instance " +
|
|
177
177
|
f"with '{type(other)}' instance")
|
|
178
178
|
|
|
179
|
-
return super().__eq__(other) and np.all(self.
|
|
179
|
+
return super().__eq__(other) and np.all(self._new_sensor_values == other.new_sensor_values)
|
|
180
180
|
|
|
181
181
|
def __str__(self) -> str:
|
|
182
182
|
return f"{type(self).__name__} {super().__str__()} " +\
|
|
183
|
-
f"new_sensor_values: {self.
|
|
183
|
+
f"new_sensor_values: {self._new_sensor_values}"
|
|
184
184
|
|
|
185
185
|
def apply(self, sensor_readings: np.ndarray,
|
|
186
186
|
sensor_readings_time: np.ndarray) -> np.ndarray:
|
|
187
187
|
for i in range(sensor_readings.shape[0]):
|
|
188
188
|
t = sensor_readings_time[i]
|
|
189
189
|
|
|
190
|
-
if self.
|
|
191
|
-
self.
|
|
192
|
-
self.
|
|
190
|
+
if self._sensor_data_time_window_start <= t <= self._sensor_data_time_window_end:
|
|
191
|
+
self._new_sensor_values[self._cur_hist_idx] = sensor_readings[i]
|
|
192
|
+
self._cur_hist_idx += 1
|
|
193
193
|
|
|
194
194
|
if self.start_time <= t <= self.end_time:
|
|
195
|
-
sensor_readings[i] = self.
|
|
196
|
-
self.
|
|
195
|
+
sensor_readings[i] = self._new_sensor_values[self._cur_replay_idx]
|
|
196
|
+
self._cur_replay_idx = (self._cur_replay_idx + 1) % len(self._new_sensor_values)
|
|
197
197
|
|
|
198
198
|
return sensor_readings
|
|
@@ -46,8 +46,8 @@ class SensorReadingEvent(Event):
|
|
|
46
46
|
if not 1 <= sensor_type <= 10:
|
|
47
47
|
raise ValueError("Invalid value of 'sensor_type'")
|
|
48
48
|
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
49
|
+
self._sensor_id = sensor_id
|
|
50
|
+
self._sensor_type = sensor_type
|
|
51
51
|
|
|
52
52
|
super().__init__(**kwds)
|
|
53
53
|
|
|
@@ -68,52 +68,52 @@ class SensorReadingEvent(Event):
|
|
|
68
68
|
|
|
69
69
|
def __show_warning() -> None:
|
|
70
70
|
warnings.warn("Event does not have any effect because there is " +
|
|
71
|
-
f"no sensor at '{self.
|
|
71
|
+
f"no sensor at '{self._sensor_id}'")
|
|
72
72
|
|
|
73
|
-
if self.
|
|
74
|
-
if self.
|
|
73
|
+
if self._sensor_type == SENSOR_TYPE_NODE_PRESSURE:
|
|
74
|
+
if self._sensor_id not in sensor_config.pressure_sensors:
|
|
75
75
|
__show_warning()
|
|
76
|
-
elif self.
|
|
77
|
-
if self.
|
|
76
|
+
elif self._sensor_type == SENSOR_TYPE_NODE_QUALITY:
|
|
77
|
+
if self._sensor_id not in sensor_config.quality_node_sensors:
|
|
78
78
|
__show_warning()
|
|
79
|
-
elif self.
|
|
80
|
-
if self.
|
|
79
|
+
elif self._sensor_type == SENSOR_TYPE_NODE_DEMAND:
|
|
80
|
+
if self._sensor_id not in sensor_config.demand_sensors:
|
|
81
81
|
__show_warning()
|
|
82
|
-
elif self.
|
|
83
|
-
if self.
|
|
82
|
+
elif self._sensor_type == SENSOR_TYPE_LINK_FLOW:
|
|
83
|
+
if self._sensor_id not in sensor_config.flow_sensors:
|
|
84
84
|
__show_warning()
|
|
85
|
-
elif self.
|
|
86
|
-
if self.
|
|
85
|
+
elif self._sensor_type == SENSOR_TYPE_LINK_QUALITY:
|
|
86
|
+
if self._sensor_id not in sensor_config.quality_link_sensors:
|
|
87
87
|
__show_warning()
|
|
88
|
-
elif self.
|
|
89
|
-
if self.
|
|
88
|
+
elif self._sensor_type == SENSOR_TYPE_VALVE_STATE:
|
|
89
|
+
if self._sensor_id not in sensor_config.valve_state_sensors:
|
|
90
90
|
__show_warning()
|
|
91
|
-
elif self.
|
|
92
|
-
if self.
|
|
91
|
+
elif self._sensor_type == SENSOR_TYPE_PUMP_STATE:
|
|
92
|
+
if self._sensor_id not in sensor_config.pump_state_sensors:
|
|
93
93
|
__show_warning()
|
|
94
|
-
elif self.
|
|
95
|
-
if self.
|
|
94
|
+
elif self._sensor_type == SENSOR_TYPE_TANK_VOLUME:
|
|
95
|
+
if self._sensor_id not in sensor_config.tank_volume_sensors:
|
|
96
96
|
__show_warning()
|
|
97
|
-
elif self.
|
|
97
|
+
elif self._sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES:
|
|
98
98
|
sensor_present = False
|
|
99
99
|
for _, sensors_id in sensor_config.bulk_species_node_sensors.items():
|
|
100
|
-
if self.
|
|
100
|
+
if self._sensor_id in sensors_id:
|
|
101
101
|
sensor_present = True
|
|
102
102
|
break
|
|
103
103
|
if sensor_present is False:
|
|
104
104
|
__show_warning()
|
|
105
|
-
elif self.
|
|
105
|
+
elif self._sensor_type == SENSOR_TYPE_LINK_BULK_SPECIES:
|
|
106
106
|
sensor_present = False
|
|
107
107
|
for _, sensors_id in sensor_config.bulk_species_link_sensors.items():
|
|
108
|
-
if self.
|
|
108
|
+
if self._sensor_id in sensors_id:
|
|
109
109
|
sensor_present = True
|
|
110
110
|
break
|
|
111
111
|
if sensor_present is False:
|
|
112
112
|
__show_warning()
|
|
113
|
-
elif self.
|
|
113
|
+
elif self._sensor_type == SENSOR_TYPE_SURFACE_SPECIES:
|
|
114
114
|
sensor_present = False
|
|
115
115
|
for _, sensors_id in sensor_config.surface_species_sensors.items():
|
|
116
|
-
if self.
|
|
116
|
+
if self._sensor_id in sensors_id:
|
|
117
117
|
sensor_present = True
|
|
118
118
|
break
|
|
119
119
|
if sensor_present is False:
|
|
@@ -129,7 +129,7 @@ class SensorReadingEvent(Event):
|
|
|
129
129
|
`str`
|
|
130
130
|
Node or link ID.
|
|
131
131
|
"""
|
|
132
|
-
return self.
|
|
132
|
+
return self._sensor_id
|
|
133
133
|
|
|
134
134
|
@property
|
|
135
135
|
def sensor_type(self) -> int:
|
|
@@ -141,23 +141,23 @@ class SensorReadingEvent(Event):
|
|
|
141
141
|
`int`
|
|
142
142
|
Sensor type code.
|
|
143
143
|
"""
|
|
144
|
-
return self.
|
|
144
|
+
return self._sensor_type
|
|
145
145
|
|
|
146
146
|
def get_attributes(self) -> dict:
|
|
147
|
-
return super().get_attributes() | {"sensor_id": self.
|
|
148
|
-
"sensor_type": self.
|
|
147
|
+
return super().get_attributes() | {"sensor_id": self._sensor_id,
|
|
148
|
+
"sensor_type": self._sensor_type}
|
|
149
149
|
|
|
150
150
|
def __eq__(self, other) -> bool:
|
|
151
151
|
if not isinstance(other, SensorReadingEvent):
|
|
152
152
|
raise TypeError("Can not compare 'SensorReadingEvent' instance " +
|
|
153
153
|
f"with '{type(other)}' instance")
|
|
154
154
|
|
|
155
|
-
return super().__eq__(other) and self.
|
|
156
|
-
and self.
|
|
155
|
+
return super().__eq__(other) and self._sensor_id == other.sensor_id \
|
|
156
|
+
and self._sensor_type == other.sensor_type
|
|
157
157
|
|
|
158
158
|
def __str__(self) -> str:
|
|
159
|
-
return f"{super().__str__()} sensor_id: {self.
|
|
160
|
-
f"sensor_type: {self.
|
|
159
|
+
return f"{super().__str__()} sensor_id: {self._sensor_id} " +\
|
|
160
|
+
f"sensor_type: {self._sensor_type}"
|
|
161
161
|
|
|
162
162
|
def __call__(self, sensor_readings: numpy.ndarray,
|
|
163
163
|
sensor_readings_time: numpy.ndarray) -> numpy.ndarray:
|
|
@@ -121,11 +121,11 @@ class RuleCondition(JsonSerializable):
|
|
|
121
121
|
EN_R_IS, EN_R_NOT, EN_R_BELOW, EN_R_ABOVE]:
|
|
122
122
|
raise ValueError(f"Invalid value '{relation_type_id}' for 'relation_type_id'")
|
|
123
123
|
|
|
124
|
-
self.
|
|
125
|
-
self.
|
|
126
|
-
self.
|
|
127
|
-
self.
|
|
128
|
-
self.
|
|
124
|
+
self._object_type_id = object_type_id
|
|
125
|
+
self._object_id = object_id
|
|
126
|
+
self._attribute_id = attribute_id
|
|
127
|
+
self._relation_type_id = relation_type_id
|
|
128
|
+
self._value = value
|
|
129
129
|
|
|
130
130
|
super().__init__(**kwds)
|
|
131
131
|
|
|
@@ -145,7 +145,7 @@ class RuleCondition(JsonSerializable):
|
|
|
145
145
|
`int`
|
|
146
146
|
ID of the object type..
|
|
147
147
|
"""
|
|
148
|
-
return self.
|
|
148
|
+
return self._object_type_id
|
|
149
149
|
|
|
150
150
|
@property
|
|
151
151
|
def object_id(self) -> str:
|
|
@@ -157,7 +157,7 @@ class RuleCondition(JsonSerializable):
|
|
|
157
157
|
`str`
|
|
158
158
|
ID of the object.
|
|
159
159
|
"""
|
|
160
|
-
return self.
|
|
160
|
+
return self._object_id
|
|
161
161
|
|
|
162
162
|
@property
|
|
163
163
|
def attribute_id(self) -> int:
|
|
@@ -183,7 +183,7 @@ class RuleCondition(JsonSerializable):
|
|
|
183
183
|
`int`
|
|
184
184
|
Type ID of the object's attribute that is checked.
|
|
185
185
|
"""
|
|
186
|
-
return self.
|
|
186
|
+
return self._attribute_id
|
|
187
187
|
|
|
188
188
|
@property
|
|
189
189
|
def relation_type_id(self) -> int:
|
|
@@ -208,7 +208,7 @@ class RuleCondition(JsonSerializable):
|
|
|
208
208
|
`int`
|
|
209
209
|
ID of the type of comparison.
|
|
210
210
|
"""
|
|
211
|
-
return self.
|
|
211
|
+
return self._relation_type_id
|
|
212
212
|
|
|
213
213
|
@property
|
|
214
214
|
def value(self) -> Any:
|
|
@@ -220,69 +220,69 @@ class RuleCondition(JsonSerializable):
|
|
|
220
220
|
`Any`
|
|
221
221
|
Value that is compared against.
|
|
222
222
|
"""
|
|
223
|
-
return self.
|
|
223
|
+
return self._value
|
|
224
224
|
|
|
225
225
|
def get_attributes(self) -> dict:
|
|
226
|
-
return super().get_attributes() | {"object_type_id": self.
|
|
227
|
-
"object_id": self.
|
|
228
|
-
"attribute_id": self.
|
|
229
|
-
"relation_type_id": self.
|
|
230
|
-
"value": self.
|
|
226
|
+
return super().get_attributes() | {"object_type_id": self._object_type_id,
|
|
227
|
+
"object_id": self._object_id,
|
|
228
|
+
"attribute_id": self._attribute_id,
|
|
229
|
+
"relation_type_id": self._relation_type_id,
|
|
230
|
+
"value": self._value}
|
|
231
231
|
|
|
232
232
|
def __eq__(self, other) -> bool:
|
|
233
|
-
return self.
|
|
234
|
-
self.
|
|
235
|
-
self.
|
|
233
|
+
return self._object_type_id == other.object_type_id and \
|
|
234
|
+
self._object_id == other.object_id and self._attribute_id == other.attribute_id and \
|
|
235
|
+
self._relation_type_id == other.relation_type_id and self._value == other.value
|
|
236
236
|
|
|
237
237
|
def __str__(self) -> str:
|
|
238
238
|
desc = ""
|
|
239
239
|
|
|
240
|
-
if self.
|
|
241
|
-
if self.
|
|
242
|
-
desc += f"JUNCTION {self.
|
|
243
|
-
elif self.
|
|
240
|
+
if self._attribute_id == EN_R_DEMAND:
|
|
241
|
+
if self._object_type_id == EpanetConstants.EN_R_NODE:
|
|
242
|
+
desc += f"JUNCTION {self._object_id} DEMAND "
|
|
243
|
+
elif self._object_type_id == EpanetConstants.EN_R_SYSTEM:
|
|
244
244
|
desc += "SYSTEM DEMAND "
|
|
245
|
-
elif self.
|
|
246
|
-
desc += f"JUNCTION {self.
|
|
247
|
-
elif self.
|
|
248
|
-
desc += f"TANK {self.
|
|
249
|
-
elif self.
|
|
250
|
-
desc += f"JUNCTION {self.
|
|
251
|
-
elif self.
|
|
252
|
-
desc += f"LINK {self.
|
|
253
|
-
elif self.
|
|
254
|
-
desc += f"LINK {self.
|
|
255
|
-
elif self.
|
|
256
|
-
desc += f"LINK {self.
|
|
257
|
-
elif self.
|
|
245
|
+
elif self._attribute_id == EN_R_HEAD:
|
|
246
|
+
desc += f"JUNCTION {self._object_id} HEAD "
|
|
247
|
+
elif self._attribute_id == EN_R_LEVEL:
|
|
248
|
+
desc += f"TANK {self._object_id} LEVEL "
|
|
249
|
+
elif self._attribute_id == EN_R_PRESSURE:
|
|
250
|
+
desc += f"JUNCTION {self._object_id} PRESSURE "
|
|
251
|
+
elif self._attribute_id == EN_R_FLOW:
|
|
252
|
+
desc += f"LINK {self._object_id} FLOW "
|
|
253
|
+
elif self._attribute_id == EN_R_STATUS:
|
|
254
|
+
desc += f"LINK {self._object_id} STATUS "
|
|
255
|
+
elif self._attribute_id == EN_R_SETTING:
|
|
256
|
+
desc += f"LINK {self._object_id} SETTING "
|
|
257
|
+
elif self._attribute_id == EN_R_TIME:
|
|
258
258
|
desc += "SYSTEM TIME "
|
|
259
|
-
elif self.
|
|
259
|
+
elif self._attribute_id == EN_R_CLOCKTIME:
|
|
260
260
|
desc += "SYSTEM CLOCKTIME "
|
|
261
|
-
elif self.
|
|
262
|
-
desc += f"TANK {self.
|
|
263
|
-
elif self.
|
|
264
|
-
desc += f"TANK {self.
|
|
261
|
+
elif self._attribute_id == EN_R_FILLTIME:
|
|
262
|
+
desc += f"TANK {self._object_id} FILLTIME "
|
|
263
|
+
elif self._attribute_id == EN_R_DRAINTIME:
|
|
264
|
+
desc += f"TANK {self._object_id} DRAINTIME "
|
|
265
265
|
|
|
266
|
-
if self.
|
|
266
|
+
if self._relation_type_id == EN_R_EQ:
|
|
267
267
|
desc += "= "
|
|
268
|
-
elif self.
|
|
268
|
+
elif self._relation_type_id == EN_R_IS:
|
|
269
269
|
desc += "IS "
|
|
270
|
-
elif self.
|
|
270
|
+
elif self._relation_type_id == EN_R_NOT:
|
|
271
271
|
desc += "IS NOT "
|
|
272
|
-
elif self.
|
|
272
|
+
elif self._relation_type_id == EN_R_LEQ:
|
|
273
273
|
desc += "<= "
|
|
274
|
-
elif self.
|
|
274
|
+
elif self._relation_type_id == EN_R_GEQ:
|
|
275
275
|
desc += ">= "
|
|
276
|
-
elif self.
|
|
276
|
+
elif self._relation_type_id == EN_R_ABOVE:
|
|
277
277
|
desc += "ABOVE "
|
|
278
|
-
elif self.
|
|
278
|
+
elif self._relation_type_id == EN_R_BELOW:
|
|
279
279
|
desc += "BELOW "
|
|
280
|
-
elif self.
|
|
280
|
+
elif self._relation_type_id == EN_R_LESS:
|
|
281
281
|
desc += "< "
|
|
282
|
-
elif self.
|
|
282
|
+
elif self._relation_type_id == EN_R_GREATER:
|
|
283
283
|
desc += "> "
|
|
284
284
|
|
|
285
|
-
desc += str(self.
|
|
285
|
+
desc += str(self._value)
|
|
286
286
|
|
|
287
287
|
return desc
|
|
288
288
|
|
|
@@ -344,10 +344,10 @@ class RuleAction(JsonSerializable):
|
|
|
344
344
|
EN_R_ACTION_STATUS_CLOSED, EN_R_ACTION_STATUS_ACTIVE]:
|
|
345
345
|
raise ValueError(f"Invalid value '{action_type_id}' for 'action_type_id'")
|
|
346
346
|
|
|
347
|
-
self.
|
|
348
|
-
self.
|
|
349
|
-
self.
|
|
350
|
-
self.
|
|
347
|
+
self._link_type_id = link_type_id
|
|
348
|
+
self._link_id = link_id
|
|
349
|
+
self._action_type_id = action_type_id
|
|
350
|
+
self._action_value = action_value
|
|
351
351
|
|
|
352
352
|
super().__init__(**kwds)
|
|
353
353
|
|
|
@@ -373,7 +373,7 @@ class RuleAction(JsonSerializable):
|
|
|
373
373
|
`int`
|
|
374
374
|
Link type ID.
|
|
375
375
|
"""
|
|
376
|
-
return self.
|
|
376
|
+
return self._link_type_id
|
|
377
377
|
|
|
378
378
|
@property
|
|
379
379
|
def link_id(self) -> str:
|
|
@@ -385,7 +385,7 @@ class RuleAction(JsonSerializable):
|
|
|
385
385
|
`str`
|
|
386
386
|
Link ID.
|
|
387
387
|
"""
|
|
388
|
-
return self.
|
|
388
|
+
return self._link_id
|
|
389
389
|
|
|
390
390
|
@property
|
|
391
391
|
def action_type_id(self) -> int:
|
|
@@ -404,7 +404,7 @@ class RuleAction(JsonSerializable):
|
|
|
404
404
|
`int`
|
|
405
405
|
Type ID of the action.
|
|
406
406
|
"""
|
|
407
|
-
return self.
|
|
407
|
+
return self._action_type_id
|
|
408
408
|
|
|
409
409
|
@property
|
|
410
410
|
def action_value(self) -> Any:
|
|
@@ -417,37 +417,37 @@ class RuleAction(JsonSerializable):
|
|
|
417
417
|
`Any`
|
|
418
418
|
Value of the action.
|
|
419
419
|
"""
|
|
420
|
-
return self.
|
|
420
|
+
return self._action_value
|
|
421
421
|
|
|
422
422
|
def get_attributes(self) -> dict:
|
|
423
|
-
return super().get_attributes() | {"link_type_id": self.
|
|
424
|
-
"link_id": self.
|
|
425
|
-
"action_type_id": self.
|
|
426
|
-
"action_value": self.
|
|
423
|
+
return super().get_attributes() | {"link_type_id": self._link_type_id,
|
|
424
|
+
"link_id": self._link_id,
|
|
425
|
+
"action_type_id": self._action_type_id,
|
|
426
|
+
"action_value": self._action_value}
|
|
427
427
|
|
|
428
428
|
def __eq__(self, other) -> bool:
|
|
429
|
-
return self.
|
|
430
|
-
self.
|
|
431
|
-
self.
|
|
432
|
-
self.
|
|
429
|
+
return self._link_type_id == other.link_type_id and \
|
|
430
|
+
self._link_id == other.link_id and \
|
|
431
|
+
self._action_type_id == other.action_type_id and \
|
|
432
|
+
self._action_value == other.action_value
|
|
433
433
|
|
|
434
434
|
def __str__(self) -> str:
|
|
435
435
|
desc = ""
|
|
436
436
|
|
|
437
|
-
if self.
|
|
437
|
+
if self._link_type_id in [EpanetConstants.EN_CVPIPE, EpanetConstants.EN_PIPE]:
|
|
438
438
|
desc += "PIPE "
|
|
439
|
-
elif self.
|
|
439
|
+
elif self._link_type_id == EpanetConstants.EN_PUMP:
|
|
440
440
|
desc += "PUMP "
|
|
441
441
|
else:
|
|
442
442
|
desc += "VALVE "
|
|
443
443
|
|
|
444
|
-
desc += f"{self.
|
|
444
|
+
desc += f"{self._link_id} "
|
|
445
445
|
|
|
446
|
-
if self.
|
|
447
|
-
desc += f"SETTING IS {self.
|
|
448
|
-
elif self.
|
|
446
|
+
if self._action_type_id == EN_R_ACTION_SETTING:
|
|
447
|
+
desc += f"SETTING IS {self._action_value}"
|
|
448
|
+
elif self._action_type_id == EN_R_ACTION_STATUS_OPEN:
|
|
449
449
|
desc += "STATUS IS OPEN"
|
|
450
|
-
elif self.
|
|
450
|
+
elif self._action_type_id == EN_R_ACTION_STATUS_CLOSED:
|
|
451
451
|
desc += "STATUS IS CLOSED"
|
|
452
452
|
elif self.action_type_id == EN_R_ACTION_STATUS_ACTIVE:
|
|
453
453
|
desc += "STATUS IS ACTIVE"
|
|
@@ -508,12 +508,12 @@ class ComplexControlModule(JsonSerializable):
|
|
|
508
508
|
if not isinstance(priority, int) or priority < 0:
|
|
509
509
|
raise TypeError("'priority' must be a non-negative integer")
|
|
510
510
|
|
|
511
|
-
self.
|
|
512
|
-
self.
|
|
513
|
-
self.
|
|
514
|
-
self.
|
|
515
|
-
self.
|
|
516
|
-
self.
|
|
511
|
+
self._rule_id = rule_id
|
|
512
|
+
self._condition_1 = condition_1
|
|
513
|
+
self._additional_conditions = additional_conditions
|
|
514
|
+
self._actions = actions
|
|
515
|
+
self._else_actions = else_actions
|
|
516
|
+
self._priority = priority
|
|
517
517
|
|
|
518
518
|
super().__init__(**kwds)
|
|
519
519
|
|
|
@@ -527,7 +527,7 @@ class ComplexControlModule(JsonSerializable):
|
|
|
527
527
|
`str`
|
|
528
528
|
ID of this control rule.
|
|
529
529
|
"""
|
|
530
|
-
return self.
|
|
530
|
+
return self._rule_id
|
|
531
531
|
|
|
532
532
|
@property
|
|
533
533
|
def condition_1(self) -> RuleCondition:
|
|
@@ -539,7 +539,7 @@ class ComplexControlModule(JsonSerializable):
|
|
|
539
539
|
:class:`~epyt_flow.simulation.scada.complex_control.RuleCondition`
|
|
540
540
|
First condition of this rule.
|
|
541
541
|
"""
|
|
542
|
-
return deepcopy(self.
|
|
542
|
+
return deepcopy(self._condition_1)
|
|
543
543
|
|
|
544
544
|
@property
|
|
545
545
|
def additional_conditions(self) -> list[tuple[int, RuleCondition]]:
|
|
@@ -552,7 +552,7 @@ class ComplexControlModule(JsonSerializable):
|
|
|
552
552
|
list[tuple[int, :class:`~epyt_flow.simulation.scada.complex_control.RuleCondition`]]
|
|
553
553
|
List of (optional) additional conditions incl. their conjunction operator.
|
|
554
554
|
"""
|
|
555
|
-
return deepcopy(self.
|
|
555
|
+
return deepcopy(self._additional_conditions)
|
|
556
556
|
|
|
557
557
|
@property
|
|
558
558
|
def actions(self) -> list[RuleAction]:
|
|
@@ -564,7 +564,7 @@ class ComplexControlModule(JsonSerializable):
|
|
|
564
564
|
list[:class:`~epyt_flow.simulation.scada.complex_control.RuleAction`]
|
|
565
565
|
List of actions that are applied if the conditions are met.
|
|
566
566
|
"""
|
|
567
|
-
return deepcopy(self.
|
|
567
|
+
return deepcopy(self._actions)
|
|
568
568
|
|
|
569
569
|
@property
|
|
570
570
|
def else_actions(self) -> list[RuleAction]:
|
|
@@ -576,7 +576,7 @@ class ComplexControlModule(JsonSerializable):
|
|
|
576
576
|
list[:class:`~epyt_flow.simulation.scada.complex_control.RuleAction`]
|
|
577
577
|
List of actions that are applied if the conditions are NOT met.
|
|
578
578
|
"""
|
|
579
|
-
return deepcopy(self.
|
|
579
|
+
return deepcopy(self._else_actions)
|
|
580
580
|
|
|
581
581
|
@property
|
|
582
582
|
def priority(self) -> int:
|
|
@@ -588,29 +588,29 @@ class ComplexControlModule(JsonSerializable):
|
|
|
588
588
|
`int`
|
|
589
589
|
Priority of this control rule.
|
|
590
590
|
"""
|
|
591
|
-
return self.
|
|
591
|
+
return self._priority
|
|
592
592
|
|
|
593
593
|
def get_attributes(self) -> dict:
|
|
594
|
-
return super().get_attributes() | {"rule_id": self.
|
|
595
|
-
"condition_1": self.
|
|
596
|
-
"additional_conditions": self.
|
|
597
|
-
"actions": self.
|
|
598
|
-
"else_actions": self.
|
|
599
|
-
"priority": self.
|
|
594
|
+
return super().get_attributes() | {"rule_id": self._rule_id,
|
|
595
|
+
"condition_1": self._condition_1,
|
|
596
|
+
"additional_conditions": self._additional_conditions,
|
|
597
|
+
"actions": self._actions,
|
|
598
|
+
"else_actions": self._else_actions,
|
|
599
|
+
"priority": self._priority}
|
|
600
600
|
|
|
601
601
|
def __eq__(self, other) -> bool:
|
|
602
|
-
return super().__eq__(other) and self.
|
|
603
|
-
self.
|
|
604
|
-
np.all(self.
|
|
605
|
-
np.all(self.
|
|
606
|
-
np.all(self.
|
|
602
|
+
return super().__eq__(other) and self._rule_id == other.rule_id and \
|
|
603
|
+
self._priority == other.priority and self._condition_1 == other.condition_1 and \
|
|
604
|
+
np.all(self._additional_conditions == other.additional_conditions) and \
|
|
605
|
+
np.all(self._actions == other.actions) and \
|
|
606
|
+
np.all(self._else_actions == other.else_actions)
|
|
607
607
|
|
|
608
608
|
def __str__(self) -> str:
|
|
609
609
|
desc = ""
|
|
610
610
|
|
|
611
|
-
desc += f"RULE {self.
|
|
612
|
-
desc += f"IF {self.
|
|
613
|
-
for op, action in self.
|
|
611
|
+
desc += f"RULE {self._rule_id}\n"
|
|
612
|
+
desc += f"IF {self._condition_1} "
|
|
613
|
+
for op, action in self._additional_conditions:
|
|
614
614
|
if op == EN_R_AND:
|
|
615
615
|
desc += "\nAND "
|
|
616
616
|
elif op == EN_R_OR:
|
|
@@ -618,10 +618,10 @@ class ComplexControlModule(JsonSerializable):
|
|
|
618
618
|
|
|
619
619
|
desc += f"{action} "
|
|
620
620
|
desc += "\nTHEN "
|
|
621
|
-
desc += "\nAND ".join(str(action) for action in self.
|
|
622
|
-
if len(self.
|
|
623
|
-
desc += "\nELSE " + "\nAND ".join(str(action) for action in self.
|
|
621
|
+
desc += "\nAND ".join(str(action) for action in self._actions)
|
|
622
|
+
if len(self._else_actions) != 0:
|
|
623
|
+
desc += "\nELSE " + "\nAND ".join(str(action) for action in self._else_actions)
|
|
624
624
|
|
|
625
|
-
desc += f"\nPRIORITY {self.
|
|
625
|
+
desc += f"\nPRIORITY {self._priority}"
|
|
626
626
|
|
|
627
627
|
return desc
|