epyt-flow 0.9.0__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.
@@ -0,0 +1,625 @@
1
+ """
2
+ Module contains a class for representing complex control rules as implemented in EPANET.
3
+ """
4
+ from copy import deepcopy
5
+ from typing import Any
6
+ import numpy as np
7
+ from epyt.epanet import ToolkitConstants
8
+
9
+ from ...serialization import JsonSerializable, COMPLEX_CONTROL_ID, COMPLEX_CONTROL_CONDITION_ID, \
10
+ COMPLEX_CONTROL_ACTION_ID, serializable
11
+
12
+
13
+ EN_R_AND = 2
14
+ EN_R_OR = 3
15
+
16
+ EN_R_DEMAND = 1
17
+ EN_R_HEAD = 2
18
+ EN_R_LEVEL = 3
19
+ EN_R_PRESSURE = 4
20
+ EN_R_FLOW = 5
21
+ EN_R_STATUS = 6
22
+ EN_R_SETTING = 7
23
+ EN_R_POWER = 8
24
+ EN_R_TIME = 9
25
+ EN_R_CLOCKTIME = 10
26
+ EN_R_FILLTIME = 11
27
+ EN_R_DRAINTIME = 12
28
+
29
+ EN_R_EQ = 0
30
+ EN_R_NEQ = 1
31
+ EN_R_LEQ = 2
32
+ EN_R_GEQ = 3
33
+ EN_R_LESS = 4
34
+ EN_R_GREATER = 5
35
+ EN_R_IS = 6
36
+ EN_R_NOT = 7
37
+ EN_R_BELOW = 8
38
+ EN_R_ABOVE = 9
39
+
40
+ EN_R_ACTION_SETTING = -1
41
+ EN_R_ACTION_STATUS_OPEN = 1
42
+ EN_R_ACTION_STATUS_CLOSED = 2
43
+ EN_R_ACTION_STATUS_ACTIVE = 3
44
+
45
+
46
+ @serializable(COMPLEX_CONTROL_CONDITION_ID, ".epytflow_complex_control_condition")
47
+ class RuleCondition(JsonSerializable):
48
+ """
49
+ Class representing a rule condition.
50
+
51
+ Parameters
52
+ ----------
53
+ object_type_id : `int`
54
+ ID of the object type.
55
+
56
+ Must be one of the following EPANET constants:
57
+
58
+ - EN_R_NODE = 6
59
+ - EN_R_LINK = 7
60
+ - EN_R_SYSTEM = 8
61
+ object_id : `str`
62
+ ID of the object (i.e. junction, pipe, link, tank, etc.).
63
+ attribute_id : `int`
64
+ Type ID of the object's attribute that is checked.
65
+
66
+ Must be on of the following constants:
67
+
68
+ - EN_R_DEMAND = 1
69
+ - EN_R_HEAD = 2
70
+ - EN_R_LEVEL = 3
71
+ - EN_R_PRESSURE = 4
72
+ - EN_R_FLOW = 5
73
+ - EN_R_STATUS = 6
74
+ - EN_R_SETTING = 7
75
+ - EN_R_TIME = 9
76
+ - EN_R_CLOCKTIME = 10
77
+ - EN_R_FILLTIME = 11
78
+ - EN_R_DRAINTIME = 12
79
+ relation_type_id : `int`
80
+ ID of the type of comparison.
81
+
82
+ Must be one of the following constants:
83
+
84
+ - EN_R_EQ = 0
85
+ - EN_R_NEQ = 1
86
+ - EN_R_LEQ = 2
87
+ - EN_R_GEQ = 3
88
+ - EN_R_LESS = 4
89
+ - EN_R_GREATER = 5
90
+ - EN_R_IS = 6
91
+ - EN_R_NOT = 7
92
+ - EN_R_BELOW = 8
93
+ - EN_R_ABOVE = 9
94
+ value : `Any`
95
+ Value that is compared against.
96
+ """
97
+ def __init__(self, object_type_id: int, object_id: str, attribute_id: int,
98
+ relation_type_id: int, value: Any, **kwds):
99
+ if not isinstance(object_type_id, int):
100
+ raise TypeError("'object_type_id' must be an instance of 'int' " +
101
+ f"but not of '{type(object_type_id)}'")
102
+ if object_type_id not in [ToolkitConstants.EN_R_NODE, ToolkitConstants.EN_R_LINK,
103
+ ToolkitConstants.EN_R_SYSTEM]:
104
+ raise ValueError(f"Invalid value '{object_type_id}' for 'object_type_id'")
105
+ if not isinstance(object_id, str):
106
+ raise TypeError("'object_id' must be an instance of 'str' " +
107
+ f"but not of '{type(object_id)}'")
108
+ if not isinstance(attribute_id, int):
109
+ raise TypeError("'attribute_id' must be an instance of 'int' " +
110
+ f"but not of '{type(attribute_id)}'")
111
+ if attribute_id not in [EN_R_DEMAND, EN_R_HEAD, EN_R_LEVEL, EN_R_PRESSURE,
112
+ EN_R_FLOW, EN_R_STATUS, EN_R_SETTING, EN_R_POWER, EN_R_TIME,
113
+ EN_R_CLOCKTIME, EN_R_FILLTIME, EN_R_DRAINTIME]:
114
+ raise ValueError(f"Invalid value '{attribute_id}' for 'attribute_id'")
115
+ if not isinstance(relation_type_id, int):
116
+ raise TypeError("'relation_type_id' must be an instance of 'int' " +
117
+ f"but not of '{type(relation_type_id)}'")
118
+ if relation_type_id not in [EN_R_EQ, EN_R_NEQ, EN_R_LEQ, EN_R_GEQ, EN_R_LESS, EN_R_GREATER,
119
+ EN_R_IS, EN_R_NOT, EN_R_BELOW, EN_R_ABOVE]:
120
+ raise ValueError(f"Invalid value '{relation_type_id}' for 'relation_type_id'")
121
+
122
+ self.__object_type_id = object_type_id
123
+ self.__object_id = object_id
124
+ self.__attribute_id = attribute_id
125
+ self.__relation_type_id = relation_type_id
126
+ self.__value = value
127
+
128
+ super().__init__(**kwds)
129
+
130
+ @property
131
+ def object_type_id(self) -> int:
132
+ """
133
+ Returns the ID of the object type.
134
+
135
+ Will be one of the following EPANET constants:
136
+
137
+ - EN_R_NODE = 6
138
+ - EN_R_LINK = 7
139
+ - EN_R_SYSTEM = 8
140
+
141
+ Returns
142
+ -------
143
+ `int`
144
+ ID of the object type..
145
+ """
146
+ return self.__object_type_id
147
+
148
+ @property
149
+ def object_id(self) -> str:
150
+ """
151
+ Returns the ID of the object (i.e. junction, pipe, link, tank, etc.).
152
+
153
+ Returns
154
+ -------
155
+ `str`
156
+ ID of the object.
157
+ """
158
+ return self.__object_id
159
+
160
+ @property
161
+ def attribute_id(self) -> int:
162
+ """
163
+ Returns the type ID of the object's attribute that is checked.
164
+
165
+ Will be one of the following constants:
166
+
167
+ - EN_R_DEMAND = 1
168
+ - EN_R_HEAD = 2
169
+ - EN_R_LEVEL = 3
170
+ - EN_R_PRESSURE = 4
171
+ - EN_R_FLOW = 5
172
+ - EN_R_STATUS = 6
173
+ - EN_R_SETTING = 7
174
+ - EN_R_TIME = 9
175
+ - EN_R_CLOCKTIME = 10
176
+ - EN_R_FILLTIME = 11
177
+ - EN_R_DRAINTIME = 12
178
+
179
+ Returns
180
+ -------
181
+ `int`
182
+ Type ID of the object's attribute that is checked.
183
+ """
184
+ return self.__attribute_id
185
+
186
+ @property
187
+ def relation_type_id(self) -> int:
188
+ """
189
+ Returns the ID of the type of comparison.
190
+
191
+ Will be one of the following constants:
192
+
193
+ - EN_R_EQ = 0
194
+ - EN_R_NEQ = 1
195
+ - EN_R_LEQ = 2
196
+ - EN_R_GEQ = 3
197
+ - EN_R_LESS = 4
198
+ - EN_R_GREATER = 5
199
+ - EN_R_IS = 6
200
+ - EN_R_NOT = 7
201
+ - EN_R_BELOW = 8
202
+ - EN_R_ABOVE = 9
203
+
204
+ Returns
205
+ -------
206
+ `int`
207
+ ID of the type of comparison.
208
+ """
209
+ return self.__relation_type_id
210
+
211
+ @property
212
+ def value(self) -> Any:
213
+ """
214
+ Returns the value that is compared against.
215
+
216
+ Returns
217
+ -------
218
+ `Any`
219
+ Value that is compared against.
220
+ """
221
+ return self.__value
222
+
223
+ def get_attributes(self) -> dict:
224
+ return super().get_attributes() | {"object_type_id": self.__object_type_id,
225
+ "object_id": self.__object_id,
226
+ "attribute_id": self.__attribute_id,
227
+ "relation_type_id": self.__relation_type_id,
228
+ "value": self.__value}
229
+
230
+ def __eq__(self, other) -> bool:
231
+ return self.__object_type_id == other.object_type_id and \
232
+ self.__object_id == other.object_id and self.__attribute_id == other.attribute_id and \
233
+ self.__relation_type_id == other.relation_type_id and self.__value == other.value
234
+
235
+ def __str__(self) -> str:
236
+ desc = ""
237
+
238
+ if self.__attribute_id == EN_R_DEMAND:
239
+ if self.__object_type_id == ToolkitConstants.EN_R_NODE:
240
+ desc += f"JUNCTION {self.__object_id} DEMAND "
241
+ elif self.__object_type_id == ToolkitConstants.EN_R_SYSTEM:
242
+ desc += "SYSTEM DEMAND "
243
+ elif self.__attribute_id == EN_R_HEAD:
244
+ desc += f"JUNCTION {self.__object_id} HEAD "
245
+ elif self.__attribute_id == EN_R_LEVEL:
246
+ desc += f"TANK {self.__object_id} LEVEL "
247
+ elif self.__attribute_id == EN_R_PRESSURE:
248
+ desc += f"JUNCTION {self.__object_id} PRESSURE "
249
+ elif self.__attribute_id == EN_R_FLOW:
250
+ desc += f"LINK {self.__object_id} FLOW "
251
+ elif self.__attribute_id == EN_R_STATUS:
252
+ desc += f"LINK {self.__object_id} STATUS "
253
+ elif self.__attribute_id == EN_R_SETTING:
254
+ desc += f"LINK {self.__object_id} SETTING "
255
+ elif self.__attribute_id == EN_R_TIME:
256
+ desc += "SYSTEM TIME "
257
+ elif self.__attribute_id == EN_R_CLOCKTIME:
258
+ desc += "SYSTEM CLOCKTIME "
259
+ elif self.__attribute_id == EN_R_FILLTIME:
260
+ desc += f"TANK {self.__object_id} FILLTIME "
261
+ elif self.__attribute_id == EN_R_DRAINTIME:
262
+ desc += f"TANK {self.__object_id} DRAINTIME "
263
+
264
+ if self.__relation_type_id == EN_R_EQ:
265
+ desc += "= "
266
+ elif self.__relation_type_id == EN_R_IS:
267
+ desc += "IS "
268
+ elif self.__relation_type_id == EN_R_NOT:
269
+ desc += "IS NOT "
270
+ elif self.__relation_type_id == EN_R_LEQ:
271
+ desc += "<= "
272
+ elif self.__relation_type_id == EN_R_GEQ:
273
+ desc += ">= "
274
+ elif self.__relation_type_id == EN_R_ABOVE:
275
+ desc += "ABOVE "
276
+ elif self.__relation_type_id == EN_R_BELOW:
277
+ desc += "BELOW "
278
+ elif self.__relation_type_id == EN_R_LESS:
279
+ desc += "< "
280
+ elif self.__relation_type_id == EN_R_GREATER:
281
+ desc += "> "
282
+
283
+ desc += str(self.__value)
284
+
285
+ return desc
286
+
287
+
288
+ @serializable(COMPLEX_CONTROL_ACTION_ID, ".epytflow_complex_control_action")
289
+ class RuleAction(JsonSerializable):
290
+ """
291
+ Class representing a rule action.
292
+
293
+ Parameters
294
+ ----------
295
+ link_type_id : `int`
296
+ Link type ID.
297
+
298
+ Must be one of following EPANET constants:
299
+
300
+ - EN_CVPIPE = 0
301
+ - EN_PIPE = 1
302
+ - EN_PUMP = 2
303
+ - EN_PRV = 3
304
+ - EN_PSV = 4
305
+ - EN_PBV = 5
306
+ - EN_FCV = 6
307
+ - EN_TCV = 7
308
+ - EN_GPV = 8
309
+ link_id : `str`
310
+ Link ID.
311
+ action_type_id : `int`
312
+ Type ID of the action.
313
+
314
+ Must be one of the following constants:
315
+
316
+ - EN_R_ACTION_SETTING = -1
317
+ - EN_R_ACTION_STATUS_OPEN = 1
318
+ - EN_R_ACTION_STATUS_CLOSED = 2
319
+ - EN_R_ACTION_STATUS_ACTIVE = 3
320
+ action_value : `Any`
321
+ Value of the acton (e.g. pump speed).
322
+ Only relevant if action_type_id = EN_R_SETTING, will be ignored in all other cases.
323
+ """
324
+ def __init__(self, link_type_id: int, link_id: str, action_type_id: int, action_value: Any,
325
+ **kwds):
326
+ if not isinstance(link_type_id, int):
327
+ raise TypeError("'link_type_id' must be an istanace of 'int' " +
328
+ f"but not of '{type(link_type_id)}'")
329
+ if link_type_id not in [ToolkitConstants.EN_CVPIPE, ToolkitConstants.EN_PIPE,
330
+ ToolkitConstants.EN_PUMP, ToolkitConstants.EN_PRV,
331
+ ToolkitConstants.EN_PSV, ToolkitConstants.EN_PBV,
332
+ ToolkitConstants.EN_FCV, ToolkitConstants.EN_TCV,
333
+ ToolkitConstants.EN_GPV]:
334
+ raise ValueError(f"Invalid value '{link_type_id}' for 'link_type_id'")
335
+ if not isinstance(link_id, str):
336
+ raise TypeError("'link_id' must be an instance of 'str' " +
337
+ f"but not of '{type(link_id)}'")
338
+ if not isinstance(action_type_id, int):
339
+ raise TypeError("'action_type_id' must be an instance of 'int' " +
340
+ f"but not of '{type(action_type_id)}'")
341
+ if action_type_id not in [EN_R_ACTION_SETTING, EN_R_ACTION_STATUS_OPEN,
342
+ EN_R_ACTION_STATUS_CLOSED, EN_R_ACTION_STATUS_ACTIVE]:
343
+ raise ValueError(f"Invalid value '{action_type_id}' for 'action_type_id'")
344
+
345
+ self.__link_type_id = link_type_id
346
+ self.__link_id = link_id
347
+ self.__action_type_id = action_type_id
348
+ self.__action_value = action_value
349
+
350
+ super().__init__(**kwds)
351
+
352
+ @property
353
+ def link_type_id(self) -> int:
354
+ """
355
+ Returns the link type ID.
356
+
357
+ Will be one of the following EPANET constants:
358
+
359
+ - EN_CVPIPE = 0
360
+ - EN_PIPE = 1
361
+ - EN_PUMP = 2
362
+ - EN_PRV = 3
363
+ - EN_PSV = 4
364
+ - EN_PBV = 5
365
+ - EN_FCV = 6
366
+ - EN_TCV = 7
367
+ - EN_GPV = 8
368
+
369
+ Returns
370
+ -------
371
+ `int`
372
+ Link type ID.
373
+ """
374
+ return self.__link_type_id
375
+
376
+ @property
377
+ def link_id(self) -> str:
378
+ """
379
+ Returns the link ID.
380
+
381
+ Returns
382
+ -------
383
+ `str`
384
+ Link ID.
385
+ """
386
+ return self.__link_id
387
+
388
+ @property
389
+ def action_type_id(self) -> int:
390
+ """
391
+ Returns the type ID of the action.
392
+
393
+ Will be one of the following constants:
394
+
395
+ - EN_R_ACTION_SETTING = -1
396
+ - EN_R_ACTION_STATUS_OPEN = 1
397
+ - EN_R_ACTION_STATUS_CLOSED = 2
398
+ - EN_R_ACTION_STATUS_ACTIVE = 3
399
+
400
+ Returns
401
+ -------
402
+ `int`
403
+ Type ID of the action.
404
+ """
405
+ return self.__action_type_id
406
+
407
+ @property
408
+ def action_value(self) -> Any:
409
+ """
410
+ Returns the value of the acton (e.g. pump speed).
411
+ Only relevant if action_type_id = EN_R_SETTING.
412
+
413
+ Returns
414
+ -------
415
+ `Any`
416
+ Value of the action.
417
+ """
418
+ return self.__action_value
419
+
420
+ def get_attributes(self) -> dict:
421
+ return super().get_attributes() | {"link_type_id": self.__link_type_id,
422
+ "link_id": self.__link_id,
423
+ "action_type_id": self.__action_type_id,
424
+ "action_value": self.__action_value}
425
+
426
+ def __eq__(self, other) -> bool:
427
+ return self.__link_type_id == other.link_type_id and \
428
+ self.__link_id == other.link_id and \
429
+ self.__action_type_id == other.action_type_id and \
430
+ self.__action_value == other.action_value
431
+
432
+ def __str__(self) -> str:
433
+ desc = ""
434
+
435
+ if self.__link_type_id in [ToolkitConstants.EN_CVPIPE, ToolkitConstants.EN_PIPE]:
436
+ desc += "PIPE "
437
+ elif self.__link_type_id == ToolkitConstants.EN_PUMP:
438
+ desc += "PUMP "
439
+ else:
440
+ desc += "VALVE "
441
+
442
+ desc += f"{self.__link_id} "
443
+
444
+ if self.__action_type_id == EN_R_ACTION_SETTING:
445
+ desc += f"SETTING IS {self.__action_value}"
446
+ elif self.__action_type_id == EN_R_ACTION_STATUS_OPEN:
447
+ desc += "STATUS IS OPEN"
448
+ elif self.__action_type_id == EN_R_ACTION_STATUS_CLOSED:
449
+ desc += "STATUS IS CLOSED"
450
+ elif self.action_type_id == EN_R_ACTION_STATUS_ACTIVE:
451
+ desc += "STATUS IS ACTIVE"
452
+
453
+ return desc
454
+
455
+
456
+ @serializable(COMPLEX_CONTROL_ID, ".epytflow_complex_control")
457
+ class ComplexControlModule(JsonSerializable):
458
+ """
459
+ Class representing a complex control module (i.e. IF-THEN-ELSE rule) as implemented in EPANET.
460
+
461
+ Parameters
462
+ ----------
463
+ rule_id : `str`
464
+ ID of the rule.
465
+ condition_1 : :class:`~epyt_flow.simulation.scada.complex_control.RuleCondition`
466
+ First condition of this rule.
467
+ additional_conditions : list[tuple[int, :class:`~epyt_flow.simulation.scada.complex_control.RuleCondition`]]
468
+ List of (optional) additional conditions incl. their conjunction operator
469
+ (must be either EN_R_AND = 2 or EN_R_OR = 3).
470
+
471
+ Empty list if there are no additional conditions.
472
+ actions : list[:class:`~epyt_flow.simulation.scada.complex_control.RuleAction`]
473
+ List of actions that are applied if the conditions are met.
474
+ Must contain at least one action.
475
+ else_actions : list[:class:`~epyt_flow.simulation.scada.complex_control.RuleAction`]
476
+ List of actions that are applied if the conditions are NOT met.
477
+ priority : `int`
478
+ Priority of this control rule.
479
+ """
480
+ def __init__(self, rule_id: str, condition_1: RuleCondition,
481
+ additional_conditions: list[tuple[int, RuleCondition]], actions: list[RuleAction],
482
+ else_actions: list[RuleAction], priority: int, **kwds):
483
+ if not isinstance(rule_id, str):
484
+ raise TypeError(f"'rule_id' must be an instance of 'str' but not of '{type(rule_id)}'")
485
+ if not isinstance(condition_1, RuleCondition):
486
+ raise TypeError("'condition_1' must be an instance of " +
487
+ "'epyt_flow.simulation.scada.RuleCondition' " +
488
+ f"but not of '{type(condition_1)}'")
489
+ if not isinstance(additional_conditions, list) or \
490
+ any(not isinstance(condition, tuple) for condition in additional_conditions) or \
491
+ any(not isinstance(condition[0], int) or condition[0] not in [EN_R_AND, EN_R_OR] or
492
+ not isinstance(condition[1], RuleCondition)
493
+ for condition in additional_conditions):
494
+ raise TypeError("'additional_conditions' must be a list of " +
495
+ "'tuple[int, epyt_flow.simulation.scada.RuleCondition]' instances")
496
+ if not isinstance(actions, list) or any(not isinstance(action, RuleAction)
497
+ for action in actions):
498
+ raise TypeError("'actions' must be a list of " +
499
+ "'epyt_flow.simulation.scada.RuleAction' instances")
500
+ if len(actions) == 0:
501
+ raise ValueError("'actions' must contain at least one action")
502
+ if not isinstance(else_actions, list) or any(not isinstance(action, RuleAction)
503
+ for action in actions):
504
+ raise TypeError("'else_actions' must be a list of " +
505
+ "'epyt_flow.simulation.scada.RuleAction' instances")
506
+ if not isinstance(priority, int) or priority < 0:
507
+ raise TypeError("'priority' must be a non-negative integer")
508
+
509
+ self.__rule_id = rule_id
510
+ self.__condition_1 = condition_1
511
+ self.__additional_conditions = additional_conditions
512
+ self.__actions = actions
513
+ self.__else_actions = else_actions
514
+ self.__priority = priority
515
+
516
+ super().__init__(**kwds)
517
+
518
+ @property
519
+ def rule_id(self) -> str:
520
+ """
521
+ Returns the ID of this control rule.
522
+
523
+ Returns
524
+ -------
525
+ `str`
526
+ ID of this control rule.
527
+ """
528
+ return self.__rule_id
529
+
530
+ @property
531
+ def condition_1(self) -> RuleCondition:
532
+ """
533
+ Returns the first condition of this rule.
534
+
535
+ Returns
536
+ -------
537
+ :class:`~epyt_flow.simulation.scada.complex_control.RuleCondition`
538
+ First condition of this rule.
539
+ """
540
+ return deepcopy(self.__condition_1)
541
+
542
+ @property
543
+ def additional_conditions(self) -> list[tuple[int, RuleCondition]]:
544
+ """
545
+ Returns the list of (optional) additional conditions incl. their conjunction operator.
546
+ Empty list if there are no additional conditions.
547
+
548
+ Returns
549
+ -------
550
+ list[tuple[int, :class:`~epyt_flow.simulation.scada.complex_control.RuleCondition`]]
551
+ List of (optional) additional conditions incl. their conjunction operator.
552
+ """
553
+ return deepcopy(self.__additional_conditions)
554
+
555
+ @property
556
+ def actions(self) -> list[RuleAction]:
557
+ """
558
+ Returns the list of actions that are applied if the conditions are met.
559
+
560
+ Returns
561
+ -------
562
+ list[:class:`~epyt_flow.simulation.scada.complex_control.RuleAction`]
563
+ List of actions that are applied if the conditions are met.
564
+ """
565
+ return deepcopy(self.__actions)
566
+
567
+ @property
568
+ def else_actions(self) -> list[RuleAction]:
569
+ """
570
+ Returns the list of actions that are applied if the conditions are NOT met.
571
+
572
+ Returns
573
+ -------
574
+ list[:class:`~epyt_flow.simulation.scada.complex_control.RuleAction`]
575
+ List of actions that are applied if the conditions are NOT met.
576
+ """
577
+ return deepcopy(self.__else_actions)
578
+
579
+ @property
580
+ def priority(self) -> int:
581
+ """
582
+ Returns the priority of this control rule.
583
+
584
+ Returns
585
+ -------
586
+ `int`
587
+ Priority of this control rule.
588
+ """
589
+ return self.__priority
590
+
591
+ def get_attributes(self) -> dict:
592
+ return super().get_attributes() | {"rule_id": self.__rule_id,
593
+ "condition_1": self.__condition_1,
594
+ "additional_conditions": self.__additional_conditions,
595
+ "actions": self.__actions,
596
+ "else_actions": self.__else_actions,
597
+ "priority": self.__priority}
598
+
599
+ def __eq__(self, other) -> bool:
600
+ return super().__eq__(other) and self.__rule_id == other.rule_id and \
601
+ self.__priority == other.priority and self.__condition_1 == other.condition_1 and \
602
+ np.all(self.__additional_conditions == other.additional_conditions) and \
603
+ np.all(self.__actions == other.actions) and \
604
+ np.all(self.__else_actions == other.else_actions)
605
+
606
+ def __str__(self) -> str:
607
+ desc = ""
608
+
609
+ desc += f"RULE {self.__rule_id}\n"
610
+ desc += f"IF {self.__condition_1} "
611
+ for op, action in self.__additional_conditions:
612
+ if op == EN_R_AND:
613
+ desc += "\nAND "
614
+ elif op == EN_R_OR:
615
+ desc += "\nOR "
616
+
617
+ desc += f"{action} "
618
+ desc += "\nTHEN "
619
+ desc += "\nAND ".join(str(action) for action in self.__actions)
620
+ if len(self.__else_actions) != 0:
621
+ desc += "\nELSE " + "\nAND ".join(str(action) for action in self.__else_actions)
622
+
623
+ desc += f"\nPRIORITY {self.__priority}"
624
+
625
+ return desc