epyt-flow 0.15.0b1__py3-none-any.whl → 0.16.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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.15.0b1
1
+ 0.16.0
@@ -125,7 +125,7 @@ def load_data(download_dir: str = None, return_X_y: bool = False,
125
125
  del df_train_1["DATETIME"]
126
126
  X_train_1 = df_train_1.to_numpy()
127
127
 
128
- y_train_2 = df_train_2[" ATT_FLAG"].to_numpy()
128
+ y_train_2 = df_train_2[" ATT_FLAG"].to_numpy(copy=True)
129
129
  idx = np.argwhere(y_train_2 == -999)
130
130
  y_train_2[idx] = 0
131
131
  y_train_2 = y_train_2.astype(np.int8)
@@ -218,18 +218,12 @@ class ScenarioControlEnv(ABC):
218
218
  "when running EPANET-MSX")
219
219
 
220
220
  pump_idx = self._scenario_sim.epanet_api.get_link_idx(pump_id)
221
- pattern_idx = int(self._scenario_sim.epanet_api.getlinkvalue(pump_idx,
222
- EpanetConstants.EN_LINKPATTERN))
223
-
224
- if pattern_idx == 0:
225
- warnings.warn(f"No pattern for pump '{pump_id}' found -- a new pattern is created")
226
- pattern_id = f"pump_speed_{pump_id}"
227
- self._scenario_sim.epanet_api.add_pattern(pattern_id, [speed])
228
- pattern_idx = self._scenario_sim.epanet_api.getpatternindex(pattern_id)
229
- self._scenario_sim.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN,
230
- pattern_idx)
231
-
232
- self._scenario_sim.epanet_api.setpattern(pattern_idx, [speed], 1)
221
+ pattern_idx = self._scenario_sim.epanet_api.getlinkvalue(pump_idx,
222
+ EpanetConstants.EN_LINKPATTERN)
223
+ if pattern_idx != 0:
224
+ warnings.warn(f"Pump setting of pump {pump_id} will be multiplied with existing pump pattern")
225
+
226
+ self._scenario_sim.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_SETTING, speed)
233
227
 
234
228
  def set_valve_status(self, valve_id: str, status: int) -> None:
235
229
  """
@@ -111,6 +111,13 @@ class Serializable(ABC):
111
111
 
112
112
  super().__init__(**kwds)
113
113
 
114
+ def __eq__(self, other) -> bool:
115
+ return self._parent_path == other.parent_path
116
+
117
+ @property
118
+ def parent_path(self) -> str:
119
+ return self._parent_path
120
+
114
121
  @abstractmethod
115
122
  def get_attributes(self) -> dict:
116
123
  """
@@ -480,16 +487,25 @@ def __decode_bsr_array(ext_data: tuple[tuple[int, int],
480
487
  return scipy.sparse.bsr_array((data[0], (data[1][0], data[1][1])), shape=(shape[0], shape[1]))
481
488
 
482
489
 
490
+ def __encode_numpy_array(array: np.ndarray) -> tuple[str, list[int], bytes]:
491
+ return array.dtype.descr[0][1], array.shape, array.tobytes()
492
+
493
+
494
+ def __decode_numpy_array(ext_data: tuple[str, list[int], bytes]) -> np.ndarray:
495
+ dtype_descr, shape, buffer = ext_data
496
+ return np.frombuffer(buffer, dtype=np.dtype(dtype_descr)).reshape(shape)
497
+
498
+
483
499
  ext_handler_pack = {np.ndarray:
484
- lambda arr: umsgpack.Ext(NUMPY_ARRAY_ID, umsgpack.packb(arr.tolist())),
500
+ lambda arr: umsgpack.Ext(NUMPY_ARRAY_ID, umsgpack.packb(__encode_numpy_array(arr))),
485
501
  networkx.Graph:
486
502
  lambda graph:
487
503
  umsgpack.Ext(NETWORKX_GRAPH_ID,
488
- umsgpack.packb(networkx.node_link_data(graph))),
504
+ umsgpack.packb(networkx.edges(graph))),
489
505
  scipy.sparse.bsr_array:
490
506
  lambda arr: umsgpack.Ext(SCIPY_BSRARRAY_ID,
491
507
  umsgpack.packb(__encode_bsr_array(arr)))}
492
- ext_handler_unpack = {NUMPY_ARRAY_ID: lambda ext: np.array(umsgpack.unpackb(ext.data)),
508
+ ext_handler_unpack = {NUMPY_ARRAY_ID: lambda ext: __decode_numpy_array(umsgpack.unpackb(ext.data)),
493
509
  NETWORKX_GRAPH_ID:
494
510
  lambda ext: networkx.node_link_graph(umsgpack.unpackb(ext.data)),
495
511
  SCIPY_BSRARRAY_ID: lambda ext: __decode_bsr_array(umsgpack.unpackb(ext.data))}
@@ -60,18 +60,18 @@ class PumpEvent(ActuatorEvent):
60
60
  ID of the pump that is affected by this event.
61
61
  """
62
62
  def __init__(self, pump_id: str, **kwds):
63
- self.__pump_id = pump_id
63
+ self._pump_id = pump_id
64
64
 
65
65
  super().__init__(**kwds)
66
66
 
67
67
  def init(self, epanet_api: EPyT) -> None:
68
- if self.__pump_id not in epanet_api.get_all_pumps_id():
69
- raise ValueError(f"Invalid pump ID '{self.__pump_id}'")
68
+ if self._pump_id not in epanet_api.get_all_pumps_id():
69
+ raise ValueError(f"Invalid pump ID '{self._pump_id}'")
70
70
 
71
71
  super().init(epanet_api)
72
72
 
73
73
  def get_attributes(self) -> dict:
74
- return super().get_attributes() | {"pump_id": self.__pump_id}
74
+ return super().get_attributes() | {"pump_id": self._pump_id}
75
75
 
76
76
  @property
77
77
  def pump_id(self) -> str:
@@ -83,7 +83,7 @@ class PumpEvent(ActuatorEvent):
83
83
  `str`
84
84
  Pump ID.
85
85
  """
86
- return self.__pump_id
86
+ return self._pump_id
87
87
 
88
88
 
89
89
  @serializable(PUMP_STATE_EVENT_ID, ".epytflow_pump_state_event")
@@ -111,12 +111,12 @@ class PumpStateEvent(PumpEvent, JsonSerializable):
111
111
  raise ValueError(f"Invalid pump state '{pump_state}' -- " +
112
112
  "must be either EN_CLOSED (0) or EN_OPEN (1)")
113
113
 
114
- self.__pump_state = pump_state
114
+ self._pump_state = pump_state
115
115
 
116
116
  super().__init__(**kwds)
117
117
 
118
118
  def get_attributes(self) -> dict:
119
- return super().get_attributes() | {"pump_state": self.__pump_state}
119
+ return super().get_attributes() | {"pump_state": self._pump_state}
120
120
 
121
121
  @property
122
122
  def pump_state(self) -> int:
@@ -134,7 +134,7 @@ class PumpStateEvent(PumpEvent, JsonSerializable):
134
134
  - EN_CLOSED = 0
135
135
  - EN_OPEN = 1
136
136
  """
137
- return self.__pump_state
137
+ return self._pump_state
138
138
 
139
139
  def apply(self, cur_time: int) -> None:
140
140
  pump_link_idx = self._epanet_api.getlinkindex(self.pump_id)
@@ -145,7 +145,7 @@ class PumpStateEvent(PumpEvent, JsonSerializable):
145
145
  "because a pump pattern exists")
146
146
  else:
147
147
  self._epanet_api.setlinkvalue(pump_link_idx, EpanetConstants.EN_STATUS,
148
- self.__pump_state)
148
+ self._pump_state)
149
149
 
150
150
 
151
151
  @serializable(PUMP_SPEED_EVENT_ID, ".epytflow_pump_speed_event")
@@ -165,12 +165,12 @@ class PumpSpeedEvent(PumpEvent, JsonSerializable):
165
165
  if pump_speed <= 0:
166
166
  raise ValueError("Pump speed must be positive")
167
167
 
168
- self.__pump_speed = pump_speed
168
+ self._pump_speed = pump_speed
169
169
 
170
170
  super().__init__(**kwds)
171
171
 
172
172
  def get_attributes(self) -> dict:
173
- return super().get_attributes() | {"pump_speed": self.__pump_speed}
173
+ return super().get_attributes() | {"pump_speed": self._pump_speed}
174
174
 
175
175
  @property
176
176
  def pump_speed(self) -> float:
@@ -182,7 +182,7 @@ class PumpSpeedEvent(PumpEvent, JsonSerializable):
182
182
  `float`
183
183
  New pump speed.
184
184
  """
185
- return self.__pump_speed
185
+ return self._pump_speed
186
186
 
187
187
  def apply(self, cur_time: int) -> None:
188
188
  pump_idx = self._epanet_api.get_link_idx(self.pump_id)
@@ -191,11 +191,11 @@ class PumpSpeedEvent(PumpEvent, JsonSerializable):
191
191
  if pattern_idx == 0:
192
192
  warnings.warn(f"No pattern for pump '{self.pump_id}' found -- a new pattern is created")
193
193
  pattern_id = f"pump_speed_{self.pump_id}"
194
- self._epanet_api.add_pattern(pattern_id, [self.__pump_speed])
194
+ self._epanet_api.add_pattern(pattern_id, [self._pump_speed])
195
195
  pattern_idx = self._epanet_api.getpatternindex(pattern_id)
196
196
  self._epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN, pattern_idx)
197
197
 
198
- self._epanet_api.setpattern(pattern_idx, [self.__pump_speed], 1)
198
+ self._epanet_api.setpattern(pattern_idx, [self._pump_speed], 1)
199
199
 
200
200
 
201
201
  @serializable(VALVE_STATE_EVENT_ID, ".epytflow_valve_state_event")
@@ -224,20 +224,20 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
224
224
  raise ValueError(f"Invalid valve state '{valve_state}' -- " +
225
225
  "must be either EN_CLOSED (0) or EN_OPEN (1)")
226
226
 
227
- self.__valve_id = valve_id
228
- self.__valve_state = valve_state
227
+ self._valve_id = valve_id
228
+ self._valve_state = valve_state
229
229
 
230
230
  super().__init__(**kwds)
231
231
 
232
232
  def init(self, epanet_api: EPyT) -> None:
233
- if self.__valve_id not in epanet_api.get_all_valves_id():
234
- raise ValueError(f"Invalid valve ID '{self.__valve_id}'")
233
+ if self._valve_id not in epanet_api.get_all_valves_id():
234
+ raise ValueError(f"Invalid valve ID '{self._valve_id}'")
235
235
 
236
236
  super().init(epanet_api)
237
237
 
238
238
  def get_attributes(self) -> dict:
239
- return super().get_attributes() | {"valve_id": self.__valve_id,
240
- "valve_state": self.__valve_state}
239
+ return super().get_attributes() | {"valve_id": self._valve_id,
240
+ "valve_state": self._valve_state}
241
241
 
242
242
  @property
243
243
  def valve_id(self) -> str:
@@ -249,7 +249,7 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
249
249
  `str`
250
250
  Valve ID.
251
251
  """
252
- return self.__valve_id
252
+ return self._valve_id
253
253
 
254
254
  @property
255
255
  def valve_state(self) -> int:
@@ -264,8 +264,8 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
264
264
  One of the following constants defined in
265
265
  :class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
266
266
  """
267
- return self.__valve_state
267
+ return self._valve_state
268
268
 
269
269
  def apply(self, cur_time: int) -> None:
270
- valve_link_idx = self._epanet_api.get_link_idx(self.__valve_id)
271
- self._epanet_api.setlinkvalue(valve_link_idx, EpanetConstants.EN_STATUS, self.__valve_state)
270
+ valve_link_idx = self._epanet_api.get_link_idx(self._valve_id)
271
+ self._epanet_api.setlinkvalue(valve_link_idx, EpanetConstants.EN_STATUS, self._valve_state)
@@ -85,15 +85,15 @@ class Leakage(SystemEvent, JsonSerializable):
85
85
  raise TypeError("'node_id' must be an instance of 'str' " +
86
86
  f"but not of '{type(node_id)}'")
87
87
 
88
- self.__link_id = link_id
89
- self.__node_id = node_id
90
- self.__diameter = diameter
91
- self.__area = area
92
- self.__profile = profile
88
+ self._link_id = link_id
89
+ self._node_id = node_id
90
+ self._diameter = diameter
91
+ self._area = area
92
+ self._profile = profile
93
93
 
94
- self.__leaky_node_idx = None
95
- self.__leak_emitter_coef = None
96
- self.__time_pattern_idx = 0
94
+ self._leaky_node_idx = None
95
+ self._leak_emitter_coef = None
96
+ self._time_pattern_idx = 0
97
97
 
98
98
  super().__init__(**kwds)
99
99
 
@@ -107,7 +107,7 @@ class Leakage(SystemEvent, JsonSerializable):
107
107
  `str`
108
108
  ID of the link at which the leak is placed.
109
109
  """
110
- return self.__link_id
110
+ return self._link_id
111
111
 
112
112
  @property
113
113
  def node_id(self) -> str:
@@ -119,7 +119,7 @@ class Leakage(SystemEvent, JsonSerializable):
119
119
  `str`
120
120
  ID of the node at which the leak is placed.
121
121
  """
122
- return self.__node_id
122
+ return self._node_id
123
123
 
124
124
  @property
125
125
  def diameter(self) -> float:
@@ -132,7 +132,7 @@ class Leakage(SystemEvent, JsonSerializable):
132
132
  `float`
133
133
  Diameter (*foot* or *meter*) of the leak.
134
134
  """
135
- return self.__diameter
135
+ return self._diameter
136
136
 
137
137
  @property
138
138
  def area(self) -> float:
@@ -145,7 +145,7 @@ class Leakage(SystemEvent, JsonSerializable):
145
145
  `float`
146
146
  Area of the leak.
147
147
  """
148
- return self.__area if self.__area is not None else self.compute_leak_area(self.__diameter)
148
+ return self._area if self._area is not None else self.compute_leak_area(self._diameter)
149
149
 
150
150
  @property
151
151
  def profile(self) -> np.ndarray:
@@ -157,7 +157,7 @@ class Leakage(SystemEvent, JsonSerializable):
157
157
  `numpy.ndarray`
158
158
  Pattern of the leak.
159
159
  """
160
- return deepcopy(self.__profile)
160
+ return deepcopy(self._profile)
161
161
 
162
162
  @profile.setter
163
163
  def profile(self, pattern: np.ndarray):
@@ -168,25 +168,25 @@ class Leakage(SystemEvent, JsonSerializable):
168
168
  raise ValueError("'profile' must be a one-dimensional array " +
169
169
  f"but not of shape '{pattern.shape}'")
170
170
 
171
- self.__profile = pattern
171
+ self._profile = pattern
172
172
 
173
173
  def get_attributes(self) -> dict:
174
- return super().get_attributes() | {"link_id": self.__link_id, "diameter": self.__diameter,
175
- "area": self.__area, "profile": self.__profile,
176
- "node_id": self.__node_id
177
- if self.__link_id is None else None}
174
+ return super().get_attributes() | {"link_id": self._link_id, "diameter": self._diameter,
175
+ "area": self._area, "profile": self._profile,
176
+ "node_id": self._node_id
177
+ if self._link_id is None else None}
178
178
 
179
179
  def __eq__(self, other) -> bool:
180
180
  if not isinstance(other, Leakage):
181
181
  raise TypeError(f"Can not compare 'Leakage' instance with '{type(other)}' instance")
182
182
 
183
- return super().__eq__(other) and self.__link_id == other.link_id \
184
- and self.__diameter == other.diameter and np.all(self.__profile == other.profile) \
185
- and self.__node_id == other.node_id and self.area == other.area
183
+ return super().__eq__(other) and self._link_id == other.link_id \
184
+ and self._diameter == other.diameter and np.all(self._profile == other.profile) \
185
+ and self._node_id == other.node_id and self.area == other.area
186
186
 
187
187
  def __str__(self) -> str:
188
- return f"{super().__str__()} link_id: {self.__link_id} diameter: {self.__diameter} " +\
189
- f"area: {self.__area} profile: {self.__profile} node_id: {self.__node_id}"
188
+ return f"{super().__str__()} link_id: {self._link_id} diameter: {self._diameter} " +\
189
+ f"area: {self._area} profile: {self._profile} node_id: {self._node_id}"
190
190
 
191
191
  def compute_leak_area(self, diameter: float) -> float:
192
192
  """
@@ -242,18 +242,18 @@ class Leakage(SystemEvent, JsonSerializable):
242
242
  return discharge_coef * area * np.sqrt(2. * g)
243
243
 
244
244
  def _get_new_link_id(self) -> str:
245
- return f"leak_pipe_{self.__link_id}"
245
+ return f"leak_pipe_{self._link_id}"
246
246
 
247
247
  def _get_new_node_id(self) -> str:
248
- return f"leak_node_{self.__link_id}"
248
+ return f"leak_node_{self._link_id}"
249
249
 
250
250
  def init(self, epanet_api: EPyT) -> None:
251
251
  super().init(epanet_api)
252
252
 
253
253
  # Split pipe if leak is placed at a link/pipe
254
- if self.__link_id is not None:
255
- if self.__link_id not in self._epanet_api.get_all_links_id():
256
- raise ValueError(f"Unknown link/pipe '{self.__link_id}'")
254
+ if self._link_id is not None:
255
+ if self._link_id not in self._epanet_api.get_all_links_id():
256
+ raise ValueError(f"Unknown link/pipe '{self._link_id}'")
257
257
 
258
258
  new_link_id = self._get_new_link_id()
259
259
  new_node_id = self._get_new_node_id()
@@ -263,22 +263,22 @@ class Leakage(SystemEvent, JsonSerializable):
263
263
  raise ValueError(f"There is already a leak at pipe {self.link_id}")
264
264
 
265
265
  self._epanet_api.split_pipe(self.link_id, new_link_id, new_node_id)
266
- self.__leaky_node_idx = self._epanet_api.get_node_idx(new_node_id)
266
+ self._leaky_node_idx = self._epanet_api.get_node_idx(new_node_id)
267
267
  else:
268
- if self.__node_id not in self._epanet_api.get_all_nodes_id():
269
- raise ValueError(f"Unknown node '{self.__node_id}'")
268
+ if self._node_id not in self._epanet_api.get_all_nodes_id():
269
+ raise ValueError(f"Unknown node '{self._node_id}'")
270
270
 
271
- self.__leaky_node_idx = self._epanet_api.get_node_idx(self.__node_id)
271
+ self._leaky_node_idx = self._epanet_api.get_node_idx(self._node_id)
272
272
 
273
- self._epanet_api.setnodevalue(self.__leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
273
+ self._epanet_api.setnodevalue(self._leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
274
274
 
275
275
  # Compute leak emitter coefficient
276
- self.__leak_emitter_coef = self.compute_leak_emitter_coefficient(
276
+ self._leak_emitter_coef = self.compute_leak_emitter_coefficient(
277
277
  self.compute_leak_area(self.area))
278
278
 
279
279
  def cleanup(self) -> None:
280
- if self.__link_id is not None:
281
- pipe_idx = self._epanet_api.get_link_idx(self.__link_id)
280
+ if self._link_id is not None:
281
+ pipe_idx = self._epanet_api.get_link_idx(self._link_id)
282
282
  link_diameter = self._epanet_api.get_link_diameter(pipe_idx)
283
283
  link_length = self._epanet_api.get_link_length(pipe_idx)
284
284
  link_roughness_coeff = self._epanet_api.get_link_roughness(pipe_idx)
@@ -293,15 +293,15 @@ class Leakage(SystemEvent, JsonSerializable):
293
293
 
294
294
  self._epanet_api.deletelink(self._epanet_api.get_link_idx(self._get_new_link_id()),
295
295
  EpanetConstants.EN_UNCONDITIONAL)
296
- self._epanet_api.deletelink(self._epanet_api.get_link_idx(self.__link_id),
296
+ self._epanet_api.deletelink(self._epanet_api.get_link_idx(self._link_id),
297
297
  EpanetConstants.EN_UNCONDITIONAL)
298
298
  self._epanet_api.deletenode(self._epanet_api.get_node_idx(self._get_new_node_id()),
299
299
  EpanetConstants.EN_UNCONDITIONAL)
300
300
 
301
- self._epanet_api.addlink(self.__link_id, EpanetConstants.EN_PIPE,
301
+ self._epanet_api.addlink(self._link_id, EpanetConstants.EN_PIPE,
302
302
  self._epanet_api.get_node_id(node_a_idx),
303
303
  self._epanet_api.get_node_id(node_b_idx))
304
- link_idx = self._epanet_api.get_link_idx(self.__link_id)
304
+ link_idx = self._epanet_api.get_link_idx(self._link_id)
305
305
  self._epanet_api.setlinknodes(link_idx, node_a_idx, node_b_idx)
306
306
  self._epanet_api.setlinktype(link_idx, EpanetConstants.EN_PIPE,
307
307
  EpanetConstants.EN_UNCONDITIONAL)
@@ -317,16 +317,16 @@ class Leakage(SystemEvent, JsonSerializable):
317
317
  link_wall_reaction_coeff)
318
318
 
319
319
  def reset(self) -> None:
320
- self.__time_pattern_idx = 0
320
+ self._time_pattern_idx = 0
321
321
 
322
322
  def exit(self, cur_time) -> None:
323
- self._epanet_api.setnodevalue(self.__leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
323
+ self._epanet_api.setnodevalue(self._leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
324
324
 
325
325
  def apply(self, cur_time: int) -> None:
326
- self._epanet_api.setnodevalue(self.__leaky_node_idx, EpanetConstants.EN_EMITTER,
327
- self.__leak_emitter_coef *
328
- self.__profile[self.__time_pattern_idx])
329
- self.__time_pattern_idx += 1
326
+ self._epanet_api.setnodevalue(self._leaky_node_idx, EpanetConstants.EN_EMITTER,
327
+ self._leak_emitter_coef *
328
+ self._profile[self._time_pattern_idx])
329
+ self._time_pattern_idx += 1
330
330
 
331
331
 
332
332
  @serializable(ABRUPT_LEAKAGE_ID, ".epytflow_leakage_abrupt")
@@ -61,10 +61,10 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
61
61
  if not 0 <= source_type <= 3:
62
62
  raise ValueError("'source_tye' must be in [0, 3]")
63
63
 
64
- self.__species_id = species_id
65
- self.__node_id = node_id
64
+ self._species_id = species_id
65
+ self._node_id = node_id
66
66
  self._profile = profile
67
- self.__source_type = source_type
67
+ self._source_type = source_type
68
68
 
69
69
  super().__init__(**kwds)
70
70
 
@@ -78,7 +78,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
78
78
  `str`
79
79
  Bulk species ID.
80
80
  """
81
- return self.__species_id
81
+ return self._species_id
82
82
 
83
83
  @property
84
84
  def node_id(self) -> str:
@@ -90,7 +90,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
90
90
  `str`
91
91
  Node ID.
92
92
  """
93
- return self.__node_id
93
+ return self._node_id
94
94
 
95
95
  @property
96
96
  def profile(self) -> np.ndarray:
@@ -120,33 +120,33 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
120
120
  `int`
121
121
  Type of the injection source.
122
122
  """
123
- return self.__source_type
123
+ return self._source_type
124
124
 
125
125
  def get_attributes(self) -> dict:
126
- return super().get_attributes() | {"species_id": self.__species_id,
127
- "node_id": self.__node_id, "profile": self._profile,
128
- "source_type": self.__source_type}
126
+ return super().get_attributes() | {"species_id": self._species_id,
127
+ "node_id": self._node_id, "profile": self._profile,
128
+ "source_type": self._source_type}
129
129
 
130
130
  def __eq__(self, other) -> bool:
131
- return super().__eq__(other) and self.__species_id == other.species_id and \
132
- self.__node_id == other.node_id and np.all(self._profile == other.profile) and \
133
- self.__source_type == other.source_type
131
+ return super().__eq__(other) and self._species_id == other.species_id and \
132
+ self._node_id == other.node_id and np.all(self._profile == other.profile) and \
133
+ self._source_type == other.source_type
134
134
 
135
135
  def __str__(self) -> str:
136
- return f"{super().__str__()} species_id: {self.__species_id} " +\
137
- f"node_id: {self.__node_id} profile: {self._profile} source_type: {self.__source_type}"
136
+ return f"{super().__str__()} species_id: {self._species_id} " +\
137
+ f"node_id: {self._node_id} profile: {self._profile} source_type: {self._source_type}"
138
138
 
139
139
  def _get_pattern_id(self) -> str:
140
- return f"{self.__species_id}_{self.__node_id}"
140
+ return f"{self._species_id}_{self._node_id}"
141
141
 
142
142
  def init(self, epanet_api: EPyT) -> None:
143
143
  super().init(epanet_api)
144
144
 
145
145
  # Check parameters
146
- if self.__species_id not in self._epanet_api.get_all_msx_species_id():
147
- raise ValueError(f"Unknown species '{self.__species_id}'")
148
- if self.__node_id not in self._epanet_api.get_all_nodes_id():
149
- raise ValueError(f"Unknown node '{self.__node_id}'")
146
+ if self._species_id not in self._epanet_api.get_all_msx_species_id():
147
+ raise ValueError(f"Unknown species '{self._species_id}'")
148
+ if self._node_id not in self._epanet_api.get_all_nodes_id():
149
+ raise ValueError(f"Unknown node '{self._node_id}'")
150
150
 
151
151
  # Create final injection strength pattern
152
152
  total_sim_duration = self._epanet_api.get_simulation_duration()
@@ -173,10 +173,10 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
173
173
  if pattern_id in [self._epanet_api.MSXgetID(EpanetConstants.MSX_PATTERN, pattern_idx + 1)
174
174
  for pattern_idx in
175
175
  range(self._epanet_api.MSXgetcount(EpanetConstants.MSX_PATTERN))]:
176
- node_idx = self._epanet_api.get_node_idx(self.__node_id)
177
- species_idx = self._epanet_api.get_msx_species_idx(self.__species_id)
176
+ node_idx = self._epanet_api.get_node_idx(self._node_id)
177
+ species_idx = self._epanet_api.get_msx_species_idx(self._species_id)
178
178
  cur_source_type = self._epanet_api.MSXgetsource(node_idx, species_idx)
179
- if cur_source_type[0] != self.__source_type:
179
+ if cur_source_type[0] != self._source_type:
180
180
  raise ValueError("Source type does not match existing source type")
181
181
 
182
182
  # Add new injection amount to existing injection --
@@ -187,7 +187,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
187
187
  self._epanet_api.MSXsetpattern(pattern_idx, cur_pattern.tolist(), len(cur_pattern))
188
188
  else:
189
189
  self._epanet_api.add_msx_pattern(pattern_id, pattern.tolist())
190
- self._epanet_api.set_msx_source(self.__node_id, self.__species_id, self.__source_type, 1,
190
+ self._epanet_api.set_msx_source(self._node_id, self._species_id, self._source_type, 1,
191
191
  pattern_id)
192
192
 
193
193
  def cleanup(self) -> None: