epyt-flow 0.7.3__py3-none-any.whl → 0.8.1__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/leakdb.py +2 -2
- epyt_flow/data/networks.py +14 -26
- epyt_flow/gym/scenario_control_env.py +129 -10
- epyt_flow/serialization.py +10 -3
- epyt_flow/simulation/events/__init__.py +1 -0
- epyt_flow/simulation/events/leakages.py +55 -12
- epyt_flow/simulation/events/quality_events.py +194 -0
- epyt_flow/simulation/events/system_event.py +5 -0
- epyt_flow/simulation/scada/scada_data.py +512 -64
- epyt_flow/simulation/scada/scada_data_export.py +7 -5
- epyt_flow/simulation/scenario_config.py +13 -2
- epyt_flow/simulation/scenario_simulator.py +275 -187
- epyt_flow/simulation/scenario_visualizer.py +1259 -13
- {epyt_flow-0.7.3.dist-info → epyt_flow-0.8.1.dist-info}/METADATA +31 -30
- {epyt_flow-0.7.3.dist-info → epyt_flow-0.8.1.dist-info}/RECORD +19 -18
- {epyt_flow-0.7.3.dist-info → epyt_flow-0.8.1.dist-info}/WHEEL +1 -1
- {epyt_flow-0.7.3.dist-info → epyt_flow-0.8.1.dist-info}/LICENSE +0 -0
- {epyt_flow-0.7.3.dist-info → epyt_flow-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module provides a class for implementing species injection (e.g. contamination) events.
|
|
3
|
+
"""
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
import warnings
|
|
6
|
+
import math
|
|
7
|
+
import numpy as np
|
|
8
|
+
import epyt
|
|
9
|
+
from epyt.epanet import ToolkitConstants
|
|
10
|
+
|
|
11
|
+
from .system_event import SystemEvent
|
|
12
|
+
from ...serialization import serializable, JsonSerializable, \
|
|
13
|
+
SPECIESINJECTION_EVENT_ID
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@serializable(SPECIESINJECTION_EVENT_ID, ".epytflow_speciesinjection_event")
|
|
17
|
+
class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
18
|
+
"""
|
|
19
|
+
Class implementing a (bulk) species injection event -- e.g. modeling a contamination event.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
species_id : `str`
|
|
24
|
+
ID of the bulk species that is going to be injected.
|
|
25
|
+
node_id : `str`
|
|
26
|
+
ID of the node at which the injection is palced.
|
|
27
|
+
profile : `numpy.ndarray`
|
|
28
|
+
Injection strength profile -- i.e. every entry corresponds to the strength of the injection
|
|
29
|
+
at a point in time. Pattern will repeat if it is shorter than the total injection time.
|
|
30
|
+
source_type : `int`
|
|
31
|
+
Type of the bulk species injection source -- must be one of
|
|
32
|
+
the following EPANET toolkit constants:
|
|
33
|
+
|
|
34
|
+
- EN_CONCEN = 0
|
|
35
|
+
- EN_MASS = 1
|
|
36
|
+
- EN_SETPOINT = 2
|
|
37
|
+
- EN_FLOWPACED = 3
|
|
38
|
+
|
|
39
|
+
Description:
|
|
40
|
+
|
|
41
|
+
- E_CONCEN Sets the concentration of external inflow entering a node
|
|
42
|
+
- EN_MASS Injects a given mass/minute into a node
|
|
43
|
+
- EN_SETPOINT Sets the concentration leaving a node to a given value
|
|
44
|
+
- EN_FLOWPACED Adds a given value to the concentration leaving a node
|
|
45
|
+
"""
|
|
46
|
+
def __init__(self, species_id: str, node_id: str, profile: np.ndarray, source_type: int,
|
|
47
|
+
**kwds):
|
|
48
|
+
if not isinstance(species_id, str):
|
|
49
|
+
raise TypeError("'species_id' must be an instance of 'str' but not of " +
|
|
50
|
+
f"'{type(species_id)}'")
|
|
51
|
+
if not isinstance(node_id, str):
|
|
52
|
+
raise TypeError("'node_id' must be an instance of 'str' but not of " +
|
|
53
|
+
f"'{type(node_id)}'")
|
|
54
|
+
if not isinstance(profile, np.ndarray):
|
|
55
|
+
raise TypeError("'profile' must be an instance of 'numpy.ndarray' but not of " +
|
|
56
|
+
f"'{type(profile)}'")
|
|
57
|
+
if not isinstance(source_type, int):
|
|
58
|
+
raise TypeError("'source_type' must be an instance of 'int' but not of " +
|
|
59
|
+
f"'{type(source_type)}'")
|
|
60
|
+
if not 0 <= source_type <= 3:
|
|
61
|
+
raise ValueError("'source_tye' must be in [0, 3]")
|
|
62
|
+
|
|
63
|
+
self.__species_id = species_id
|
|
64
|
+
self.__node_id = node_id
|
|
65
|
+
self.__profile = profile
|
|
66
|
+
self.__source_type = source_type
|
|
67
|
+
|
|
68
|
+
super().__init__(**kwds)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def species_id(self) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Gets the ID of the bulk species that is going to be injected.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
`str`
|
|
78
|
+
Bulk species ID.
|
|
79
|
+
"""
|
|
80
|
+
return self.__species_id
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def node_id(self) -> str:
|
|
84
|
+
"""
|
|
85
|
+
Gets the ID of the node at which the injection is palced.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
`str`
|
|
90
|
+
Node ID.
|
|
91
|
+
"""
|
|
92
|
+
return self.__node_id
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def profile(self) -> np.ndarray:
|
|
96
|
+
"""
|
|
97
|
+
Gets the injection strength profile.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
`numpy.ndarray`
|
|
102
|
+
Pattern of the injection.
|
|
103
|
+
"""
|
|
104
|
+
return deepcopy(self.__profile)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def source_type(self) -> int:
|
|
108
|
+
"""
|
|
109
|
+
Type of the bulk species injection source -- will be one of
|
|
110
|
+
the following EPANET toolkit constants:
|
|
111
|
+
|
|
112
|
+
- EN_CONCEN = 0
|
|
113
|
+
- EN_MASS = 1
|
|
114
|
+
- EN_SETPOINT = 2
|
|
115
|
+
- EN_FLOWPACED = 3
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
`int`
|
|
120
|
+
Type of the injection source.
|
|
121
|
+
"""
|
|
122
|
+
return self.__source_type
|
|
123
|
+
|
|
124
|
+
def get_attributes(self) -> dict:
|
|
125
|
+
return super().get_attributes() | {"species_id": self.__species_id,
|
|
126
|
+
"node_id": self.__node_id, "profile": self.__profile,
|
|
127
|
+
"source_type": self.__source_type}
|
|
128
|
+
|
|
129
|
+
def __eq__(self, other) -> bool:
|
|
130
|
+
return super().__eq__(other) and self.__species_id == other.species_id and \
|
|
131
|
+
self.__node_id == other.node_id and np.all(self.__profile == other.profile) and \
|
|
132
|
+
self.__source_type == other.source_type
|
|
133
|
+
|
|
134
|
+
def __str__(self) -> str:
|
|
135
|
+
return f"{super().__str__()} species_id: {self.__species_id} " +\
|
|
136
|
+
f"node_id: {self.__node_id} profile: {self.__profile} source_type: {self.__source_type}"
|
|
137
|
+
|
|
138
|
+
def _get_pattern_id(self) -> str:
|
|
139
|
+
return f"{self.__species_id}_{self.__node_id}_{self.start_time}"
|
|
140
|
+
|
|
141
|
+
def init(self, epanet_api: epyt.epanet) -> None:
|
|
142
|
+
super().init(epanet_api)
|
|
143
|
+
|
|
144
|
+
# Check parameters
|
|
145
|
+
if self.__species_id not in self._epanet_api.getMSXSpeciesNameID():
|
|
146
|
+
raise ValueError(f"Unknown species '{self.__species_id}'")
|
|
147
|
+
if self.__node_id not in self._epanet_api.getNodeNameID():
|
|
148
|
+
raise ValueError(f"Unknown node '{self.__node_id}'")
|
|
149
|
+
|
|
150
|
+
# Create final injection strength pattern
|
|
151
|
+
total_sim_duration = self._epanet_api.getTimeSimulationDuration()
|
|
152
|
+
time_step = self._epanet_api.getTimeHydraulicStep()
|
|
153
|
+
|
|
154
|
+
pattern = np.zeros(math.ceil(total_sim_duration / time_step))
|
|
155
|
+
|
|
156
|
+
end_time = self.end_time if self.end_time is not None else total_sim_duration
|
|
157
|
+
injection_pattern_length = math.ceil((end_time - self.start_time) / time_step)
|
|
158
|
+
injection_time_start_idx = int(self.start_time / time_step)
|
|
159
|
+
|
|
160
|
+
injection_pattern = None
|
|
161
|
+
if len(self.__profile) == injection_pattern_length:
|
|
162
|
+
injection_pattern = self.profile
|
|
163
|
+
else:
|
|
164
|
+
injection_pattern = np.tile(self.profile,
|
|
165
|
+
math.ceil(injection_pattern_length / len(self.profile)))
|
|
166
|
+
|
|
167
|
+
pattern[injection_time_start_idx:
|
|
168
|
+
injection_time_start_idx + injection_pattern_length] = injection_pattern
|
|
169
|
+
|
|
170
|
+
# Create injection
|
|
171
|
+
source_type_ = "None"
|
|
172
|
+
if self.__source_type == ToolkitConstants.EN_CONCEN:
|
|
173
|
+
source_type_ = "CONCEN"
|
|
174
|
+
elif self.__source_type == ToolkitConstants.EN_MASS:
|
|
175
|
+
source_type_ = "MASS"
|
|
176
|
+
elif self.__source_type == ToolkitConstants.EN_SETPOINT:
|
|
177
|
+
source_type_ = "SETPOINT"
|
|
178
|
+
elif self.__source_type == ToolkitConstants.EN_FLOWPACED:
|
|
179
|
+
source_type_ = "FLOWPACED"
|
|
180
|
+
|
|
181
|
+
pattern_id = self._get_pattern_id()
|
|
182
|
+
if pattern_id in self._epanet_api.getMSXPatternsNameID():
|
|
183
|
+
raise ValueError("Duplicated injection event")
|
|
184
|
+
|
|
185
|
+
self._epanet_api.addMSXPattern(pattern_id, pattern)
|
|
186
|
+
self._epanet_api.setMSXSources(self.__node_id, self.__species_id, source_type_, 1,
|
|
187
|
+
pattern_id)
|
|
188
|
+
|
|
189
|
+
def cleanup(self) -> None:
|
|
190
|
+
warnings.warn("Can not undo SpeciedInjectionEvent -- " +
|
|
191
|
+
"EPANET-MSX does not support removing patterns")
|
|
192
|
+
|
|
193
|
+
def apply(self, cur_time: int) -> None:
|
|
194
|
+
pass
|
|
@@ -73,6 +73,11 @@ class SystemEvent(Event):
|
|
|
73
73
|
Current time (seconds since the start) in the simulation.
|
|
74
74
|
"""
|
|
75
75
|
|
|
76
|
+
def cleanup(self) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Clean up any changes/modifications made by this event.
|
|
79
|
+
"""
|
|
80
|
+
|
|
76
81
|
@abstractmethod
|
|
77
82
|
def apply(self, cur_time: int) -> None:
|
|
78
83
|
"""
|