epyt-flow 0.15.0__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/topology.py CHANGED
@@ -4,12 +4,13 @@ Module provides a class for representing the topology of WDN.
4
4
  from copy import deepcopy
5
5
  import warnings
6
6
  from typing import Any
7
+ import math
7
8
  import numpy as np
8
9
  import networkx as nx
9
10
  from scipy.sparse import bsr_array
10
11
  from geopandas import GeoDataFrame
11
12
  from shapely.geometry import Point, LineString
12
- from epanet_plus import EpanetConstants
13
+ from epanet_plus import EpanetConstants, EPyT
13
14
 
14
15
  from .serialization import serializable, JsonSerializable, NETWORK_TOPOLOGY_ID
15
16
 
@@ -67,6 +68,10 @@ class NetworkTopology(nx.Graph, JsonSerializable):
67
68
  valves : `dict`
68
69
  List of all valves -- i.e. valve ID, and information such as
69
70
  valve type and connecting nodes.
71
+ curves : `dict[str, tuple[int, list[tuple[float, float]]]]`
72
+ All curves -- i.e. curve ID, and list of points.
73
+ patterns : `dict[str, list[float]]`
74
+ All time patterns -- i.e., pattern ID and list of multipliers.
70
75
  units : `int`
71
76
  Measurement units category -- i.e. US Customary or SI Metric.
72
77
 
@@ -79,16 +84,25 @@ class NetworkTopology(nx.Graph, JsonSerializable):
79
84
  links: list[tuple[str, tuple[str, str], dict]],
80
85
  pumps: dict,
81
86
  valves: dict,
87
+ curves: dict[str, tuple[int, list[tuple[float, float]]]],
88
+ patterns: dict[str, list[float]],
82
89
  units: int,
83
90
  **kwds):
84
- super().__init__(name=f_inp, **kwds)
91
+ nx.Graph.__init__(self, name=f_inp, **kwds)
92
+ JsonSerializable.__init__(self)
85
93
 
86
94
  self.__nodes = nodes
87
95
  self.__links = links
88
96
  self.__pumps = pumps
89
97
  self.__valves = valves
98
+ self.__curves = curves
99
+ self.__patterns = patterns
90
100
  self.__units = units
91
101
 
102
+ for key in self.__curves.keys(): # Fix value types -- tuple gets converted to list when deserializing it
103
+ self.__curves[key] = (self.__curves[key][0],
104
+ [tuple(value) for value in self.__curves[key][1]])
105
+
92
106
  for node_id, node_info in nodes:
93
107
  node_elevation = node_info["elevation"]
94
108
  node_type = node_info["type"]
@@ -102,6 +116,141 @@ class NetworkTopology(nx.Graph, JsonSerializable):
102
116
  info={"id": link_id, "type": link_type, "nodes": link,
103
117
  "diameter": link_diameter, "length": link_length})
104
118
 
119
+ def to_inp_file(self, inp_file_out: str) -> None:
120
+ """
121
+ Creates an .inp file with the network layout and parameters as specified in
122
+ this instance.
123
+ Note that no control rules are set!
124
+
125
+ Parameters
126
+ ----------
127
+ inp_file_out : `str`
128
+ Path to the .inp file.
129
+ """
130
+ with EPyT(inp_file_in=inp_file_out, use_project=False) as epanet_api:
131
+ if self.units == UNITS_SIMETRIC:
132
+ epanet_api.setflowunits(EpanetConstants.EN_CMH)
133
+ else:
134
+ epanet_api.setflowunits(EpanetConstants.EN_GPM)
135
+
136
+ for curve_id, (curve_type, curve_data) in self.__curves.items():
137
+ epanet_api.addcurve(curve_id)
138
+ curve_idx = epanet_api.getcurveindex(curve_id)
139
+ epanet_api.setcurvetype(curve_idx, curve_type)
140
+ for i, (x, y) in enumerate(curve_data):
141
+ epanet_api.setcurvevalue(curve_idx, i+1, x, y)
142
+
143
+ for pattern_id, values in self.__patterns.items():
144
+ epanet_api.add_pattern(pattern_id, values)
145
+
146
+ for junc_id in self.get_all_junctions():
147
+ epanet_api.addnode(junc_id, EpanetConstants.EN_JUNCTION)
148
+
149
+ node_idx = epanet_api.get_node_idx(junc_id)
150
+ junc_info = self.get_node_info(junc_id)
151
+ epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION,
152
+ junc_info["elevation"])
153
+ epanet_api.setbasedemand(node_idx, 1, junc_info["base_demand"])
154
+ epanet_api.setcoord(node_idx, junc_info["coord"][0], junc_info["coord"][1])
155
+ epanet_api.setcomment(EpanetConstants.EN_NODE, node_idx, junc_info["comment"])
156
+ if "demand_patterns_id" in junc_info:
157
+ for i, demand_pattern_id in enumerate(junc_info["demand_patterns_id"]):
158
+ epanet_api.setdemandpattern(node_idx, i+1,
159
+ epanet_api.getpatternindex(demand_pattern_id))
160
+
161
+ for reservoir_id in self.get_all_reservoirs():
162
+ epanet_api.addnode(reservoir_id, EpanetConstants.EN_RESERVOIR)
163
+
164
+ node_idx = epanet_api.get_node_idx(reservoir_id)
165
+ reservoir_info = self.get_node_info(reservoir_id)
166
+ epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION,
167
+ reservoir_info["elevation"])
168
+ epanet_api.setcoord(node_idx, reservoir_info["coord"][0],
169
+ reservoir_info["coord"][1])
170
+ epanet_api.setcomment(EpanetConstants.EN_NODE, node_idx,
171
+ reservoir_info["comment"])
172
+
173
+ for tank_id in self.get_all_tanks():
174
+ epanet_api.addnode(tank_id, EpanetConstants.EN_TANK)
175
+
176
+ node_idx = epanet_api.get_node_idx(tank_id)
177
+ tank_info = self.get_node_info(tank_id)
178
+ if tank_info["cylindric"] is False:
179
+ raise NotImplementedError("Non-cylindric tanks are not supported!")
180
+ else:
181
+ epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION,
182
+ tank_info["elevation"])
183
+ epanet_api.setcoord(node_idx, tank_info["coord"][0], tank_info["coord"][1])
184
+ epanet_api.setcomment(EpanetConstants.EN_NODE, node_idx, tank_info["comment"])
185
+ epanet_api.setnodevalue(node_idx, EpanetConstants.EN_TANKDIAM,
186
+ tank_info["diameter"])
187
+ epanet_api.setnodevalue(node_idx, EpanetConstants.EN_MIXFRACTION,
188
+ tank_info["mixing_fraction"])
189
+ epanet_api.setnodevalue(node_idx, EpanetConstants.EN_MIXMODEL,
190
+ tank_info["mixing_model"])
191
+ epanet_api.setnodevalue(node_idx, EpanetConstants.EN_CANOVERFLOW,
192
+ float(tank_info["can_overflow"]))
193
+
194
+ tank_info["min_level"] = tank_info["min_vol"] / \
195
+ (math.pi * (0.5 * tank_info["diameter"])**2)
196
+ tank_info["init_level"] = tank_info["init_vol"] / \
197
+ (math.pi * (0.5 * tank_info["diameter"])**2)
198
+
199
+ epanet_api.settankdata(node_idx, tank_info["elevation"],
200
+ tank_info["init_level"], tank_info["min_level"],
201
+ tank_info["max_level"], tank_info["diameter"],
202
+ tank_info["min_vol"], tank_info["vol_curve_id"])
203
+
204
+ for pipe_id, (node_a, node_b) in self.get_all_pipes():
205
+ epanet_api.addlink(pipe_id, EpanetConstants.EN_PIPE, node_a, node_b)
206
+
207
+ pipe_idx = epanet_api.get_link_idx(pipe_id)
208
+ pipe_info = self.get_link_info(pipe_id)
209
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_LENGTH, pipe_info["length"])
210
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_DIAMETER,
211
+ pipe_info["diameter"])
212
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_ROUGHNESS,
213
+ pipe_info["roughness_coeff"])
214
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_KBULK, pipe_info["bulk_coeff"])
215
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_KWALL, pipe_info["wall_coeff"])
216
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_MINORLOSS,
217
+ pipe_info["loss_coeff"])
218
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_INITSETTING,
219
+ pipe_info["init_setting"])
220
+ epanet_api.setlinkvalue(pipe_idx, EpanetConstants.EN_INITSTATUS,
221
+ pipe_info["init_status"])
222
+
223
+ for valve_id in self.get_all_valves():
224
+ valve_info = self.get_valve_info(valve_id)
225
+ node_a, node_b = valve_info["end_points"]
226
+ epanet_api.addlink(valve_id, valve_info["type"], node_a, node_b)
227
+ link_idx = epanet_api.get_link_idx(valve_id)
228
+ epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER,
229
+ valve_info["diameter"])
230
+ epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSETTING,
231
+ valve_info["initial_setting"])
232
+ if valve_info["type"] not in [EpanetConstants.EN_GPV, EpanetConstants.EN_PRV,
233
+ EpanetConstants.EN_CVPIPE]:
234
+ epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSTATUS,
235
+ valve_info["initial_status"])
236
+
237
+ for pump_id in self.get_all_pumps():
238
+ pump_info = self.get_pump_info(pump_id)
239
+ node_a, node_b = pump_info["end_points"]
240
+ epanet_api.addlink(pump_id, pump_info["type"], node_a, node_b)
241
+
242
+ link_idx = link_idx = epanet_api.get_link_idx(pump_id)
243
+ epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSETTING,
244
+ pump_info["init_setting"])
245
+ epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSTATUS,
246
+ pump_info["init_status"])
247
+
248
+ if pump_info["curve_id"] is not None:
249
+ curve_idx = epanet_api.getcurveindex(pump_info["curve_id"])
250
+ epanet_api.setheadcurveindex(link_idx, curve_idx)
251
+
252
+ epanet_api.saveinpfile(inp_file_out)
253
+
105
254
  def convert_units(self, units: int) -> Any:
106
255
  """
107
256
  Converts this instance to a :class:`~epyt_flow.topology.NetworkTopology` instance
@@ -138,6 +287,7 @@ class NetworkTopology(nx.Graph, JsonSerializable):
138
287
  # Get all data and convert units
139
288
  inch_to_millimeter = 25.4
140
289
  feet_to_meter = 0.3048
290
+ cubicmeter_to_cubicfeet = 35.3146667215
141
291
 
142
292
  nodes = []
143
293
  for node_id in self.get_all_nodes():
@@ -146,10 +296,23 @@ class NetworkTopology(nx.Graph, JsonSerializable):
146
296
  conv_factor = 1. / feet_to_meter
147
297
  else:
148
298
  conv_factor = feet_to_meter
299
+
149
300
  node_info["elevation"] *= conv_factor
150
301
  if "diameter" in node_info:
151
302
  node_info["diameter"] *= conv_factor
152
303
 
304
+ if "max_level" in node_info:
305
+ node_info["max_level"] *= conv_factor
306
+ if "min_level" in node_info:
307
+ node_info["min_level"] *= conv_factor
308
+ if "init_level" in node_info:
309
+ node_info["init_level"] *= conv_factor
310
+ if "min_vol" in node_info:
311
+ if units == UNITS_USCUSTOM:
312
+ node_info["min_vol"] *= cubicmeter_to_cubicfeet
313
+ else:
314
+ node_info["min_vol"] *= 1. / cubicmeter_to_cubicfeet
315
+
153
316
  nodes.append((node_id, node_info))
154
317
 
155
318
  links = []
@@ -171,7 +334,8 @@ class NetworkTopology(nx.Graph, JsonSerializable):
171
334
  links.append((link_id, link_nodes, link_info))
172
335
 
173
336
  return NetworkTopology(f_inp=self.name, nodes=nodes, links=links, pumps=self.pumps,
174
- valves=self.valves, units=units)
337
+ valves=self.valves, units=units, curves=self.__curves,
338
+ patterns=self.__patterns)
175
339
 
176
340
  def get_all_nodes(self) -> list[str]:
177
341
  """
@@ -455,6 +619,30 @@ class NetworkTopology(nx.Graph, JsonSerializable):
455
619
  else:
456
620
  raise ValueError(f"Unknown valve: '{valve_id}'")
457
621
 
622
+ @property
623
+ def curves(self) -> dict[str, tuple[int, list[tuple[float, float]]]]:
624
+ """
625
+ Gets all curves -- i.e., ID and list of points.
626
+
627
+ Returns
628
+ -------
629
+ `dict[str, tuple[int, list[tuple[float, float]]]]`
630
+ All curves.
631
+ """
632
+ return deepcopy(self.__curves)
633
+
634
+ @property
635
+ def patterns(self) -> dict[str, list[float]]:
636
+ """
637
+ Returns all time patterns -- i.e., ID and list of multipliers.
638
+
639
+ Returns
640
+ -------
641
+ `dict[str, list[float]]`
642
+ All time patterns.
643
+ """
644
+ return deepcopy(self.__patterns)
645
+
458
646
  @property
459
647
  def pumps(self) -> dict:
460
648
  """
@@ -504,8 +692,7 @@ class NetworkTopology(nx.Graph, JsonSerializable):
504
692
  adj_matrix = self.get_adj_matrix()
505
693
  other_adj_matrix = other.get_adj_matrix()
506
694
 
507
- return super().__eq__(other) and \
508
- self.name == other.name \
695
+ return self.name == other.name \
509
696
  and not np.any(adj_matrix.data != other_adj_matrix.data) \
510
697
  and not np.any(adj_matrix.indices != other_adj_matrix.indices) \
511
698
  and not np.any(adj_matrix.indptr != other_adj_matrix.indptr) \
@@ -514,7 +701,9 @@ class NetworkTopology(nx.Graph, JsonSerializable):
514
701
  for link_a, link_b in zip(self.get_all_links(), other.get_all_links())) \
515
702
  and self.__units == other.units \
516
703
  and self.get_all_pumps() == other.get_all_pumps() \
517
- and self.get_all_valves() == other.get_all_valves()
704
+ and self.get_all_valves() == other.get_all_valves() \
705
+ and self.__curves == other.curves \
706
+ and self.__patterns == other.patterns
518
707
 
519
708
  def __str__(self) -> str:
520
709
  return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links} " +\
@@ -527,6 +716,8 @@ class NetworkTopology(nx.Graph, JsonSerializable):
527
716
  "links": self.__links,
528
717
  "pumps": self.__pumps,
529
718
  "valves": self.__valves,
719
+ "curves": self.__curves,
720
+ "patterns": self.__patterns,
530
721
  "units": self.__units}
531
722
 
532
723
  def to_gis(self, coord_reference_system: str = None, pumps_as_points: bool = False,
@@ -328,6 +328,8 @@ class ModelUncertainty(JsonSerializable):
328
328
  self._cache_msx_tanks_parameters = None
329
329
  self._cache_msx_patterns = None
330
330
 
331
+ self.__np_rand_gen = np.random.default_rng(seed=self.__seed)
332
+
331
333
  super().__init__(**kwds)
332
334
 
333
335
  @property
@@ -703,13 +705,12 @@ class ModelUncertainty(JsonSerializable):
703
705
  epanet_api : `epanet_plus.EPyT <https://epanet-plus.readthedocs.io/en/stable/api.html#epanet_plus.epanet_toolkit.EPyT>`_
704
706
  Interface to EPANET and EPANET-MSX.
705
707
  """
706
- np_rand_gen = np.random.default_rng(seed=self.__seed)
707
708
 
708
709
  all_links_idx = epanet_api.get_all_links_idx()
709
710
  all_nodes_idx = epanet_api.get_all_nodes_idx()
710
711
 
711
712
  if self._global_pipe_length is not None:
712
- self._global_pipe_length.set_random_generator(np_rand_gen)
713
+ self._global_pipe_length.set_random_generator(self.__np_rand_gen)
713
714
 
714
715
  link_length = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH)
715
716
  for link_idx in all_links_idx])
@@ -720,7 +721,7 @@ class ModelUncertainty(JsonSerializable):
720
721
  epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_value)
721
722
 
722
723
  if self._local_pipe_length is not None:
723
- self._local_pipe_length.set_random_generator(np_rand_gen)
724
+ self._local_pipe_length.set_random_generator(self.__np_rand_gen)
724
725
  self._cache_links_length = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH)
725
726
  for link_idx in all_links_idx])
726
727
 
@@ -732,7 +733,7 @@ class ModelUncertainty(JsonSerializable):
732
733
  epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_length)
733
734
 
734
735
  if self._global_pipe_diameter is not None:
735
- self._global_pipe_diameter.set_random_generator(np_rand_gen)
736
+ self._global_pipe_diameter.set_random_generator(self.__np_rand_gen)
736
737
 
737
738
  link_diameters = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_DIAMETER)
738
739
  for link_idx in all_links_idx])
@@ -743,7 +744,7 @@ class ModelUncertainty(JsonSerializable):
743
744
  epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_value)
744
745
 
745
746
  if self._local_pipe_diameter is not None:
746
- self._local_pipe_diameter.set_random_generator(np_rand_gen)
747
+ self._local_pipe_diameter.set_random_generator(self.__np_rand_gen)
747
748
  self._cache_links_diameter = np.array([epanet_api.getlinkvalue(link_idx,
748
749
  EpanetConstants.EN_DIAMETER)
749
750
  for link_idx in all_links_idx])
@@ -756,7 +757,7 @@ class ModelUncertainty(JsonSerializable):
756
757
  epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_diameter)
757
758
 
758
759
  if self._global_pipe_roughness is not None:
759
- self._global_pipe_roughness.set_random_generator(np_rand_gen)
760
+ self._global_pipe_roughness.set_random_generator(self.__np_rand_gen)
760
761
 
761
762
  coeffs = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS)
762
763
  for link_idx in all_links_idx])
@@ -767,7 +768,7 @@ class ModelUncertainty(JsonSerializable):
767
768
  epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS, link_value)
768
769
 
769
770
  if self._local_pipe_roughness is not None:
770
- self._local_pipe_roughness.set_random_generator(np_rand_gen)
771
+ self._local_pipe_roughness.set_random_generator(self.__np_rand_gen)
771
772
  self._cache_links_roughness_coeff = \
772
773
  np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS)
773
774
  for link_idx in all_links_idx])
@@ -782,7 +783,7 @@ class ModelUncertainty(JsonSerializable):
782
783
  link_roughness_coeff)
783
784
 
784
785
  if self._global_base_demand is not None:
785
- self._global_base_demand.set_random_generator(np_rand_gen)
786
+ self._global_base_demand.set_random_generator(self.__np_rand_gen)
786
787
 
787
788
  self._cache_nodes_base_demand = {}
788
789
  for node_idx in all_nodes_idx:
@@ -796,7 +797,7 @@ class ModelUncertainty(JsonSerializable):
796
797
  epanet_api.setbasedemand(node_idx, demand_idx + 1, base_demand)
797
798
 
798
799
  if self._local_base_demand is not None:
799
- self._local_base_demand.set_random_generator(np_rand_gen)
800
+ self._local_base_demand.set_random_generator(self.__np_rand_gen)
800
801
 
801
802
  self._cache_nodes_base_demand = {}
802
803
  for node_id, uncertainty in self._local_base_demand.items():
@@ -811,14 +812,16 @@ class ModelUncertainty(JsonSerializable):
811
812
  epanet_api.setbasedemand(node_idx, demand_idx + 1, base_demand)
812
813
 
813
814
  if self._global_demand_pattern is not None:
814
- self._global_demand_pattern.set_random_generator(np_rand_gen)
815
+ self._global_demand_pattern.set_random_generator(self.__np_rand_gen)
815
816
 
816
817
  self._cache_nodes_demand_pattern = {}
817
818
  demand_patterns_idx = np.array([epanet_api.getdemandpattern(node_idx, demand_idx + 1)
818
819
  for node_idx in epanet_api.get_all_nodes_idx()
819
820
  for demand_idx in range(epanet_api.getnumdemands(node_idx))])
820
821
 
821
- for pattern_idx in list(set(demand_patterns_idx)):
822
+ demand_patterns_idx = set(demand_patterns_idx)
823
+ demand_patterns_idx.discard(0)
824
+ for pattern_idx in list(demand_patterns_idx):
822
825
  demand_pattern = np.array(epanet_api.get_pattern(pattern_idx))
823
826
  self._cache_nodes_demand_pattern[pattern_idx] = np.copy(demand_pattern)
824
827
 
@@ -826,7 +829,7 @@ class ModelUncertainty(JsonSerializable):
826
829
  epanet_api.set_pattern(pattern_idx, demand_pattern.tolist())
827
830
 
828
831
  if self._local_demand_pattern is not None:
829
- self._local_demand_pattern.set_random_generator(np_rand_gen)
832
+ self._local_demand_pattern.set_random_generator(self.__np_rand_gen)
830
833
 
831
834
  self._cache_nodes_demand_pattern = {}
832
835
 
@@ -839,7 +842,7 @@ class ModelUncertainty(JsonSerializable):
839
842
  epanet_api.set_pattern(pattern_idx, demand_pattern.tolist())
840
843
 
841
844
  if self._global_elevation is not None:
842
- self._global_elevation.set_random_generator(np_rand_gen)
845
+ self._global_elevation.set_random_generator(self.__np_rand_gen)
843
846
 
844
847
  elevations = np.array([epanet_api.get_node_elevation(node_idx)
845
848
  for node_idx in epanet_api.get_all_nodes_idx()])
@@ -850,7 +853,7 @@ class ModelUncertainty(JsonSerializable):
850
853
  epanet_api.setnodevalue(node_idx + 1, EpanetConstants.EN_ELEVATION, node_elev)
851
854
 
852
855
  if self._local_elevation is not None:
853
- self._local_elevation.set_random_generator(np_rand_gen)
856
+ self._local_elevation.set_random_generator(self.__np_rand_gen)
854
857
  self._cache_nodes_elevation = np.array([epanet_api.get_node_elevation(node_idx)
855
858
  for node_idx in epanet_api.get_all_nodes_idx()])
856
859
 
@@ -862,7 +865,7 @@ class ModelUncertainty(JsonSerializable):
862
865
  epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION, elevation)
863
866
 
864
867
  if self._local_patterns is not None:
865
- self._local_patterns.set_random_generator(np_rand_gen)
868
+ self._local_patterns.set_random_generator(self.__np_rand_gen)
866
869
  self._cache_patterns = {}
867
870
 
868
871
  for pattern_id, uncertainty in self._local_patterns.items():
@@ -875,7 +878,7 @@ class ModelUncertainty(JsonSerializable):
875
878
 
876
879
  if epanet_api.msx_file is not None:
877
880
  if self._global_constants is not None:
878
- self._global_constants.set_random_generator(np_rand_gen)
881
+ self._global_constants.set_random_generator(self.__np_rand_gen)
879
882
 
880
883
  constants = np.array([epanet_api.MSXgetconstant(const_idx + 1)
881
884
  for const_idx in range(epanet_api.MSXgetcount(EpanetConstants.MSX_CONSTANT))])
@@ -886,7 +889,7 @@ class ModelUncertainty(JsonSerializable):
886
889
  epanet_api.MSXsetconstant(const_idx + 1, const_value)
887
890
 
888
891
  if self._local_constants:
889
- self._local_constants.set_random_generator(np_rand_gen)
892
+ self._local_constants.set_random_generator(self.__np_rand_gen)
890
893
 
891
894
  self._cache_msx_patterns = np.array([epanet_api.MSXgetconstant(const_idx + 1)
892
895
  for const_idx in range(epanet_api.MSXgetcount(EpanetConstants.MSX_CONSTANT))])
@@ -899,7 +902,7 @@ class ModelUncertainty(JsonSerializable):
899
902
  epanet_api.MSXsetconstant(idx, constant)
900
903
 
901
904
  if self._global_parameters is not None:
902
- self._global_parameters.set_random_generator(np_rand_gen)
905
+ self._global_parameters.set_random_generator(self.__np_rand_gen)
903
906
 
904
907
  self._cache_msx_links_parameters = {}
905
908
  num_params = epanet_api.MSXgetcount(EpanetConstants.MSX_PARAMETER)
@@ -934,7 +937,7 @@ class ModelUncertainty(JsonSerializable):
934
937
  epanet_api.MSXsetparameter(EpanetConstants.MSX_NODE, tank_idx, idx + 1, val)
935
938
 
936
939
  if self._local_parameters is not None:
937
- self._local_parameters.set_random_generator(np_rand_gen)
940
+ self._local_parameters.set_random_generator(self.__np_rand_gen)
938
941
  self._cache_msx_links_parameters = {}
939
942
  self._cache_msx_tanks_parameters = {}
940
943
 
@@ -959,7 +962,7 @@ class ModelUncertainty(JsonSerializable):
959
962
  epanet_api.MSXsetparameter(item_type, item_idx, idx, parameter)
960
963
 
961
964
  if self._local_msx_patterns is not None:
962
- self._local_msx_patterns.set_random_generator(np_rand_gen)
965
+ self._local_msx_patterns.set_random_generator(self.__np_rand_gen)
963
966
  self._cache_msx_patterns = {}
964
967
 
965
968
  for pattern_id, uncertainty in self._local_msx_patterns.items():
@@ -50,9 +50,9 @@ class SensorNoise(JsonSerializable):
50
50
  raise TypeError("'local_uncertainties' must be an instance of " +
51
51
  "'dict[tuple[int, str], epyt_flow.uncertainty.Uncertainty]'")
52
52
 
53
- self.__global_uncertainty = global_uncertainty
54
- self.__local_uncertainties = local_uncertainties
55
- self.__np_rand_gen = default_rng(seed)
53
+ self._global_uncertainty = global_uncertainty
54
+ self._local_uncertainties = local_uncertainties
55
+ self._np_rand_gen = default_rng(seed)
56
56
 
57
57
  super().__init__(**kwds)
58
58
 
@@ -66,7 +66,7 @@ class SensorNoise(JsonSerializable):
66
66
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
67
67
  Global sensor readings uncertainty.
68
68
  """
69
- return deepcopy(self.__global_uncertainty)
69
+ return deepcopy(self._global_uncertainty)
70
70
 
71
71
  @property
72
72
  def local_uncertainties(self) -> dict[int, str, Uncertainty]:
@@ -78,23 +78,23 @@ class SensorNoise(JsonSerializable):
78
78
  dict[tuple[int, str], :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`]
79
79
  Local (i.e. sensor specific) uncertainties.
80
80
  """
81
- return deepcopy(self.__local_uncertainties)
81
+ return deepcopy(self._local_uncertainties)
82
82
 
83
83
  def get_attributes(self) -> dict:
84
- return super().get_attributes() | {"global_uncertainty": self.__global_uncertainty,
85
- "local_uncertainties": self.__local_uncertainties}
84
+ return super().get_attributes() | {"global_uncertainty": self._global_uncertainty,
85
+ "local_uncertainties": self._local_uncertainties}
86
86
 
87
87
  def __eq__(self, other) -> bool:
88
88
  if not isinstance(other, SensorNoise):
89
89
  raise TypeError("Can not compare 'SensorNoise' instance " +
90
90
  f"with '{type(other)}' instance")
91
91
 
92
- return super().__eq__(other) and self.__global_uncertainty == other.global_uncertainty and \
93
- self.__local_uncertainties == other.local_uncertainties
92
+ return super().__eq__(other) and self._global_uncertainty == other.global_uncertainty and \
93
+ self._local_uncertainties == other.local_uncertainties
94
94
 
95
95
  def __str__(self) -> str:
96
- return f"global_uncertainty: {self.__global_uncertainty} " + \
97
- f"local_uncertainties: {self.__local_uncertainties}"
96
+ return f"global_uncertainty: {self._global_uncertainty} " + \
97
+ f"local_uncertainties: {self._local_uncertainties}"
98
98
 
99
99
  def apply_local_uncertainty(self, map_sensor_to_idx: Callable[[int, str], int],
100
100
  sensor_readings: numpy.ndarray) -> numpy.ndarray:
@@ -115,10 +115,10 @@ class SensorNoise(JsonSerializable):
115
115
  `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
116
116
  Perturbed sensor readings.
117
117
  """
118
- if self.__local_uncertainties is None:
118
+ if self._local_uncertainties is None:
119
119
  return sensor_readings
120
120
  else:
121
- self.__local_uncertainties.set_random_generator(self.__np_rand_gen)
121
+ self._local_uncertainties.set_random_generator(self._np_rand_gen)
122
122
 
123
123
  for (sensor_type, sensor_id), uncertainty in map_sensor_to_idx.items():
124
124
  idx = map_sensor_to_idx(sensor_type, sensor_id)
@@ -144,9 +144,9 @@ class SensorNoise(JsonSerializable):
144
144
  `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
145
145
  Perturbed sensor readings.
146
146
  """
147
- if self.__global_uncertainty is None:
147
+ if self._global_uncertainty is None:
148
148
  return sensor_readings
149
149
  else:
150
- self.__global_uncertainty.set_random_generator(self.__np_rand_gen)
150
+ self._global_uncertainty.set_random_generator(self._np_rand_gen)
151
151
 
152
- return self.__global_uncertainty.apply_batch(sensor_readings)
152
+ return self._global_uncertainty.apply_batch(sensor_readings)
@@ -101,9 +101,11 @@ class Uncertainty(ABC):
101
101
  return {"min_value": self.__min_value, "max_value": self.__max_value}
102
102
 
103
103
  def __eq__(self, other) -> bool:
104
- if not isinstance(other, Uncertainty):
104
+ if not isinstance(other, (Uncertainty, type(None))):
105
105
  raise TypeError("Can not compare 'Uncertainty' instance " +
106
106
  f"with '{type(other)}' instance")
107
+ if other is None:
108
+ return False
107
109
 
108
110
  return self.__min_value == other.min_value and self.__max_value == other.max_value and \
109
111
  self._random_generator == other.random_generator
@@ -221,7 +223,7 @@ class GaussianUncertainty(Uncertainty):
221
223
  return super().get_attributes() | {"mean": self._mean, "scale": self._scale}
222
224
 
223
225
  def __eq__(self, other) -> bool:
224
- if not isinstance(other, GaussianUncertainty):
226
+ if not isinstance(other, (GaussianUncertainty, type(None))):
225
227
  raise TypeError("Can not compare 'GaussianUncertainty' instance " +
226
228
  f"with '{type(other)}' instance")
227
229
 
@@ -320,7 +322,7 @@ class UniformUncertainty(Uncertainty):
320
322
  return super().get_attributes() | {"low": self.__low, "high": self.__high}
321
323
 
322
324
  def __eq__(self, other) -> bool:
323
- if not isinstance(other, UniformUncertainty):
325
+ if not isinstance(other, (UniformUncertainty, type(None))):
324
326
  raise TypeError("Can not compare 'UniformUncertainty' instance " +
325
327
  f"with '{type(other)}' instance")
326
328
 
@@ -562,7 +564,7 @@ class DeepUncertainty(Uncertainty):
562
564
  "max_noise_value": self.__max_noise_value}
563
565
 
564
566
  def __eq__(self, other) -> bool:
565
- if not isinstance(other, DeepUncertainty):
567
+ if not isinstance(other, (DeepUncertainty, type(None))):
566
568
  raise TypeError("Can not compare 'DeepUncertainty' instance " +
567
569
  f"with '{type(other)}' instance")
568
570