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,134 @@
1
+ """
2
+ Module provides a base class for custom control modules.
3
+ """
4
+ from abc import abstractmethod, ABC
5
+ import warnings
6
+ import numpy as np
7
+ import epyt
8
+
9
+ from . import ScadaData
10
+
11
+
12
+ class CustomControlModule(ABC):
13
+ """
14
+ Base class for a custom control module.
15
+
16
+ Attributes
17
+ ----------
18
+ epanet_api : `epyt.epanet <https://epanet-python-toolkit-epyt.readthedocs.io/en/latest/api.html#epyt.epanet.epanet>`_
19
+ API to EPANET and EPANET-MSX. Is set in :func:`init`.
20
+ """
21
+ def __init__(self, **kwds):
22
+ self._epanet_api = None
23
+
24
+ super().__init__(**kwds)
25
+
26
+ def init(self, epanet_api: epyt.epanet) -> None:
27
+ """
28
+ Initializes the control module.
29
+
30
+ Parameters
31
+ ----------
32
+ epanet_api : `epyt.epanet <https://epanet-python-toolkit-epyt.readthedocs.io/en/latest/api.html#epyt.epanet.epanet>`_
33
+ API to EPANET for implementing the control module.
34
+ """
35
+ if not isinstance(epanet_api, epyt.epanet):
36
+ raise TypeError("'epanet_api' must be an instance of 'epyt.epanet' but not of " +
37
+ f"'{type(epanet_api)}'")
38
+
39
+ self._epanet_api = epanet_api
40
+
41
+ def set_pump_status(self, pump_id: str, status: int) -> None:
42
+ """
43
+ Sets the status of a pump.
44
+
45
+ Parameters
46
+ ----------
47
+ pump_id : `str`
48
+ ID of the pump for which the status is set.
49
+ status : `int`
50
+ New status of the pump -- either active (i.e. open) or inactive (i.e. closed).
51
+
52
+ Must be one of the following constants defined in
53
+ :class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
54
+
55
+ - EN_CLOSED = 0
56
+ - EN_OPEN = 1
57
+ """
58
+ pump_idx = self._epanet_api.getLinkPumpNameID().index(pump_id)
59
+ pump_link_idx = self._epanet_api.getLinkPumpIndex(pump_idx + 1)
60
+ self._epanet_api.setLinkStatus(pump_link_idx, status)
61
+
62
+ def set_pump_speed(self, pump_id: str, speed: float) -> None:
63
+ """
64
+ Sets the speed of a pump.
65
+
66
+ Parameters
67
+ ----------
68
+ pump_id : `str`
69
+ ID of the pump for which the pump speed is set.
70
+ speed : `float`
71
+ New pump speed.
72
+ """
73
+ pump_idx = self._epanet_api.getLinkPumpNameID().index(pump_id)
74
+ pattern_idx = self._epanet_api.getLinkPumpPatternIndex(pump_idx + 1)
75
+
76
+ if pattern_idx == 0:
77
+ warnings.warn(f"No pattern for pump '{pump_id}' found -- a new pattern is created")
78
+ pattern_idx = self._epanet_api.addPattern(f"pump_speed_{pump_id}")
79
+ self._epanet_api.setLinkPumpPatternIndex(pattern_idx)
80
+
81
+ self._epanet_api.setPattern(pattern_idx, np.array([speed]))
82
+
83
+ def set_valve_status(self, valve_id: str, status: int) -> None:
84
+ """
85
+ Sets the status of a valve.
86
+
87
+ Parameters
88
+ ----------
89
+ valve_id : `str`
90
+ ID of the valve for which the status is set.
91
+ status : `int`
92
+ New status of the valve -- either open or closed.
93
+
94
+ Must be one of the following constants defined in
95
+ :class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
96
+
97
+ - EN_CLOSED = 0
98
+ - EN_OPEN = 1
99
+ """
100
+ valve_idx = self._epanet_api.getLinkValveNameID().index(valve_id)
101
+ valve_link_idx = self._epanet_api.getLinkValveIndex()[valve_idx]
102
+ self._epanet_api.setLinkStatus(valve_link_idx, status)
103
+
104
+ def set_node_quality_source_value(self, node_id: str, pattern_id: str,
105
+ qual_value: float) -> None:
106
+ """
107
+ Sets the quality source at a particular node to a specific value -- e.g.
108
+ setting the chlorine concentration injection to a specified value.
109
+
110
+ Parameters
111
+ ----------
112
+ node_id : `str`
113
+ ID of the node.
114
+ pattern_id : `str`
115
+ ID of the quality pattern at the specific node.
116
+ qual_value : `float`
117
+ New quality source value.
118
+ """
119
+ node_idx = self._epanet_api.getNodeIndex(node_id)
120
+ pattern_idx = self._epanet_api.getPatternIndex(pattern_id)
121
+ self._epanet_api.setNodeSourceQuality(node_idx, 1)
122
+ self._epanet_api.setPattern(pattern_idx, np.array([qual_value]))
123
+
124
+ @abstractmethod
125
+ def step(self, scada_data: ScadaData) -> None:
126
+ """
127
+ Implements the control algorithm -- i.e. mapping of sensor reading to actions.
128
+
129
+ Parameters
130
+ ----------
131
+ scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
132
+ Sensor readings.
133
+ """
134
+ raise NotImplementedError()