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.
@@ -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
  """