epyt-flow 0.1.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/EPANET/EPANET/SRC_engines/AUTHORS +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +21 -0
- epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +18 -0
- epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +134 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +5578 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +865 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +131 -0
- epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +73 -0
- epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +193 -0
- epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +1000 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +1151 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +1117 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +720 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +476 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +431 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +1786 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +468 -0
- epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +810 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input1.c +707 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input2.c +864 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input3.c +2170 -0
- epyt_flow/EPANET/EPANET/SRC_engines/main.c +93 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +142 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +24 -0
- epyt_flow/EPANET/EPANET/SRC_engines/output.c +852 -0
- epyt_flow/EPANET/EPANET/SRC_engines/project.c +1359 -0
- epyt_flow/EPANET/EPANET/SRC_engines/quality.c +685 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +743 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +694 -0
- epyt_flow/EPANET/EPANET/SRC_engines/report.c +1489 -0
- epyt_flow/EPANET/EPANET/SRC_engines/rules.c +1362 -0
- epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +871 -0
- epyt_flow/EPANET/EPANET/SRC_engines/text.h +497 -0
- epyt_flow/EPANET/EPANET/SRC_engines/types.h +874 -0
- epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +53 -0
- epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +27 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.c +107 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.h +28 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +102 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +937 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +204 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +24 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +1285 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +368 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +586 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +116 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +260 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +175 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +1504 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +401 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +791 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +2010 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +400 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +422 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +1164 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +551 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +524 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +56 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.c +158 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.h +34 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +287 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +293 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +816 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +29 -0
- epyt_flow/EPANET/EPANET-MSX/readme.txt +14 -0
- epyt_flow/EPANET/compile.sh +4 -0
- epyt_flow/VERSION +1 -0
- epyt_flow/__init__.py +24 -0
- epyt_flow/data/__init__.py +0 -0
- epyt_flow/data/benchmarks/__init__.py +11 -0
- epyt_flow/data/benchmarks/batadal.py +257 -0
- epyt_flow/data/benchmarks/batadal_data.py +28 -0
- epyt_flow/data/benchmarks/battledim.py +473 -0
- epyt_flow/data/benchmarks/battledim_data.py +51 -0
- epyt_flow/data/benchmarks/gecco_water_quality.py +267 -0
- epyt_flow/data/benchmarks/leakdb.py +592 -0
- epyt_flow/data/benchmarks/leakdb_data.py +18923 -0
- epyt_flow/data/benchmarks/water_usage.py +123 -0
- epyt_flow/data/networks.py +650 -0
- epyt_flow/gym/__init__.py +4 -0
- epyt_flow/gym/control_gyms.py +47 -0
- epyt_flow/gym/scenario_control_env.py +101 -0
- epyt_flow/metrics.py +404 -0
- epyt_flow/models/__init__.py +2 -0
- epyt_flow/models/event_detector.py +31 -0
- epyt_flow/models/sensor_interpolation_detector.py +118 -0
- epyt_flow/rest_api/__init__.py +4 -0
- epyt_flow/rest_api/base_handler.py +70 -0
- epyt_flow/rest_api/res_manager.py +95 -0
- epyt_flow/rest_api/scada_data_handler.py +476 -0
- epyt_flow/rest_api/scenario_handler.py +352 -0
- epyt_flow/rest_api/server.py +106 -0
- epyt_flow/serialization.py +438 -0
- epyt_flow/simulation/__init__.py +5 -0
- epyt_flow/simulation/events/__init__.py +6 -0
- epyt_flow/simulation/events/actuator_events.py +259 -0
- epyt_flow/simulation/events/event.py +81 -0
- epyt_flow/simulation/events/leakages.py +404 -0
- epyt_flow/simulation/events/sensor_faults.py +267 -0
- epyt_flow/simulation/events/sensor_reading_attack.py +185 -0
- epyt_flow/simulation/events/sensor_reading_event.py +170 -0
- epyt_flow/simulation/events/system_event.py +88 -0
- epyt_flow/simulation/parallel_simulation.py +147 -0
- epyt_flow/simulation/scada/__init__.py +3 -0
- epyt_flow/simulation/scada/advanced_control.py +134 -0
- epyt_flow/simulation/scada/scada_data.py +1589 -0
- epyt_flow/simulation/scada/scada_data_export.py +255 -0
- epyt_flow/simulation/scenario_config.py +608 -0
- epyt_flow/simulation/scenario_simulator.py +1897 -0
- epyt_flow/simulation/scenario_visualizer.py +61 -0
- epyt_flow/simulation/sensor_config.py +1289 -0
- epyt_flow/topology.py +290 -0
- epyt_flow/uncertainty/__init__.py +3 -0
- epyt_flow/uncertainty/model_uncertainty.py +302 -0
- epyt_flow/uncertainty/sensor_noise.py +73 -0
- epyt_flow/uncertainty/uncertainties.py +555 -0
- epyt_flow/uncertainty/utils.py +206 -0
- epyt_flow/utils.py +306 -0
- epyt_flow-0.1.0.dist-info/LICENSE +21 -0
- epyt_flow-0.1.0.dist-info/METADATA +139 -0
- epyt_flow-0.1.0.dist-info/RECORD +131 -0
- epyt_flow-0.1.0.dist-info/WHEEL +5 -0
- epyt_flow-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module provides functions and classes for serialization.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Any, Union
|
|
5
|
+
from abc import abstractmethod, ABC
|
|
6
|
+
from io import BufferedIOBase
|
|
7
|
+
import importlib
|
|
8
|
+
import json
|
|
9
|
+
import gzip
|
|
10
|
+
import umsgpack
|
|
11
|
+
import numpy as np
|
|
12
|
+
import networkx
|
|
13
|
+
import scipy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SCIPY_BSRARRAY_ID = -3
|
|
17
|
+
NETWORKX_GRAPH_ID = -2
|
|
18
|
+
NUMPY_ARRAY_ID = -1
|
|
19
|
+
SENSOR_CONFIG_ID = 0
|
|
20
|
+
SCENARIO_CONFIG_ID = 1
|
|
21
|
+
MODEL_UNCERTAINTY_ID = 2
|
|
22
|
+
SENSOR_NOISE_ID = 3
|
|
23
|
+
ABSOLUTE_GAUSSIAN_UNCERTAINTY_ID = 4
|
|
24
|
+
RELATIVE_GAUSSIAN_UNCERTAINTY_ID = 5
|
|
25
|
+
ABSOLUTE_UNIFORM_UNCERTAINTY_ID = 6
|
|
26
|
+
RELATIVE_UNIFORM_UNCERTAINTY_ID = 7
|
|
27
|
+
PERCENTAGE_DEVIATON_UNCERTAINTY_ID = 8
|
|
28
|
+
ABSOLUTE_DEEP_UNIFORM_UNCERTAINTY_ID = 9
|
|
29
|
+
RELATIVE_DEEP_UNIFORM_UNCERTAINTY_ID = 10
|
|
30
|
+
ABSOLUTE_DEEP_GAUSSIAN_UNCERTAINTY_ID = 11
|
|
31
|
+
RELATIVE_DEEP_GAUSSIAN_UNCERTAINTY_ID = 12
|
|
32
|
+
ABSOLUTE_DEEP_UNCERTAINTY_ID = 13
|
|
33
|
+
RELATIVE_DEEP_UNCERTAINTY_ID = 14
|
|
34
|
+
SENSOR_FAULT_CONSTANT_ID = 15
|
|
35
|
+
SENSOR_FAULT_DRIFT_ID = 16
|
|
36
|
+
SENSOR_FAULT_GAUSSIAN_ID = 17
|
|
37
|
+
SENSOR_FAULT_PERCENTAGE_ID = 18
|
|
38
|
+
SENSOR_FAULT_STUCKATZERO_ID = 19
|
|
39
|
+
LEAKAGE_ID = 20
|
|
40
|
+
ABRUPT_LEAKAGE_ID = 21
|
|
41
|
+
INCIPIENT_LEAKAGE_ID = 22
|
|
42
|
+
SCADA_DATA_ID = 23
|
|
43
|
+
SENSOR_ATTACK_OVERRIDE_ID = 24
|
|
44
|
+
SENSOR_ATTACK_REPLAY_ID = 25
|
|
45
|
+
NETWORK_TOPOLOGY_ID = 26
|
|
46
|
+
PUMP_STATE_EVENT_ID = 28
|
|
47
|
+
PUMP_SPEED_EVENT_ID = 29
|
|
48
|
+
VALVE_STATE_EVENT_ID = 30
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def my_packb(data: Any) -> bytes:
|
|
52
|
+
"""
|
|
53
|
+
Overriden `umsgpack.packb` method to support custom serialization handlers.
|
|
54
|
+
"""
|
|
55
|
+
return umsgpack.packb(data, ext_handlers=ext_handler_pack)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def my_unpackb(data: Any) -> Any:
|
|
59
|
+
"""
|
|
60
|
+
Overriden `umsgpack.unpackb` method to support custom serialization handlers.
|
|
61
|
+
"""
|
|
62
|
+
return umsgpack.unpackb(data, ext_handlers=ext_handler_unpack)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def serializable(my_id: int, my_file_ext: str) -> Any:
|
|
66
|
+
"""
|
|
67
|
+
Decorator for a serializable class -- i.e. subclass of
|
|
68
|
+
:class:`~epyt_flow.serialization.Serializable`.
|
|
69
|
+
|
|
70
|
+
This decorator registers a new class as a serializable class.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
my_id : `int`
|
|
75
|
+
ID of the class.
|
|
76
|
+
my_file_ext : `str`
|
|
77
|
+
File extension.
|
|
78
|
+
"""
|
|
79
|
+
def wrapper(my_class):
|
|
80
|
+
@staticmethod
|
|
81
|
+
def unpackb(data: bytes) -> Any:
|
|
82
|
+
return my_class(**my_unpackb(data))
|
|
83
|
+
setattr(my_class, "unpackb", unpackb)
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def file_ext() -> str:
|
|
87
|
+
return my_file_ext
|
|
88
|
+
setattr(my_class, "file_ext", file_ext)
|
|
89
|
+
|
|
90
|
+
return umsgpack.ext_serializable(my_id)(my_class)
|
|
91
|
+
|
|
92
|
+
return wrapper
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Serializable(ABC):
|
|
96
|
+
"""
|
|
97
|
+
Base class for a serializable class -- must be used in conjunction with the
|
|
98
|
+
:func:`~epyt_flow.serialization.serializable` decorator.
|
|
99
|
+
"""
|
|
100
|
+
def __init__(self, **kwds):
|
|
101
|
+
super().__init__(**kwds)
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def get_attributes(self) -> dict:
|
|
105
|
+
"""
|
|
106
|
+
Gets all attributes to be serialized -- these attributes are passed to the
|
|
107
|
+
constructor when the object is deserialized.
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
`dict`
|
|
112
|
+
Dictionary of attributes -- i.e. pairs of attribute name + value.
|
|
113
|
+
"""
|
|
114
|
+
return {}
|
|
115
|
+
|
|
116
|
+
def file_ext(self) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Returns the file extension of this class.
|
|
119
|
+
|
|
120
|
+
This function is automatically implemented by applying the
|
|
121
|
+
:func:`~epyt_flow.serialization.serializable` decorator.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
`str`
|
|
126
|
+
File extension.
|
|
127
|
+
"""
|
|
128
|
+
raise NotImplementedError()
|
|
129
|
+
|
|
130
|
+
def packb(self) -> bytes:
|
|
131
|
+
"""
|
|
132
|
+
Serializes the attributes of this object.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
`bytes`
|
|
137
|
+
Serialized object.
|
|
138
|
+
"""
|
|
139
|
+
return my_packb(self.get_attributes())
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def load(data: Union[bytes, BufferedIOBase]) -> Any:
|
|
143
|
+
"""
|
|
144
|
+
Deserializes an instance of this class.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
data : `bytes` or `io.BufferedIOBase`
|
|
149
|
+
Serialized data or stream from which serialized data can be read.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
`Any`
|
|
154
|
+
Deserialized object.
|
|
155
|
+
"""
|
|
156
|
+
return load(data)
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def load_from_file(f_in: str, use_zip: bool = True) -> Any:
|
|
160
|
+
"""
|
|
161
|
+
Deserializes an instance of this class from a (compressed) file.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
f_in : `str`
|
|
166
|
+
Path to the file from which to deserialize the object.
|
|
167
|
+
use_zip : `bool`, optional
|
|
168
|
+
If True, the file `f_in` is supposed to be zip compressed -- False,
|
|
169
|
+
if no compression was used when serializing the object.
|
|
170
|
+
|
|
171
|
+
The default is True.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
`Any`
|
|
176
|
+
Deserialized object.
|
|
177
|
+
"""
|
|
178
|
+
return load_from_file(f_in, use_zip)
|
|
179
|
+
|
|
180
|
+
def dump(self, stream_out: BufferedIOBase = None) -> Any:
|
|
181
|
+
"""
|
|
182
|
+
Serializes this object to a byte array.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
stream_out : `io.BufferedIOBase`, optional
|
|
187
|
+
Stream to which the serialized object is written.
|
|
188
|
+
If None, the serialized object is returned as a `bytes` array.
|
|
189
|
+
|
|
190
|
+
The default is None.
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
`bytes`
|
|
195
|
+
If `stream_out` is None, the serialized object is returned as a `bytes` array.
|
|
196
|
+
"""
|
|
197
|
+
return dump(self, stream_out)
|
|
198
|
+
|
|
199
|
+
def save_to_file(self, f_out: str, use_zip: bool = True) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Serializes this instance and stores it in a (compressed) file.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
f_in : `str`
|
|
206
|
+
Path to the file where this serialized object will be stored.
|
|
207
|
+
use_zip : `bool`, optional
|
|
208
|
+
If True, the file `f_in` will be zip compressed -- False,
|
|
209
|
+
if no compression is wanted.
|
|
210
|
+
|
|
211
|
+
The default is True.
|
|
212
|
+
"""
|
|
213
|
+
if not f_out.endswith(self.file_ext()):
|
|
214
|
+
f_out += self.file_ext()
|
|
215
|
+
|
|
216
|
+
return save_to_file(f_out, self, use_zip)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def my_to_json(obj: Any) -> str:
|
|
220
|
+
"""
|
|
221
|
+
Serializes a given object to JSON.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
obj : `Any`
|
|
226
|
+
Object to be serialized.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
`str`
|
|
231
|
+
JSON data.
|
|
232
|
+
"""
|
|
233
|
+
def __json_serialize(obj_: Any) -> dict:
|
|
234
|
+
if isinstance(obj_, JsonSerializable):
|
|
235
|
+
my_class_name = (obj_.__module__, obj_.__class__.__name__)
|
|
236
|
+
return obj_.get_attributes() | {"__type__": my_class_name}
|
|
237
|
+
elif isinstance(obj_, np.ndarray):
|
|
238
|
+
return obj_.tolist()
|
|
239
|
+
else:
|
|
240
|
+
return obj_
|
|
241
|
+
|
|
242
|
+
return json.dumps(obj, default=__json_serialize)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def my_load_from_json(data: str) -> Any:
|
|
246
|
+
"""
|
|
247
|
+
Loads (i.e. deserializes) an object from given JSON data.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
data : `str`
|
|
252
|
+
JSON data.
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
`Any`
|
|
257
|
+
Deserialized object.
|
|
258
|
+
"""
|
|
259
|
+
def __object_hook(obj: dict) -> dict:
|
|
260
|
+
if "__type__" in obj:
|
|
261
|
+
module_name, class_name = obj["__type__"]
|
|
262
|
+
cls = getattr(importlib.import_module(module_name), class_name)
|
|
263
|
+
del obj["__type__"]
|
|
264
|
+
|
|
265
|
+
for attr in obj:
|
|
266
|
+
if isinstance(attr, dict):
|
|
267
|
+
obj[attr] = __object_hook(obj[attr])
|
|
268
|
+
|
|
269
|
+
return cls(**obj)
|
|
270
|
+
return obj
|
|
271
|
+
|
|
272
|
+
return json.loads(data, object_hook=__object_hook)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class JsonSerializable(Serializable):
|
|
276
|
+
"""
|
|
277
|
+
Base class for JSON serializable classes.
|
|
278
|
+
Inherits from :class:`~epyt_flow.serialization.Serializable`.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def to_json(self) -> str:
|
|
282
|
+
"""
|
|
283
|
+
Serializes this instance to JSON.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
`str`
|
|
288
|
+
JSON data.
|
|
289
|
+
"""
|
|
290
|
+
return my_to_json(self)
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
def load_from_json(data: str) -> Any:
|
|
294
|
+
"""
|
|
295
|
+
Loads (i.e. deserializes) an instance of this class from given JSON data.
|
|
296
|
+
|
|
297
|
+
Parameters
|
|
298
|
+
----------
|
|
299
|
+
data : `str`
|
|
300
|
+
JSON data.
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
`Any`
|
|
305
|
+
Deserialized instance of this class.
|
|
306
|
+
"""
|
|
307
|
+
return my_load_from_json(data)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def load(data: Union[bytes, BufferedIOBase]) -> Any:
|
|
311
|
+
"""
|
|
312
|
+
Deserializes data.
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
data : `bytes` or `io.BufferedIOBase`
|
|
317
|
+
Serialized data or stream from which serialized data can be read.
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
`Any`
|
|
322
|
+
Deserialized data.
|
|
323
|
+
"""
|
|
324
|
+
if isinstance(data, bytes):
|
|
325
|
+
return my_unpackb(data)
|
|
326
|
+
elif isinstance(data, BufferedIOBase):
|
|
327
|
+
return my_unpackb(data.read())
|
|
328
|
+
else:
|
|
329
|
+
raise TypeError("Invalid type of 'data' -- must be either instance of 'bytes' or " +
|
|
330
|
+
f"'io.BufferedIOBase' but not of '{type(data)}'")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def dump(data: Any, stream_out: BufferedIOBase = None) -> Union[bytes, None]:
|
|
334
|
+
"""
|
|
335
|
+
Serializes some given data to a byte array.
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
stream_out : `io.BufferedIOBase`, optional
|
|
340
|
+
Stream to which the serialized object is written.
|
|
341
|
+
If None, the serialized object is returned as a `bytes` array.
|
|
342
|
+
|
|
343
|
+
The default is None.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
`bytes`
|
|
348
|
+
Serialized data if `stream_out` is None -- otherwise, nothing is returned.
|
|
349
|
+
"""
|
|
350
|
+
if stream_out is None:
|
|
351
|
+
return my_packb(data)
|
|
352
|
+
else:
|
|
353
|
+
if not isinstance(stream_out, BufferedIOBase):
|
|
354
|
+
raise TypeError("'stream_out' must be an instance of 'io.BufferedIOBase' " +
|
|
355
|
+
f"but not of '{type(stream_out)}'")
|
|
356
|
+
|
|
357
|
+
stream_out.write(my_packb(data))
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def load_from_file(f_in: str, use_compression: bool = True) -> Any:
|
|
361
|
+
"""
|
|
362
|
+
Deserializes data from a (compressed) file.
|
|
363
|
+
|
|
364
|
+
Parameters
|
|
365
|
+
----------
|
|
366
|
+
f_in : `str`
|
|
367
|
+
Path to the file from which to deserialize the data.
|
|
368
|
+
use_compression : `bool`, optional
|
|
369
|
+
If True, the file `f_in` is supposed to be gzip compressed -- False,
|
|
370
|
+
if no compression was used when serializing the data.
|
|
371
|
+
|
|
372
|
+
The default is True.
|
|
373
|
+
|
|
374
|
+
Returns
|
|
375
|
+
-------
|
|
376
|
+
`Any`
|
|
377
|
+
Deserialized data.
|
|
378
|
+
"""
|
|
379
|
+
if use_compression is False:
|
|
380
|
+
with open(f_in, "rb") as f:
|
|
381
|
+
return umsgpack.unpack(f, ext_handlers=ext_handler_unpack)
|
|
382
|
+
else:
|
|
383
|
+
with gzip.open(f_in, "rb") as f:
|
|
384
|
+
return load(f.read())
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def save_to_file(f_out: str, data: Any, use_compression: bool = True) -> None:
|
|
388
|
+
"""
|
|
389
|
+
Serializes data and stores it in a (compressed) file.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
f_in : `str`
|
|
394
|
+
Path to the file where the serialized data will be stored.
|
|
395
|
+
use_compression : `bool`, optional
|
|
396
|
+
If True, the file `f_in` will be gzip compressed -- False, if no compression is wanted.
|
|
397
|
+
|
|
398
|
+
The default is True.
|
|
399
|
+
"""
|
|
400
|
+
if use_compression is False:
|
|
401
|
+
with open(f_out, "wb") as f:
|
|
402
|
+
umsgpack.pack(data, f, ext_handlers=ext_handler_pack)
|
|
403
|
+
else:
|
|
404
|
+
with gzip.open(f_out, "wb") as f:
|
|
405
|
+
f.write(dump(data))
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# Add numpy.ndarray, networkx.Graph, and scipy.sparse.bsr_array support
|
|
409
|
+
def __encode_bsr_array(array: scipy.sparse.bsr_array
|
|
410
|
+
) -> tuple[tuple[int, int], tuple[list[float], tuple[list[int], list[int]]]]:
|
|
411
|
+
shape = array.shape
|
|
412
|
+
data = array.data.flatten().tolist()
|
|
413
|
+
rows = array.nonzero()[0].tolist()
|
|
414
|
+
cols = array.nonzero()[1].tolist()
|
|
415
|
+
|
|
416
|
+
return shape, (data, (rows, cols))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def __decode_bsr_array(ext_data: tuple[tuple[int, int],
|
|
420
|
+
tuple[list[float], tuple[list[int], list[int]]]]
|
|
421
|
+
) -> scipy.sparse.bsr_array:
|
|
422
|
+
shape, data = ext_data
|
|
423
|
+
return scipy.sparse.bsr_array((data[0], (data[1][0], data[1][1])), shape=(shape[0], shape[1]))
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
ext_handler_pack = {np.ndarray:
|
|
427
|
+
lambda arr: umsgpack.Ext(NUMPY_ARRAY_ID, umsgpack.packb(arr.tolist())),
|
|
428
|
+
networkx.Graph:
|
|
429
|
+
lambda graph:
|
|
430
|
+
umsgpack.Ext(NETWORKX_GRAPH_ID,
|
|
431
|
+
umsgpack.packb(networkx.node_link_data(graph))),
|
|
432
|
+
scipy.sparse.bsr_array:
|
|
433
|
+
lambda arr: umsgpack.Ext(SCIPY_BSRARRAY_ID,
|
|
434
|
+
umsgpack.packb(__encode_bsr_array(arr)))}
|
|
435
|
+
ext_handler_unpack = {NUMPY_ARRAY_ID: lambda ext: np.array(umsgpack.unpackb(ext.data)),
|
|
436
|
+
NETWORKX_GRAPH_ID:
|
|
437
|
+
lambda ext: networkx.node_link_graph(umsgpack.unpackb(ext.data)),
|
|
438
|
+
SCIPY_BSRARRAY_ID: lambda ext: __decode_bsr_array(umsgpack.unpackb(ext.data))}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module provides implementations of different types of actuator events.
|
|
3
|
+
"""
|
|
4
|
+
import warnings
|
|
5
|
+
from epyt.epanet import epanet
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .system_event import SystemEvent
|
|
9
|
+
from ...serialization import serializable, JsonSerializable, PUMP_STATE_EVENT_ID, \
|
|
10
|
+
PUMP_SPEED_EVENT_ID, VALVE_STATE_EVENT_ID
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ActuatorConstants:
|
|
14
|
+
"""
|
|
15
|
+
Class defining some constants related to actuator events.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
EN_CLOSED
|
|
20
|
+
Valve or pump is closed.
|
|
21
|
+
EN_OPEN
|
|
22
|
+
Valve or pump is open -- i.e. active.
|
|
23
|
+
"""
|
|
24
|
+
EN_CLOSED = 0
|
|
25
|
+
EN_OPEN = 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ActuatorEvent(SystemEvent):
|
|
29
|
+
"""
|
|
30
|
+
Base class of an actuator event.
|
|
31
|
+
|
|
32
|
+
.. note::
|
|
33
|
+
Note that actuator events are one-time events -- i.e.
|
|
34
|
+
they are executed only once at a given point in time.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
time : int
|
|
39
|
+
Time (in seconds since simulation start) at which this event is executed.
|
|
40
|
+
"""
|
|
41
|
+
def __init__(self, time: int, **kwds):
|
|
42
|
+
super().__init__(start_time=time, end_time=time+1, **kwds)
|
|
43
|
+
|
|
44
|
+
def get_attributes(self) -> dict:
|
|
45
|
+
return {"time": self.start_time}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PumpEvent(ActuatorEvent):
|
|
49
|
+
"""
|
|
50
|
+
Base class of a pump event.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
pump_id : str
|
|
55
|
+
ID of the pump that is affected by this event.
|
|
56
|
+
"""
|
|
57
|
+
def __init__(self, pump_id: str, **kwds):
|
|
58
|
+
self.__pump_id = pump_id
|
|
59
|
+
|
|
60
|
+
super().__init__(**kwds)
|
|
61
|
+
|
|
62
|
+
def init(self, epanet_api: epanet) -> None:
|
|
63
|
+
if self.__pump_id not in epanet_api.getLinkPumpNameID():
|
|
64
|
+
raise ValueError(f"Invalid pump ID '{self.__pump_id}'")
|
|
65
|
+
|
|
66
|
+
super().init(epanet_api)
|
|
67
|
+
|
|
68
|
+
def get_attributes(self) -> dict:
|
|
69
|
+
return super().get_attributes() | {"pump_id": self.__pump_id}
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def pump_id(self) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Gets the ID of the pump affected by this event.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
`str`
|
|
79
|
+
Pump ID.
|
|
80
|
+
"""
|
|
81
|
+
return self.__pump_id
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@serializable(PUMP_STATE_EVENT_ID, ".epytflow_pump_state_event")
|
|
85
|
+
class PumpStateEvent(PumpEvent, JsonSerializable):
|
|
86
|
+
"""
|
|
87
|
+
Class implementing a pump state event.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
pump_state : `str`
|
|
92
|
+
New state of the pump -- i.e. the state of the pump is set to this value
|
|
93
|
+
while the event is active.
|
|
94
|
+
|
|
95
|
+
Must be one of the following constants defined in
|
|
96
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
97
|
+
|
|
98
|
+
- EN_CLOSED = 0
|
|
99
|
+
- EN_OPEN = 1
|
|
100
|
+
"""
|
|
101
|
+
def __init__(self, pump_state: int, **kwds):
|
|
102
|
+
if not isinstance(pump_state, int):
|
|
103
|
+
raise TypeError("'pump_state' must be an instace of 'int' " +
|
|
104
|
+
f"but not of {type(pump_state)}")
|
|
105
|
+
if not 0 <= pump_state <= 1:
|
|
106
|
+
raise ValueError(f"Invalid pump state '{pump_state}' -- " +
|
|
107
|
+
"must be either EN_CLOSED (0) or EN_OPEN (1)")
|
|
108
|
+
|
|
109
|
+
self.__pump_state = pump_state
|
|
110
|
+
|
|
111
|
+
super().__init__(**kwds)
|
|
112
|
+
|
|
113
|
+
def get_attributes(self) -> dict:
|
|
114
|
+
return super().get_attributes() | {"pump_state": self.__pump_state}
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def pump_state(self) -> int:
|
|
118
|
+
"""
|
|
119
|
+
Gets the new pump state.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
`int`
|
|
124
|
+
New pump state.
|
|
125
|
+
|
|
126
|
+
One of the following constants defined in
|
|
127
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
128
|
+
|
|
129
|
+
- EN_CLOSED = 0
|
|
130
|
+
- EN_OPEN = 1
|
|
131
|
+
"""
|
|
132
|
+
return self.__pump_state
|
|
133
|
+
|
|
134
|
+
def apply(self, cur_time: int) -> None:
|
|
135
|
+
pump_idx = self._epanet_api.getLinkPumpNameID().index(self.pump_id) + 1
|
|
136
|
+
pump_link_idx = self._epanet_api.getLinkPumpIndex(pump_idx)
|
|
137
|
+
self._epanet_api.setLinkStatus(pump_link_idx, self.__pump_state)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@serializable(PUMP_SPEED_EVENT_ID, ".epytflow_pump_speed_event")
|
|
141
|
+
class PumpSpeedEvent(PumpEvent, JsonSerializable):
|
|
142
|
+
"""
|
|
143
|
+
Class implementing a pump speed event.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
pump_speed : float
|
|
148
|
+
New pump speed -- i.e. the speed of the pump is set to this value while the event is active.
|
|
149
|
+
"""
|
|
150
|
+
def __init__(self, pump_speed: float, **kwds):
|
|
151
|
+
if not isinstance(pump_speed, float):
|
|
152
|
+
raise TypeError("'pump_speed' must be an instance of 'float' " +
|
|
153
|
+
f"but not of {type(pump_speed)}")
|
|
154
|
+
if pump_speed <= 0:
|
|
155
|
+
raise ValueError("Pump speed must be positive")
|
|
156
|
+
|
|
157
|
+
self.__pump_speed = pump_speed
|
|
158
|
+
|
|
159
|
+
super().__init__(**kwds)
|
|
160
|
+
|
|
161
|
+
def get_attributes(self) -> dict:
|
|
162
|
+
return super().get_attributes() | {"pump_speed": self.__pump_speed}
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def pump_speed(self) -> float:
|
|
166
|
+
"""
|
|
167
|
+
Gets the new pump speed.
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
`float`
|
|
172
|
+
New pump speed.
|
|
173
|
+
"""
|
|
174
|
+
return self.__pump_speed
|
|
175
|
+
|
|
176
|
+
def apply(self, cur_time: int) -> None:
|
|
177
|
+
pump_idx = self._epanet_api.getLinkPumpNameID().index(self.pump_id) + 1
|
|
178
|
+
pattern_idx = self._epanet_api.getLinkPumpPatternIndex(pump_idx)
|
|
179
|
+
|
|
180
|
+
if pattern_idx == 0:
|
|
181
|
+
warnings.warn(f"No pattern for pump '{self.pump_id}' found -- a new pattern is created")
|
|
182
|
+
pattern_idx = self._epanet_api.addPattern(f"pump_speed_{self.pump_id}")
|
|
183
|
+
self._epanet_api.setLinkPumpPatternIndex(pattern_idx)
|
|
184
|
+
|
|
185
|
+
self._epanet_api.setPattern(pattern_idx, np.array([self.__pump_speed]))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@serializable(VALVE_STATE_EVENT_ID, ".epytflow_valve_state_event")
|
|
189
|
+
class ValveStateEvent(ActuatorEvent, JsonSerializable):
|
|
190
|
+
"""
|
|
191
|
+
Class implementing a valve state event.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
valve_id : `str`
|
|
196
|
+
ID of the valve that is affected by this event.
|
|
197
|
+
valve_state : `str`
|
|
198
|
+
New state of the valve -- i.e. the valve state is set to this value this event is executed.
|
|
199
|
+
|
|
200
|
+
Must be one of the following constants defined in
|
|
201
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
202
|
+
|
|
203
|
+
- EN_CLOSED = 0
|
|
204
|
+
- EN_OPEN = 1
|
|
205
|
+
"""
|
|
206
|
+
def __init__(self, valve_id: str, valve_state: int, **kwds):
|
|
207
|
+
if not isinstance(valve_state, int):
|
|
208
|
+
raise TypeError("'valve_state' must be an instance of 'int' " +
|
|
209
|
+
f"but not of {type(valve_state)}")
|
|
210
|
+
if not 0 <= valve_state <= 1:
|
|
211
|
+
raise ValueError(f"Invalid valve state '{valve_state}' -- " +
|
|
212
|
+
"must be either EN_CLOSED (0) or EN_OPEN (1)")
|
|
213
|
+
|
|
214
|
+
self.__valve_id = valve_id
|
|
215
|
+
self.__valve_state = valve_state
|
|
216
|
+
|
|
217
|
+
super().__init__(**kwds)
|
|
218
|
+
|
|
219
|
+
def init(self, epanet_api: epanet) -> None:
|
|
220
|
+
if self.__valve_id not in epanet_api.getLinkValveNameID():
|
|
221
|
+
raise ValueError(f"Invalid valve ID '{self.__valve_id}'")
|
|
222
|
+
|
|
223
|
+
super().init(epanet_api)
|
|
224
|
+
|
|
225
|
+
def get_attributes(self) -> dict:
|
|
226
|
+
return super().get_attributes() | {"valve_id": self.__valve_id,
|
|
227
|
+
"valve_state": self.__valve_state}
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def valve_id(self) -> str:
|
|
231
|
+
"""
|
|
232
|
+
Gets the ID of the valve affected by this event.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
`str`
|
|
237
|
+
Valve ID.
|
|
238
|
+
"""
|
|
239
|
+
return self.__valve_id
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def valve_state(self) -> int:
|
|
243
|
+
"""
|
|
244
|
+
Gets the new state of the valve.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
`int`
|
|
249
|
+
New valve state.
|
|
250
|
+
|
|
251
|
+
One of the following constants defined in
|
|
252
|
+
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
253
|
+
"""
|
|
254
|
+
return self.__valve_state
|
|
255
|
+
|
|
256
|
+
def apply(self, cur_time: int) -> None:
|
|
257
|
+
valve_idx = self._epanet_api.getLinkValveNameID().index(self.__valve_id) + 1
|
|
258
|
+
valve_link_idx = self._epanet_api.getLinkValveIndex(valve_idx)
|
|
259
|
+
self._epanet_api.setLinkStatus(valve_link_idx, self.__valve_state)
|