epyt-flow 0.2.0__py3-none-any.whl → 0.4.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
@@ -3,9 +3,12 @@ Module provides a class for representing the topology of WDN.
3
3
  """
4
4
  from copy import deepcopy
5
5
  import warnings
6
+ from typing import Any
6
7
  import numpy as np
7
8
  import networkx as nx
8
9
  from scipy.sparse import bsr_array
10
+ from geopandas import GeoDataFrame
11
+ from shapely.geometry import Point, LineString
9
12
 
10
13
  from .serialization import serializable, JsonSerializable, NETWORK_TOPOLOGY_ID
11
14
 
@@ -14,6 +17,35 @@ UNITS_USCUSTOM = 0
14
17
  UNITS_SIMETRIC = 1
15
18
 
16
19
 
20
+ def unitscategoryid_to_str(unit_category_id: int) -> str:
21
+ """
22
+ Converts a given units category ID to the corresponding description.
23
+
24
+ Parameters
25
+ ----------
26
+ unit_category_id : `int`
27
+ ID of the units category.
28
+
29
+ Must be one of the following constants:
30
+
31
+ - UNITS_USCUSTOM = 0
32
+ - UNITS_SIMETRIC = 1
33
+
34
+ Returns
35
+ -------
36
+ `str`
37
+ Units category description.
38
+ """
39
+ if unit_category_id is None:
40
+ return ""
41
+ elif unit_category_id == UNITS_USCUSTOM:
42
+ return "US CUSTOMARY"
43
+ elif unit_category_id == UNITS_SIMETRIC:
44
+ return "SI METRIC"
45
+ else:
46
+ raise ValueError(f"Unknown units category ID '{unit_category_id}'")
47
+
48
+
17
49
  @serializable(NETWORK_TOPOLOGY_ID, ".epytflow_topology")
18
50
  class NetworkTopology(nx.Graph, JsonSerializable):
19
51
  """
@@ -25,11 +57,17 @@ class NetworkTopology(nx.Graph, JsonSerializable):
25
57
  Path to .inp file to which this topology belongs.
26
58
  nodes : `list[tuple[str, dict]]`
27
59
  List of all nodes -- i.e. node ID and node information such as type and elevation.
28
- links : `list[tuple[tuple[str, str], dict]]`
60
+ links : `list[tuple[str, tuple[str, str], dict]]`
29
61
  List of all links/pipes -- i.e. link ID, ID of connecting nodes, and link information
30
62
  such as pipe diameter, length, etc.
63
+ pumps : `dict`
64
+ List of all pumps -- i.e. valve ID, and information such as
65
+ pump type and connecting nodes.
66
+ valves : `dict`
67
+ List of all valves -- i.e. valve ID, and information such as
68
+ valve type and connecting nodes.
31
69
  units : `int`
32
- Measurement units category.
70
+ Measurement units category -- i.e. US Customary or SI Metric.
33
71
 
34
72
  Must be one of the following constants:
35
73
 
@@ -38,12 +76,16 @@ class NetworkTopology(nx.Graph, JsonSerializable):
38
76
  """
39
77
  def __init__(self, f_inp: str, nodes: list[tuple[str, dict]],
40
78
  links: list[tuple[str, tuple[str, str], dict]],
79
+ pumps: dict,
80
+ valves: dict,
41
81
  units: int = None,
42
82
  **kwds):
43
83
  super().__init__(name=f_inp, **kwds)
44
84
 
45
85
  self.__nodes = nodes
46
86
  self.__links = links
87
+ self.__pumps = pumps
88
+ self.__valves = valves
47
89
  self.__units = units
48
90
 
49
91
  if units is None:
@@ -57,11 +99,83 @@ class NetworkTopology(nx.Graph, JsonSerializable):
57
99
  self.add_node(node_id, info={"elevation": node_elevation, "type": node_type})
58
100
 
59
101
  for link_id, link, link_info in links:
102
+ link_type = link_info["type"]
60
103
  link_diameter = link_info["diameter"]
61
104
  link_length = link_info["length"]
62
105
  self.add_edge(link[0], link[1], length=link_length,
63
- info={"id": link_id, "nodes": link, "diameter": link_diameter,
64
- "length": link_length})
106
+ info={"id": link_id, "type": link_type, "nodes": link,
107
+ "diameter": link_diameter, "length": link_length})
108
+
109
+ def convert_units(self, units: int) -> Any:
110
+ """
111
+ Converts this instance to a :class:`epyt_flow.topology.NetworkTopology` instance
112
+ where everything is measured in given measurement units category
113
+ (US Customary or SI Metric).
114
+
115
+ Parameters
116
+ ----------
117
+ units : `int`
118
+ Measurement units category.
119
+
120
+ Must be one of the following constants:
121
+
122
+ - UNITS_USCUSTOM = 0 (US Customary)
123
+ - UNITS_SIMETRIC = 1 (SI Metric)
124
+
125
+ Returns
126
+ -------
127
+ :class:`epyt_flow.topology.NetworkTopology`
128
+ Network topology with the new measurements units.
129
+ """
130
+ if self.__units is None:
131
+ raise ValueError("This instance does not contain any units!")
132
+
133
+ if not isinstance(units, int):
134
+ raise TypeError(f"'units' must be an instance of 'int' but not of '{type(units)}'")
135
+ if units not in [UNITS_SIMETRIC, UNITS_USCUSTOM]:
136
+ raise ValueError(f"Invalid units '{units}'")
137
+
138
+ if units == self.__units:
139
+ warnings.warn("Units already set in this NetworkTopology instance -- nothing to do!")
140
+ return deepcopy(self)
141
+
142
+ # Get all data and convert units
143
+ inch_to_millimeter = 25.4
144
+ feet_to_meter = 0.3048
145
+
146
+ nodes = []
147
+ for node_id in self.get_all_nodes():
148
+ node_info = self.get_node_info(node_id)
149
+ if units == UNITS_USCUSTOM:
150
+ conv_factor = 1. / feet_to_meter
151
+ else:
152
+ conv_factor = feet_to_meter
153
+ node_info["elevation"] *= conv_factor
154
+ if "diameter" in node_info:
155
+ node_info["diameter"] *= conv_factor
156
+
157
+ nodes.append((node_id, node_info))
158
+
159
+ links = []
160
+ for link_id, link_nodes in self.get_all_links():
161
+ link_info = self.get_link_info(link_id)
162
+
163
+ if units == UNITS_USCUSTOM:
164
+ conv_factor = 1. / feet_to_meter
165
+ else:
166
+ conv_factor = feet_to_meter
167
+ link_info["length"] *= conv_factor
168
+
169
+ if units == UNITS_USCUSTOM:
170
+ conv_factor = 1. / inch_to_millimeter
171
+ else:
172
+ conv_factor = inch_to_millimeter
173
+ link_info["diameter"] *= conv_factor
174
+
175
+ links.append((link_id, link_nodes, link_info))
176
+
177
+ return NetworkTopology(f_inp=self.name, nodes=nodes, links=links, pumps=self.pumps,
178
+ valves=self.valves, units=units)
65
179
 
66
180
  def get_all_nodes(self) -> list[str]:
67
181
  """
@@ -85,7 +199,29 @@ class NetworkTopology(nx.Graph, JsonSerializable):
85
199
  """
86
200
  return [(link_id, end_points) for link_id, end_points, _ in self.__links]
87
201
 
88
- def get_node_info(self, node_id) -> dict:
202
+ def get_all_pumps(self) -> list[str]:
203
+ """
204
+ Gets the IDs of all pumps.
205
+
206
+ Returns
207
+ -------
208
+ `list[str]`
209
+ Pump IDs.
210
+ """
211
+ return self.__pumps.keys()
212
+
213
+ def get_all_valves(self) -> list[str]:
214
+ """
215
+ Gets the IDs of all valves.
216
+
217
+ Returns
218
+ -------
219
+ `list[str]`
220
+ Valve IDs.
221
+ """
222
+ return self.__valves.keys()
223
+
224
+ def get_node_info(self, node_id: str) -> dict:
89
225
  """
90
226
  Gets all information (e.g. elevation, type, etc.) associated with a given node.
91
227
 
@@ -105,19 +241,21 @@ class NetworkTopology(nx.Graph, JsonSerializable):
105
241
 
106
242
  raise ValueError(f"Unknown node '{node_id}'")
107
243
 
108
- def get_link_info(self, link_id) -> dict:
244
+ def get_link_info(self, link_id: str) -> dict:
109
245
  """
110
- Gets all information (e.g. diameter, length, etc.) associated with a given link/pipe.
246
+ Gets all information (e.g. diameter, length, etc.) associated with a given link.
247
+
248
+ Note that links can be pipes, pumps, or valves.
111
249
 
112
250
  Parameters
113
251
  ----------
114
252
  link_id : `str`
115
- ID of the link/pipe.
253
+ ID of the link.
116
254
 
117
255
  Returns
118
256
  -------
119
257
  `dict`
120
- Information associated with the given link/pipe.
258
+ Information associated with the given link.
121
259
  """
122
260
  for link_id_, link_nodes, link_info in self.__links:
123
261
  if link_id_ == link_id:
@@ -125,6 +263,68 @@ class NetworkTopology(nx.Graph, JsonSerializable):
125
263
 
126
264
  raise ValueError(f"Unknown link '{link_id}'")
127
265
 
266
+ def get_pump_info(self, pump_id: str) -> dict:
267
+ """
268
+ Gets all information associated with a given pump.
269
+
270
+ Parameters
271
+ ----------
272
+ pump_id : `str`
273
+ ID of the pump.
274
+
275
+ Returns
276
+ -------
277
+ `dict`
278
+ Pump information.
279
+ """
280
+ if pump_id in self.__pumps:
281
+ return self.__pumps[pump_id]
282
+ else:
283
+ raise ValueError(f"Unknown pump: '{pump_id}'")
284
+
285
+ def get_valve_info(self, valve_id: str) -> dict:
286
+ """
287
+ Gets all information associated with a given valve.
288
+
289
+ Parameters
290
+ ----------
291
+ valve_id : `str`
292
+ ID of the valve.
293
+
294
+ Returns
295
+ -------
296
+ `dict`
297
+ Valve information.
298
+ """
299
+ if valve_id in self.__valves:
300
+ return self.__valves[valve_id]
301
+ else:
302
+ raise ValueError(f"Unknown valve: '{valve_id}'")
303
+
304
+ @property
305
+ def pumps(self) -> dict:
306
+ """
307
+ Gets all pumps -- i.e. ID and associated information such as the pump type.
308
+
309
+ Returns
310
+ -------
311
+ `dict`
312
+ All pumps and their associated information.
313
+ """
314
+ return deepcopy(self.__pumps)
315
+
316
+ @property
317
+ def valves(self) -> dict:
318
+ """
319
+ Gets all valves -- i.e. ID and associated information such as the valve type.
320
+
321
+ Returns
322
+ -------
323
+ `dict`
324
+ All valves and their associated information.
325
+ """
326
+ return deepcopy(self.__valves)
327
+
128
328
  @property
129
329
  def units(self) -> int:
130
330
  """
@@ -151,18 +351,120 @@ class NetworkTopology(nx.Graph, JsonSerializable):
151
351
  self.get_all_nodes() == other.get_all_nodes() \
152
352
  and all(link_a[0] == link_b[0] and all(link_a[1] == link_b[1])
153
353
  for link_a, link_b in zip(self.get_all_links(), other.get_all_links())) \
154
- and self.__units == other.units
354
+ and self.__units == other.units \
355
+ and self.__pumps == other.pumps \
356
+ and self.__valves == other.valves
155
357
 
156
358
  def __str__(self) -> str:
157
359
  return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links} " +\
158
- f"units: {self.__units}"
360
+ f"pumps: {self.__pumps} valves: {self.__valves} " +\
361
+ f"units: {unitscategoryid_to_str(self.__units)}"
159
362
 
160
363
  def get_attributes(self) -> dict:
161
364
  return super().get_attributes() | {"f_inp": self.name,
162
365
  "nodes": self.__nodes,
163
366
  "links": self.__links,
367
+ "pumps": self.__pumps,
368
+ "valves": self.__valves,
164
369
  "units": self.__units}
165
370
 
371
+ def to_gis(self, coord_reference_system: str = None, pumps_as_points: bool = False,
372
+ valves_as_points: bool = False) -> dict:
373
+ """
374
+ Gets the network topology as a dictionary of `geopandas.GeoDataFrames` instances --
375
+ i.e. each quantity (nodes, links/pipes, valves, etc.) is represented by a
376
+ `geopandas.GeoDataFrames` instance.
377
+
378
+ Parameters
379
+ ----------
380
+ coord_reference_system : `str`, optional
381
+ Coordinate reference system.
382
+
383
+ The default is None.
384
+ pumps_as_points : `bool`, optional
385
+ If True, pumps are represented by points, otherwise by lines.
386
+
387
+ The default is False.
388
+
389
+ valves_as_points : `bool`, optional
390
+ If True, valves are represented by points, otherwise by lines.
391
+
392
+ The default is False.
393
+
394
+ Returns
395
+ -------
396
+ `dict`
397
+ Network topology as a dictionary of `geopandas.GeoDataFrames` instances.
398
+ If a quantity does not exist, the data frame will be None.
399
+ """
400
+ gis = {"nodes": None, "links": None,
401
+ "tanks": None, "reservoirs": None,
402
+ "valves": None, "pumps": None}
403
+
404
+ # Nodes
405
+ node_data = {"id": [], "type": [], "elevation": [], "geometry": []}
406
+ tank_data = {"id": [], "elevation": [], "diameter": [], "geometry": []}
407
+ reservoir_data = {"id": [], "elevation": [], "geometry": []}
408
+ for node_id in self.get_all_nodes():
409
+ node_info = self.get_node_info(node_id)
410
+
411
+ node_data["id"].append(node_id)
412
+ node_data["type"].append(node_info["type"])
413
+ node_data["elevation"].append(node_info["elevation"])
414
+ node_data["geometry"].append(Point(node_info["coord"]))
415
+
416
+ if node_info["type"] == "TANK":
417
+ tank_data["id"].append(node_id)
418
+ tank_data["elevation"].append(node_info["elevation"])
419
+ tank_data["diameter"].append(node_info["diameter"])
420
+ tank_data["geometry"].append(Point(node_info["coord"]))
421
+ elif node_info["type"] == "RESERVOIR":
422
+ reservoir_data["id"].append(node_id)
423
+ reservoir_data["elevation"].append(node_info["elevation"])
424
+ reservoir_data["geometry"].append(Point(node_info["coord"]))
425
+
426
+ gis["nodes"] = GeoDataFrame(node_data, crs=coord_reference_system)
427
+ gis["tanks"] = GeoDataFrame(tank_data, crs=coord_reference_system)
428
+ gis["reservoirs"] = GeoDataFrame(reservoir_data, crs=coord_reference_system)
429
+
430
+ # Links
431
+ pipe_data = {"id": [], "type": [], "end_point_a": [], "end_point_b": [],
432
+ "length": [], "diameter": [], "geometry": []}
433
+ valve_data = {"id": [], "type": [], "geometry": []}
434
+ pump_data = {"id": [], "type": [], "geometry": []}
435
+ for link_id, link_nodes in self.get_all_links():
436
+ link_info = self.get_link_info(link_id)
437
+ end_points_coord = [self.get_node_info(n)["coord"] for n in link_nodes]
438
+
439
+ if link_info["type"] == "PIPE":
440
+ pipe_data["id"].append(link_id)
441
+ pipe_data["type"].append(link_info["type"])
442
+ pipe_data["end_point_a"].append(link_nodes[0])
443
+ pipe_data["end_point_b"].append(link_nodes[1])
444
+ pipe_data["length"].append(link_info["length"])
445
+ pipe_data["diameter"].append(link_info["diameter"])
446
+ pipe_data["geometry"].append(LineString(end_points_coord))
447
+ elif link_info["type"] == "PUMP":
448
+ pump_data["id"].append(link_id)
449
+ pump_data["type"].append(self.get_pump_info(link_id)["type"])
450
+ if pumps_as_points is True:
451
+ pump_data["geometry"].append(Point(end_points_coord[0]))
452
+ else:
453
+ pump_data["geometry"].append(LineString(end_points_coord))
454
+ else: # Valve
455
+ valve_data["id"].append(link_id)
456
+ valve_data["type"].append(self.get_valve_info[link_id]["type"])
457
+ if valves_as_points is True:
458
+ valve_data["geometry"].append(Point(end_points_coord[0]))
459
+ else:
460
+ valve_data["geometry"].append(LineString(end_points_coord))
461
+
462
+ gis["pipes"] = GeoDataFrame(pipe_data, crs=coord_reference_system)
463
+ gis["valves"] = GeoDataFrame(valve_data, crs=coord_reference_system)
464
+ gis["pumps"] = GeoDataFrame(pump_data, crs=coord_reference_system)
465
+
466
+ return gis
467
+
166
468
  def get_adj_matrix(self) -> bsr_array:
167
469
  """
168
470
  Gets the adjacency matrix of this graph.
@@ -2,6 +2,7 @@
2
2
  Module provides a class for implementing model uncertainty.
3
3
  """
4
4
  from copy import deepcopy
5
+ import warnings
5
6
  import epyt
6
7
  import numpy as np
7
8
 
@@ -29,7 +30,7 @@ class ModelUncertainty(JsonSerializable):
29
30
  Uncertainty of pipe diameters. None, in the case of no uncertainty.
30
31
 
31
32
  The default is None.
32
- demand_base_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional
33
+ base_demand_uncertainty : :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`, optional
33
34
  Uncertainty of base demands. None, in the case of no uncertainty.
34
35
 
35
36
  The default is None.
@@ -53,11 +54,17 @@ class ModelUncertainty(JsonSerializable):
53
54
  def __init__(self, pipe_length_uncertainty: Uncertainty = None,
54
55
  pipe_roughness_uncertainty: Uncertainty = None,
55
56
  pipe_diameter_uncertainty: Uncertainty = None,
56
- demand_base_uncertainty: Uncertainty = None,
57
+ base_demand_uncertainty: Uncertainty = None,
57
58
  demand_pattern_uncertainty: Uncertainty = None,
58
59
  elevation_uncertainty: Uncertainty = None,
59
60
  constants_uncertainty: Uncertainty = None,
60
- parameters_uncertainty: Uncertainty = None, **kwds):
61
+ parameters_uncertainty: Uncertainty = None,
62
+ demand_base_uncertainty: Uncertainty = None, **kwds):
63
+ if demand_base_uncertainty is not None:
64
+ warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
65
+ " -- support of such old files will be removed in the next release!",
66
+ DeprecationWarning)
67
+
61
68
  if pipe_length_uncertainty is not None:
62
69
  if not isinstance(pipe_length_uncertainty, Uncertainty):
63
70
  raise TypeError("'pipe_length_uncertainty' must be an instance of " +
@@ -73,11 +80,11 @@ class ModelUncertainty(JsonSerializable):
73
80
  raise TypeError("'pipe_diameter_uncertainty' must be an instance of " +
74
81
  "'epyt_flow.uncertainty.Uncertainty' but not of " +
75
82
  f"'{type(pipe_diameter_uncertainty)}'")
76
- if demand_base_uncertainty is not None:
77
- if not isinstance(demand_base_uncertainty, Uncertainty):
78
- raise TypeError("'demand_base_uncertainty' must be an instance of " +
83
+ if base_demand_uncertainty is not None:
84
+ if not isinstance(base_demand_uncertainty, Uncertainty):
85
+ raise TypeError("'base_demand_uncertainty' must be an instance of " +
79
86
  "'epyt_flow.uncertainty.Uncertainty' but not of " +
80
- f"'{type(demand_base_uncertainty)}'")
87
+ f"'{type(base_demand_uncertainty)}'")
81
88
  if demand_pattern_uncertainty is not None:
82
89
  if not isinstance(demand_pattern_uncertainty, Uncertainty):
83
90
  raise TypeError("'demand_pattern_uncertainty' must be an instance of " +
@@ -102,7 +109,7 @@ class ModelUncertainty(JsonSerializable):
102
109
  self.__pipe_length = pipe_length_uncertainty
103
110
  self.__pipe_roughness = pipe_roughness_uncertainty
104
111
  self.__pipe_diameter = pipe_diameter_uncertainty
105
- self.__demand_base = demand_base_uncertainty
112
+ self.__base_demand = base_demand_uncertainty
106
113
  self.__demand_pattern = demand_pattern_uncertainty
107
114
  self.__elevation = elevation_uncertainty
108
115
  self.__constants = constants_uncertainty
@@ -147,16 +154,16 @@ class ModelUncertainty(JsonSerializable):
147
154
  return deepcopy(self.__pipe_diameter)
148
155
 
149
156
  @property
150
- def demand_base(self) -> Uncertainty:
157
+ def base_demand(self) -> Uncertainty:
151
158
  """
152
- Gets the demand base uncertainty.
159
+ Gets the base demand uncertainty.
153
160
 
154
161
  Returns
155
162
  -------
156
163
  :class:`~epyt_flow.uncertainty.uncertainties.Uncertainty`
157
164
  Demand base uncertainty.
158
165
  """
159
- return deepcopy(self.__demand_base)
166
+ return deepcopy(self.__base_demand)
160
167
 
161
168
  @property
162
169
  def demand_pattern(self) -> Uncertainty:
@@ -210,7 +217,7 @@ class ModelUncertainty(JsonSerializable):
210
217
  return super().get_attributes() | {"pipe_length_uncertainty": self.__pipe_length,
211
218
  "pipe_roughness_uncertainty": self.__pipe_roughness,
212
219
  "pipe_diameter_uncertainty": self.__pipe_diameter,
213
- "demand_base_uncertainty": self.__demand_base,
220
+ "base_demand_uncertainty": self.__base_demand,
214
221
  "demand_pattern_uncertainty": self.__demand_pattern,
215
222
  "elevation_uncertainty": self.__elevation,
216
223
  "constants_uncertainty": self.__constants,
@@ -224,14 +231,14 @@ class ModelUncertainty(JsonSerializable):
224
231
  return self.__pipe_length == other.pipe_length \
225
232
  and self.__pipe_roughness == other.pipe_roughness \
226
233
  and self.__pipe_diameter == other.pipe_diameter \
227
- and self.__demand_base == other.demand_base \
234
+ and self.__base_demand == other.base_demand \
228
235
  and self.__demand_pattern == other.demand_pattern \
229
236
  and self.__elevation == other.elevation \
230
237
  and self.__parameters == other.parameters and self.__constants == other.constants
231
238
 
232
239
  def __str__(self) -> str:
233
240
  return f"pipe_length: {self.__pipe_length} pipe_roughness: {self.__pipe_roughness} " + \
234
- f"pipe_diameter: {self.__pipe_diameter} demand_base: {self.__demand_base} " + \
241
+ f"pipe_diameter: {self.__pipe_diameter} demand_base: {self.__base_demand} " + \
235
242
  f"demand_pattern: {self.__demand_pattern} elevation: {self.__elevation} " + \
236
243
  f"constants: {self.__constants} parameters: {self.__parameters}"
237
244
 
@@ -259,14 +266,14 @@ class ModelUncertainty(JsonSerializable):
259
266
  coeffs = self.__pipe_roughness.apply_batch(coeffs)
260
267
  epanet_api.setLinkRoughnessCoeff(coeffs)
261
268
 
262
- if self.__demand_base is not None:
269
+ if self.__base_demand is not None:
263
270
  all_nodes_idx = epanet_api.getNodeIndex()
264
271
  for node_idx in all_nodes_idx:
265
272
  n_demand_categories = epanet_api.getNodeDemandCategoriesNumber(node_idx)
266
- for demand_category in range(1, n_demand_categories):
267
- base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category]
268
- base_demand = self.__demand_base.apply(base_demand)
269
- epanet_api.setNodeBaseDemands(node_idx, demand_category, base_demand)
273
+ for demand_category in range(n_demand_categories):
274
+ base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1]
275
+ base_demand = self.__base_demand.apply(base_demand)
276
+ epanet_api.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
270
277
 
271
278
  if self.__demand_pattern is not None:
272
279
  demand_patterns_idx = epanet_api.getNodeDemandPatternIndex()
epyt_flow/utils.py CHANGED
@@ -214,8 +214,8 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
214
214
  y_pred - confidence_interval[0],
215
215
  y_pred + confidence_interval[1],
216
216
  alpha=0.5)
217
- ax.plot(y_pred, ".-", label="Prediction")
218
217
  ax.plot(y, ".-", label="Ground truth")
218
+ ax.plot(y_pred, ".-", label="Prediction")
219
219
  ax.legend()
220
220
 
221
221
  if show is True and fig is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: epyt-flow
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: EPyT-Flow -- EPANET Python Toolkit - Flow
5
5
  Author-email: André Artelt <aartelt@techfak.uni-bielefeld.de>, "Marios S. Kyriakou" <kiriakou.marios@ucy.ac.cy>, "Stelios G. Vrachimis" <vrachimis.stelios@ucy.ac.cy>
6
6
  License: MIT License
@@ -30,10 +30,12 @@ Requires-Dist: tqdm >=4.66.2
30
30
  Requires-Dist: openpyxl >=3.1.2
31
31
  Requires-Dist: falcon >=3.1.3
32
32
  Requires-Dist: multiprocess >=0.70.16
33
+ Requires-Dist: geopandas >=0.14.4
33
34
  Requires-Dist: psutil
34
35
 
35
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
36
36
  [![pypi](https://img.shields.io/pypi/v/epyt-flow.svg)](https://pypi.org/project/epyt-flow/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/epyt-flow)
37
39
  [![build](https://github.com/WaterFutures/EPyT-Flow/actions/workflows/build_tests.yml/badge.svg)](https://github.com/WaterFutures/EPyT-Flow/actions/workflows/build_tests.yml)
38
40
  [![Documentation Status](https://readthedocs.org/projects/epyt-flow/badge/?version=stable)](https://epyt-flow.readthedocs.io/en/stable/?badge=stable)
39
41
  [![Downloads](https://static.pepy.tech/badge/epyt-flow)](https://pepy.tech/project/epyt-flow)
@@ -41,6 +43,8 @@ Requires-Dist: psutil
41
43
 
42
44
  # EPyT-Flow -- EPANET Python Toolkit - Flow
43
45
 
46
+ <img src="https://github.com/WaterFutures/EPyT-Flow/blob/main/docs/_static/net1_plot.png?raw=true" align="right" height="230px"/>
47
+
44
48
  EPyT-Flow is a Python package building on top of [EPyT](https://github.com/OpenWaterAnalytics/EPyT)
45
49
  for providing easy access to water distribution network simulations.
46
50
  It aims to provide a high-level interface for the easy generation of hydraulic and water quality scenario data.
@@ -50,8 +54,6 @@ and [EPANET-MSX](https://github.com/USEPA/EPANETMSX/).
50
54
  EPyT-Flow provides easy access to popular benchmark data sets for event detection and localization.
51
55
  Furthermore, it also provides an environment for developing and testing control algorithms.
52
56
 
53
- ![](https://github.com/WaterFutures/EPyT-Flow/blob/main/docs/_static/net1_plot.png?raw=true)
54
-
55
57
 
56
58
  ## Unique Features
57
59
 
@@ -74,8 +76,18 @@ Unique features of EPyT-Flow that make it superior to other (Python) toolboxes a
74
76
  EPyT-Flow supports Python 3.9 - 3.12
75
77
 
76
78
  Note that [EPANET and EPANET-MSX sources](epyt_flow/EPANET/) are compiled and overwrite the binaries
77
- shipped by EPyT IF EPyT-Flow is installed on a Linux system. By this we not only aim to achieve
78
- a better performance of the simulations but also avoid any compatibility problems of pre-compiled binaries.
79
+ shipped by EPyT **IF** EPyT-Flow is installed on a Unix system and the *gcc* compiler is available.
80
+ By this, we not only aim to achieve a better performance of the simulations but also avoid any
81
+ compatibility issues of pre-compiled binaries.
82
+
83
+ #### Prerequisites for macOS users
84
+ The "true" *gcc* compiler (version 12) is needed which is not the
85
+ *clang* compiler that is shipped with Xcode and is linked to gcc!
86
+
87
+ The correct version of the "true" *gcc* can be installed via [brew](https://brew.sh/):
88
+ ```
89
+ brew install gcc@12
90
+ ```
79
91
 
80
92
  ### PyPI
81
93