1"""
2This module contains a Python toolkit with higher-level functions for working with
3EPANET and EPANET-MSX.
4"""
5import os
6import re
7import time
8import tempfile
9
10from .epanet_wrapper import EpanetAPI
11
12
[docs]
13class EpanetConstants:
14 """
15 EPANET and EPANET-MSX constants.
16 """
17 EN_TRUE = 1
18 EN_FALSE = 0
19
20 EN_MAXID = 31
21 EN_MAXMSG = 255
22 EN_ELEVATION = 0 # Elevation
23 EN_BASEDEMAND = 1 # Primary demand baseline value
24 EN_PATTERN = 2 # Primary demand time pattern index
25 EN_EMITTER = 3 # Emitter flow coefficient
26 EN_INITQUAL = 4 # Initial quality
27 EN_SOURCEQUAL = 5 # Quality source strength
28 EN_SOURCEPAT = 6 # Quality source pattern index
29 EN_SOURCETYPE = 7 # Quality source type (see @ref EN_SourceType)
30 EN_TANKLEVEL = 8 # Current computed tank water level (read only)
31 EN_DEMAND = 9 # Current computed demand (read only)
32 EN_HEAD = 10 # Current computed hydraulic head (read only)
33 EN_PRESSURE = 11 # Current computed pressure (read only)
34 EN_QUALITY = 12 # Current computed quality (read only)
35 EN_SOURCEMASS = 13 # Current computed quality source mass inflow (read only)
36 EN_INITVOLUME = 14 # Tank initial volume (read only)
37 EN_MIXMODEL = 15 # Tank mixing model (see @ref EN_MixingModel)
38 EN_MIXZONEVOL = 16 # Tank mixing zone volume (read only)
39 EN_TANKDIAM = 17 # Tank diameter
40 EN_MINVOLUME = 18 # Tank minimum volume
41 EN_VOLCURVE = 19 # Tank volume curve index
42 EN_MINLEVEL = 20 # Tank minimum level
43 EN_MAXLEVEL = 21 # Tank maximum level
44 EN_MIXFRACTION = 22 # Tank mixing fraction
45 EN_TANK_KBULK = 23 # Tank bulk decay coefficient
46 EN_TANKVOLUME = 24 # Current computed tank volume (read only)
47 EN_MAXVOLUME = 25 # Tank maximum volume (read only)
48 EN_CANOVERFLOW = 26 # Tank can overflow (= 1) or not (= 0)
49 EN_DEMANDDEFICIT = 27 # Amount that full demand is reduced under PDA (read only)
50 EN_NODE_INCONTROL = 28 # `EN_TRUE` (= 1) if the node appears in any control, `EN_FALSE` (= 0) if not
51 EN_EMITTERFLOW = 29 # Current emitter flow (read only)
52 EN_LEAKAGEFLOW = 30 # Current leakage flow (read only)
53 EN_DEMANDFLOW = 31 # Current consumer demand delivered (read only)
54 EN_FULLDEMAND = 32 # Current consumer demand requested (read only)
55
56 EN_DIAMETER = 0 # Pipe/valve diameter
57 EN_LENGTH = 1 # Pipe length
58 EN_ROUGHNESS = 2 # Pipe roughness coefficient
59 EN_MINORLOSS = 3 # Pipe/valve minor loss coefficient
60 EN_INITSTATUS = 4 # Initial status (see @ref EN_LinkStatusType)
61 EN_INITSETTING = 5 # Initial pump speed or valve setting
62 EN_KBULK = 6 # Bulk chemical reaction coefficient
63 EN_KWALL = 7 # Pipe wall chemical reaction coefficient
64 EN_FLOW = 8 # Current computed flow rate (read only)
65 EN_VELOCITY = 9 # Current computed flow velocity (read only)
66 EN_HEADLOSS = 10 # Current computed head loss (read only)
67 EN_STATUS = 11 # Current link status (see @ref EN_LinkStatusType)
68 EN_SETTING = 12 # Current link setting
69 EN_ENERGY = 13 # Current computed pump energy usage (read only)
70 EN_LINKQUAL = 14 # Current computed link quality (read only)
71 EN_LINKPATTERN = 15 # Pump speed time pattern index
72 EN_PUMP_STATE = 16 # Current computed pump state (read only) (see @ref EN_PumpStateType)
73 EN_PUMP_EFFIC = 17 # Current computed pump efficiency (read only)
74 EN_PUMP_POWER = 18 # Pump constant power rating
75 EN_PUMP_HCURVE = 19 # Pump head v. flow curve index
76 EN_PUMP_ECURVE = 20 # Pump efficiency v. flow curve index
77 EN_PUMP_ECOST = 21 # Pump average energy price
78 EN_PUMP_EPAT = 22 # Pump energy price time pattern index
79 EN_LINK_INCONTROL = 23 # Is present in any simple or rule-based control (= 1) or not (= 0)
80 EN_GPV_CURVE = 24 # GPV head loss v. flow curve index
81 EN_PCV_CURVE = 25 # PCV characteristic curve index
82 EN_LEAK_AREA = 26 # Pipe leak area (sq mm per 100 length units)
83 EN_LEAK_EXPAN = 27 # Leak expansion rate (sq mm per unit of pressure head)
84 EN_LINK_LEAKAGE = 28 # Current leakage rate (read only)
85 EN_VALVE_TYPE = 29 # Type of valve (see @ref EN_LinkType)
86
87 EN_DURATION = 0 # Total simulation duration
88 EN_HYDSTEP = 1 # Hydraulic time step
89 EN_QUALSTEP = 2 # Water quality time step
90 EN_PATTERNSTEP = 3 # Time pattern period
91 EN_PATTERNSTART = 4 # Time when time patterns begin
92 EN_REPORTSTEP = 5 # Reporting time step
93 EN_REPORTSTART = 6 # Time when reporting starts
94 EN_RULESTEP = 7 # Rule-based control evaluation time step
95 EN_STATISTIC = 8 # Reporting statistic code (see @ref EN_StatisticType)
96 EN_PERIODS = 9 # Number of reporting time periods (read only)
97 EN_STARTTIME = 10 # Simulation starting time of day
98 EN_HTIME = 11 # Elapsed time of current hydraulic solution (read only)
99 EN_QTIME = 12 # Elapsed time of current quality solution (read only)
100 EN_HALTFLAG = 13 # Flag indicating if the simulation was halted (read only)
101 EN_NEXTEVENT = 14 # Shortest time until a tank becomes empty or full (read only)
102 EN_NEXTEVENTTANK = 15 # Index of tank with shortest time to become empty or full (read only)
103
104 EN_STEP_REPORT = 0 # A reporting time step has ended
105 EN_STEP_HYD = 1 # A hydraulic time step has ended
106 EN_STEP_WQ = 2 # A water quality time step has ended
107 EN_STEP_TANKEVENT = 3 # A tank has become empty or full
108 EN_STEP_CONTROLEVENT = 4 # A link control needs to be activated
109
110 EN_ITERATIONS = 0 # Number of hydraulic iterations taken
111 EN_RELATIVEERROR = 1 # Sum of link flow changes / sum of link flows
112 EN_MAXHEADERROR = 2 # Largest head loss error for links
113 EN_MAXFLOWCHANGE = 3 # Largest flow change in links
114 EN_MASSBALANCE = 4 # Cumulative water quality mass balance ratio
115 EN_DEFICIENTNODES = 5 # Number of pressure deficient nodes
116 EN_DEMANDREDUCTION = 6 # % demand reduction at pressure deficient nodes
117 EN_LEAKAGELOSS = 7 # % flow lost to system leakage
118
119 EN_ITERATIONS = 0 # Number of hydraulic iterations taken
120 EN_RELATIVEERROR = 1 # Sum of link flow changes / sum of link flows
121 EN_MAXHEADERROR = 2 # Largest head loss error for links
122 EN_MAXFLOWCHANGE = 3 # Largest flow change in links
123 EN_MASSBALANCE = 4 # Cumulative water quality mass balance ratio
124 EN_DEFICIENTNODES = 5 # Number of pressure deficient nodes
125 EN_DEMANDREDUCTION = 6
126
127 EN_NODE = 0 # Nodes
128 EN_LINK = 1 # Links
129 EN_TIMEPAT = 2 # Time patterns
130 EN_CURVE = 3 # Data curves
131 EN_CONTROL = 4 # Simple controls
132 EN_RULE = 5 # Control rules
133
134 EN_NODECOUNT = 0 # Number of nodes (junctions + tanks + reservoirs)
135 EN_TANKCOUNT = 1 # Number of tanks and reservoirs
136 EN_LINKCOUNT = 2 # Number of links (pipes + pumps + valves)
137 EN_PATCOUNT = 3 # Number of time patterns
138 EN_CURVECOUNT = 4 # Number of data curves
139 EN_CONTROLCOUNT = 5 # Number of simple controls
140 EN_RULECOUNT = 6 # Number of rule-based controls
141
142 EN_JUNCTION = 0 # Junction node
143 EN_RESERVOIR = 1 # Reservoir node
144 EN_TANK = 2 # Storage tank node
145
146 EN_CVPIPE = 0 # Pipe with check valve
147 EN_PIPE = 1 # Pipe
148 EN_PUMP = 2 # Pump
149 EN_PRV = 3 # Pressure reducing valve
150 EN_PSV = 4 # Pressure sustaining valve
151 EN_PBV = 5 # Pressure breaker valve
152 EN_FCV = 6 # Flow control valve
153 EN_TCV = 7 # Throttle control valve
154 EN_GPV = 8 # General purpose valve
155 EN_PCV = 9 # Positional control valve
156
157 EN_CLOSED = 0 # Link is closed and cannot convey any flow
158 EN_OPEN = 1 # Link is open and is able to convey flow
159
160 EN_PUMP_XHEAD = 0 # Pump closed - cannot supply head
161 EN_PUMP_CLOSED = 2 # Pump closed
162 EN_PUMP_OPEN = 3 # Pump open
163 EN_PUMP_XFLOW = 5 # Pump open - cannot supply flow
164
165 EN_NONE = 0 # No quality analysis
166 EN_CHEM = 1 # Chemical fate and transport
167 EN_AGE = 2 # Water age analysis
168 EN_TRACE = 3 # Source tracing analysis
169
170 EN_CONCEN = 0 # Sets the concentration of external inflow entering a node
171 EN_MASS = 1 # Injects a given mass/minute into a node
172 EN_SETPOINT = 2 # Sets the concentration leaving a node to a given value
173 EN_FLOWPACED = 3 # Adds a given value to the concentration leaving a node
174
175 EN_HW = 0 # Hazen-Williams
176 EN_DW = 1 # Darcy-Weisbach
177 EN_CM = 2 # Chezy-Manning
178
179 EN_CFS = 0 # Cubic feet per second
180 EN_GPM = 1 # Gallons per minute
181 EN_MGD = 2 # Million gallons per day
182 EN_IMGD = 3 # Imperial million gallons per day
183 EN_AFD = 4 # Acre-feet per day
184 EN_LPS = 5 # Liters per second
185 EN_LPM = 6 # Liters per minute
186 EN_MLD = 7 # Million liters per day
187 EN_CMH = 8 # Cubic meters per hour
188 EN_CMD = 9 # Cubic meters per day
189 EN_CMS = 10 # Cubic meters per second
190
191 EN_PSI = 0 # Pounds per square inch
192 EN_KPA = 1 # Kilopascals
193 EN_METERS = 2 # Meters
194 EN_BAR = 3 # Bar
195 EN_FEET = 4 # Feet
196
197 EN_DDA = 0 # Demand driven analysis
198 EN_PDA = 1 # Pressure driven analysis
199
200 EN_TRIALS = 0 # Maximum trials allowed for hydraulic convergence
201 EN_ACCURACY = 1 # Total normalized flow change for hydraulic convergence
202 EN_TOLERANCE = 2 # Water quality tolerance
203 EN_EMITEXPON = 3 # Exponent in emitter discharge formula
204 EN_DEMANDMULT = 4 # Global demand multiplier
205 EN_HEADERROR = 5 # Maximum head loss error for hydraulic convergence
206 EN_FLOWCHANGE = 6 # Maximum flow change for hydraulic convergence
207 EN_HEADLOSSFORM = 7 # Head loss formula (see @ref EN_HeadLossType)
208 EN_GLOBALEFFIC = 8 # Global pump efficiency (percent)
209 EN_GLOBALPRICE = 9 # Global energy price per KWH
210 EN_GLOBALPATTERN = 10 # Index of a global energy price pattern
211 EN_DEMANDCHARGE = 11 # Energy charge per max. KW usage
212 EN_SP_GRAVITY = 12 # Specific gravity
213 EN_SP_VISCOS = 13 # Specific viscosity (relative to water at 20 deg C)
214 EN_UNBALANCED = 14 # Extra trials allowed if hydraulics don't converge
215 EN_CHECKFREQ = 15 # Frequency of hydraulic status checks
216 EN_MAXCHECK = 16 # Maximum trials for status checking
217 EN_DAMPLIMIT = 17 # Accuracy level where solution damping begins
218 EN_SP_DIFFUS = 18 # Specific diffusivity (relative to chlorine at 20 deg C)
219 EN_BULKORDER = 19 # Bulk water reaction order for pipes
220 EN_WALLORDER = 20 # Wall reaction order for pipes (either 0 or 1)
221 EN_TANKORDER = 21 # Bulk water reaction order for tanks
222 EN_CONCENLIMIT = 22 # Limiting concentration for growth reactions
223 EN_DEMANDPATTERN = 23 # Name of default demand pattern
224 EN_EMITBACKFLOW = 24 # `EN_TRUE` (= 1) if emitters can backflow, `EN_FALSE` (= 0) if not
225 EN_PRESS_UNITS = 25 # Pressure units (see @ref EN_PressUnits)
226 EN_STATUS_REPORT = 26 # Type of status report to produce (see @ref EN_StatusReport)
227
228 EN_LOWLEVEL = 0 # Act when pressure or tank level drops below a setpoint
229 EN_HILEVEL = 1 # Act when pressure or tank level rises above a setpoint
230 EN_TIMER = 2 # Act at a prescribed elapsed amount of time
231 EN_TIMEOFDAY = 3 # Act at a particular time of day
232
233 EN_SERIES = 0 # Report all time series points
234 EN_AVERAGE = 1 # Report average value over simulation period
235 EN_MINIMUM = 2 # Report minimum value over simulation period
236 EN_MAXIMUM = 3 # Report maximum value over simulation period
237 EN_RANGE = 4 # Report maximum - minimum over simulation period
238
239 EN_MIX1 = 0 # Complete mix model
240 EN_MIX2 = 1 # 2-compartment model
241 EN_FIFO = 2 # First in, first out model
242 EN_LIFO = 3 # Last in, first out model
243
244 EN_NOSAVE = 0 # Don't save hydraulics; don't re-initialize flows
245 EN_SAVE = 1 # Save hydraulics to file, don't re-initialize flows
246 EN_INITFLOW = 10 # Don't save hydraulics; re-initialize flows
247 EN_SAVE_AND_INIT = 11 # Save hydraulics; re-initialize flows
248
249 EN_CONST_HP = 0 # Constant horsepower
250 EN_POWER_FUNC = 1 # Power function
251 EN_CUSTOM = 2 # User-defined custom curve
252 EN_NOCURVE = 3 # No curve
253
254 EN_VOLUME_CURVE = 0 # Tank volume v. depth curve
255 EN_PUMP_CURVE = 1 # Pump head v. flow curve
256 EN_EFFIC_CURVE = 2 # Pump efficiency v. flow curve
257 EN_HLOSS_CURVE = 3 # Valve head loss v. flow curve
258 EN_GENERIC_CURVE = 4 # Generic curve
259 EN_VALVE_CURVE = 5 # % of fully open flow v. % open
260
261 EN_UNCONDITIONAL = 0 # Delete all controls and connecing links
262 EN_CONDITIONAL = 1 # Cancel object deletion if it appears in controls or has connecting links
263
264 N_NO_REPORT = 0 # No status reporting
265 EN_NORMAL_REPORT = 1 # Normal level of status reporting
266 EN_FULL_REPORT = 2 # Full level of status reporting
267
268 EN_R_NODE = 6 # Clause refers to a node
269 EN_R_LINK = 7 # Clause refers to a link
270 EN_R_SYSTEM = 8 # Clause refers to a system parameter (e.g., time)
271
272 EN_R_DEMAND = 0 # Nodal demand
273 EN_R_HEAD = 1 # Nodal hydraulic head
274 EN_R_GRADE = 2 # Nodal hydraulic grade
275 EN_R_LEVEL = 3 # Tank water level
276 EN_R_PRESSURE = 4 # Nodal pressure
277 EN_R_FLOW = 5 # Link flow rate
278 EN_R_STATUS = 6 # Link status
279 EN_R_SETTING = 7 # Link setting
280 EN_R_POWER = 8 # Pump power output
281 EN_R_TIME = 9 # Elapsed simulation time
282 EN_R_CLOCKTIME = 10 # Time of day
283 EN_R_FILLTIME = 11 # Time to fill a tank
284 EN_R_DRAINTIME = 12 # Time to drain a tank
285
286 EN_R_EQ = 0 # Equal to
287 EN_R_NE = 1 # Not equal
288 EN_R_LE = 2 # Less than or equal to
289 EN_R_GE = 3 # Greater than or equal to
290 EN_R_LT = 4 # Less than
291 EN_R_GT = 5 # Greater than
292 EN_R_IS = 6 # Is equal to
293 EN_R_NOT = 7 # Is not equal to
294 EN_R_BELOW = 8 # Is below
295 EN_R_ABOVE = 9 # Is above
296
297 EN_R_IS_OPEN = 1 # Link is open
298 EN_R_IS_CLOSED = 2 # Link is closed
299 EN_R_IS_ACTIVE = 3 # Control valve is active
300
301 EN_MISSING = -1.E10 # Missing value indicator
302 EN_SET_CLOSED = -1.E10 # Link set closed indicator
303 EN_SET_OPEN = 1.E10 # Link set open indicator
304
305 MSX_NODE = 0
306 MSX_LINK = 1
307 MSX_TANK = 2
308 MSX_SPECIES = 3
309 MSX_TERM = 4
310 MSX_PARAMETER = 5
311 MSX_CONSTANT = 6
312 MSX_PATTERN = 7
313 MSX_BULK = 0
314 MSX_WALL = 1
315 MSX_NOSOURCE = -1
316 MSX_CONCEN = 0
317 MSX_MASS = 1
318 MSX_SETPOINT = 2
319 MSX_FLOWPACED = 3
320
321
[docs]
322class EPyT(EpanetAPI):
323 """
324 Python toolkit for EPANET and EPANET-MSX.
325
326 Parameters
327 ----------
328 inp_file_in : `str`, optional
329 Path to .inp file. Note that the file will be created automatically if it does not exist.
330
331 If None, an empty network will be created in the temp folder.
332
333 The default is None.
334 msx_file_in : `str`, optional
335 Path to .msx file.
336 If this is not None, `use_project` must be set to False.
337
338 The default is None.
339 use_project : `bool`, optional
340 If True, projects will be used when calling EPANET functions (default in EPANET >= 2.2).
341 Note that this is incompatible with EPANET-MSX. Please set to False when using EPANET-MSX
342 or when specifying an .msx file in `msx_file_in`.
343
344 The default is False.
345 inp_buffer : `str`, optional,
346 Buffer containing the network -- i.e., content of an .inp file.
347
348 The default is None.
349 """
350 def __init__(self, inp_file_in: str = None, msx_file_in: str = None, use_project: bool = False,
351 inp_buffer: str = None, **kwds):
352 if msx_file_in is not None and use_project is True:
353 raise ValueError("'use_project' must be False if 'msx_file_in' is not None")
354
355 super().__init__(use_project=use_project, **kwds)
356
357 if use_project is True:
358 self.createproject()
359
360 if inp_file_in is None:
361 inp_file_in = os.path.join(tempfile.gettempdir(), f"{time.time()}.inp")
362
363 with open(inp_file_in, "w") as f_inp:
364 f_inp.flush()
365 else:
366 if not os.path.exists(inp_file_in): # Create empty file if it does not exist
367 with open(inp_file_in, "w") as f_inp:
368 f_inp.flush()
369
370 self._inp_file = inp_file_in
371 self._msx_file = msx_file_in
372
373 if inp_buffer is not None:
374 self.openfrombuffer(inp_buffer, self._inp_file, self._inp_file + ".rpt", "")
375 else:
376 self.open(self._inp_file, self._inp_file + ".rpt", "")
377
378 if msx_file_in is not None:
379 self.load_msx_file(self._msx_file)
380
381 def __enter__(self):
382 return self
383
384 def __exit__(self, *args):
385 self.close()
386
[docs]
387 def load_msx_file(self, msx_file_in: str) -> None:
388 """
389 Loads an EPANET-MSX file.
390
391 Parameters
392 ----------
393 msx_file_in : `str`
394 Path to .msx file.
395 """
396 if self._use_project is True:
397 raise ValueError("EPANET-MSX can not be used with EPANET projects")
398
399 self.MSXopen(msx_file_in)
400 self._msx_file = msx_file_in
401
[docs]
402 def close(self) -> None:
403 """
404 Closes EPANET and EPANET-MSX, and deletes all temprorary files.
405 """
406 if self._msx_file is not None:
407 self.MSXclose()
408
409 super().close()
410
411 if self._use_project is True:
412 self.deleteproject()
413
414 @property
415 def msx_file(self) -> str:
416 """
417 Returns the file path to the .msx file.
418
419 Returns
420 -------
421 `str`
422 File path to .msx file.
423 """
424 return self._msx_file
425
[docs]
426 def get_all_nodes_id(self) -> list[str]:
427 """
428 Returns all node IDs.
429
430 Returns
431 -------
432 `list[str]`
433 List of node IDs.
434 """
435 return [self.getnodeid(i + 1) for i in range(self.getcount(EpanetConstants.EN_NODECOUNT))]
436
[docs]
437 def get_num_nodes(self) -> int:
438 """
439 Returns the number of nodes in the network.
440
441 Returns
442 -------
443 `int`
444 Number of nodes.
445 """
446 return self.getcount(EpanetConstants.EN_NODECOUNT)
447
[docs]
448 def get_all_nodes_idx(self) -> list[int]:
449 """
450 Returns all node indices.
451
452 Returns
453 -------
454 `list[int]`
455 List of node indices:
456 """
457 return list(range(1, self.getcount(EpanetConstants.EN_NODECOUNT) + 1))
458
[docs]
459 def get_all_junctions_id(self) -> list[str]:
460 """
461 Returns all junction IDs -- i.e., IDs of nodes that are neither a reservoir
462 nor a tank.
463
464 Returns
465 -------
466 `list[str]`
467 List of all junction IDs.
468 """
469 r = []
470
471 for i in range(self.getcount(EpanetConstants.EN_NODECOUNT)):
472 if self.getnodetype(i + 1) == EpanetConstants.EN_JUNCTION:
473 r.append(self.getnodeid(i + 1))
474
475 return r
476
[docs]
477 def get_all_junctions_idx(self) -> list[int]:
478 """
479 Returns all junction indices -- i.e., indices of nodes that are neither a reservoir
480 nor a tank.
481
482 Returns
483 -------
484 `list[int]`
485 List of all junction indices.
486 """
487 r = []
488
489 for i in range(self.getcount(EpanetConstants.EN_NODECOUNT)):
490 if self.getnodetype(i + 1) == EpanetConstants.EN_JUNCTION:
491 r.append(i + 1)
492
493 return r
494
[docs]
495 def get_num_junctions(self) -> int:
496 """
497 Returns the number of junctions -- i.e., number of nodes that are neither a tank nor
498 a reservoir.
499
500 Returns
501 -------
502 `int`
503 Number of junctions.
504 """
505 return len(self.get_all_junctions_idx())
506
[docs]
507 def get_all_links_id(self) -> list[str]:
508 """
509 Returns all link IDs.
510
511 Returns
512 -------
513 `list[str]`
514 List of all link IDs.
515 """
516 return [self.getlinkid(i + 1) for i in range(self.getcount(EpanetConstants.EN_LINKCOUNT))]
517
[docs]
518 def get_all_links_idx(self) -> list[int]:
519 """
520 Returns all link indcies.
521
522 Returns
523 -------
524 `list[int]`
525 List of all link indices.
526 """
527 return list(range(1, self.getcount(EpanetConstants.EN_LINKCOUNT) + 1))
528
[docs]
529 def get_num_links(self) -> int:
530 """
531 Returns the number of links in the network.
532
533 Returns
534 -------
535 `int`
536 Number of links.
537 """
538 return self.getcount(EpanetConstants.EN_LINKCOUNT)
539
[docs]
540 def get_all_pipes_idx(self) -> list[int]:
541 """
542 Return the indices of all pipes in the network.
543
544 Returns
545 -------
546 `list[int]`
547 List of pipe indices.
548 """
549 r = []
550
551 for i in range(self.getcount(EpanetConstants.EN_LINKCOUNT)):
552 link_type = self.getlinktype(i + 1)
553 if link_type == EpanetConstants.EN_PIPE:
554 r.append(i + 1)
555
556 return r
557
[docs]
558 def get_all_pipes_id(self) -> list[str]:
559 """
560 Returns the IDs of all pipes in the network.
561
562 Returns
563 -------
564 `list[str]`
565 List of pipe IDs.
566 """
567 r = []
568
569 for i in range(self.getcount(EpanetConstants.EN_LINKCOUNT)):
570 link_type = self.getlinktype(i + 1)
571 if link_type == EpanetConstants.EN_PIPE:
572 r.append(self.getlinkid(i + 1))
573
574 return r
575
[docs]
576 def get_num_pipes(self) -> int:
577 """
578 Returns the number of pipes in the network.
579
580 Returns
581 -------
582 `int`
583 Returns the maximum number of pipes.
584 """
585 return len(self.get_all_pipes_idx())
586
[docs]
587 def get_all_valves_id(self) -> list[str]:
588 """
589 Returns a list of all valve IDs.
590
591 Returns
592 -------
593 `list[str]`
594 List of all valve IDs.
595 """
596 r = []
597
598 for i in range(self.getcount(EpanetConstants.EN_LINKCOUNT)):
599 link_type = self.getlinktype(i + 1)
600 if link_type != EpanetConstants.EN_PIPE and link_type != EpanetConstants.EN_PUMP:
601 r.append(self.getlinkid(i + 1))
602
603 return r
604
[docs]
605 def get_num_valves(self) -> int:
606 """
607 Returns the number of valves.
608
609 Returns
610 -------
611 `int`
612 Number of valves.
613 """
614 return len(self.get_all_valves_idx())
615
[docs]
616 def get_all_valves_idx(self) -> list[int]:
617 """
618 Returns all valve indices.
619
620 Returns
621 -------
622 `list[int]`
623 List of all valve indices.
624 """
625 r = []
626
627 for i in range(self.getcount(EpanetConstants.EN_LINKCOUNT)):
628 link_type = self.getlinktype(i + 1)
629 if link_type != EpanetConstants.EN_PIPE and link_type != EpanetConstants.EN_PUMP:
630 r.append(i + 1)
631
632 return r
633
[docs]
634 def get_all_pumps_id(self) -> list[str]:
635 """
636 Returns all pump IDs.
637
638 Returns
639 -------
640 `list[str]`
641 List of all pump IDs.
642 """
643 r = []
644
645 for i in range(self.getcount(EpanetConstants.EN_LINKCOUNT)):
646 if self.getlinktype(i + 1) == EpanetConstants.EN_PUMP:
647 r.append(self.getlinkid(i + 1))
648
649 return r
650
[docs]
651 def get_num_pumps(self) -> int:
652 """
653 Returns the number of pumps in the network.
654
655 Returns
656 -------
657 `int`
658 Number of pumps.
659 """
660 return len(self.get_all_pumps_idx())
661
[docs]
662 def get_all_pumps_idx(self) -> list[int]:
663 """
664 Returns all pump_indices.
665
666 Returns
667 -------
668 `list[int]`
669 List of all pump indices.
670 """
671 r = []
672
673 for i in range(self.getcount(EpanetConstants.EN_LINKCOUNT)):
674 if self.getlinktype(i + 1) == EpanetConstants.EN_PUMP:
675 r.append(i + 1)
676
677 return r
678
[docs]
679 def get_all_tanks_id(self) -> list[str]:
680 """
681 Returns all tank IDs.
682
683 Returns
684 -------
685 `list[str]`
686 List of all tank IDs.
687 """
688 r = []
689
690 for i in range(self.getcount(EpanetConstants.EN_NODECOUNT)):
691 if self.getnodetype(i + 1) == EpanetConstants.EN_TANK:
692 r.append(self.getnodeid(i + 1))
693
694 return r
695
[docs]
696 def get_num_tanks(self) -> int:
697 """
698 Returns the number of tanks in the network.
699
700 Returns
701 -------
702 `int`
703 Number of tanks.
704 """
705 return self.getcount(EpanetConstants.EN_TANKCOUNT)
706
[docs]
707 def get_all_tanks_idx(self) -> list[int]:
708 """
709 Returns all tank indices.
710
711 Returns
712 -------
713 `list[int]`
714 List of all tank indices.
715 """
716 r = []
717
718 for i in range(self.getcount(EpanetConstants.EN_NODECOUNT)):
719 if self.getnodetype(i + 1) == EpanetConstants.EN_TANK:
720 r.append(i + 1)
721
722 return r
723
[docs]
724 def get_all_reservoirs_id(self) -> list[str]:
725 """
726 Returns all reservoir IDs.
727
728 Returns
729 -------
730 `list[str]`
731 List of all reservoir IDs.
732 """
733 r = []
734
735 for i in range(self.getcount(EpanetConstants.EN_NODECOUNT)):
736 if self.getnodetype(i + 1) == EpanetConstants.EN_RESERVOIR:
737 r.append(self.getnodeid(i + 1))
738
739 return r
740
[docs]
741 def get_num_reservoirs(self) -> int:
742 """
743 Returns the number of reservoirs in the network.
744
745 Returns
746 -------
747 `int`
748 Number of reservoirs.
749 """
750 return len(self.get_all_reservoirs_idx())
751
[docs]
752 def get_all_reservoirs_idx(self) -> list[int]:
753 """
754 Returns all reservoir indices.
755
756 Returns
757 -------
758 `list[int]`
759 List of all reservoir indices.
760 """
761 r = []
762
763 for i in range(self.getcount(EpanetConstants.EN_NODECOUNT)):
764 if self.getnodetype(i + 1) == EpanetConstants.EN_RESERVOIR:
765 r.append(i + 1)
766
767 return r
768
[docs]
769 def get_node_idx(self, node_id: str) -> int:
770 """
771 Returns the index of a given node.
772
773 Parameters
774 ----------
775 node_id : `str`
776 ID of the node.
777
778 Returns
779 -------
780 `int`
781 Index of the node.
782 """
783 return self.getnodeindex(node_id)
784
[docs]
785 def get_link_idx(self, link_id: str) -> int:
786 """
787 Returns the index of a given link.
788
789 Parameters
790 ----------
791 link_id : `str`
792 ID of the link.
793
794 Returns
795 -------
796 `int`
797 Index of the link.
798 """
799 return self.getlinkindex(link_id)
800
[docs]
801 def get_node_id(self, node_idx) -> str:
802 """
803 Returns the ID of a given node.
804
805 Parameters
806 ----------
807 node_idx : `int`
808 Index of the node.
809
810 Returns
811 -------
812 `str`
813 ID of the node.
814 """
815 return self.getnodeid(node_idx)
816
[docs]
817 def get_link_id(self, link_idx) -> str:
818 """
819 Returns the ID of a given link.
820
821 Parameters
822 ----------
823 link_idx : `int`
824 Index of the link.
825
826 Returns
827 -------
828 `str`
829 ID of the link.
830 """
831 return self.getlinkid(link_idx)
832
[docs]
833 def get_node_type(self, node_idx: int) -> int:
834 """
835 Returns the type of a given node.
836
837 Parameters
838 ----------
839 node_idx : `int`
840 Index of the node.
841
842 Returns
843 -------
844 `int`
845 Type of the node. Will be one of the following:
846
847 - EN_JUNCTION
848 - EN_TANK
849 - EN_RESERVOIR
850 """
851 return self.getnodetype(node_idx)
852
[docs]
853 def get_link_type(self, link_idx: int) -> int:
854 """
855 Returns the type of a given link.
856
857 Parameters
858 ----------
859 link_idx : `int`
860 Index of the link.
861
862 Returns
863 -------
864 `int`
865 Type of the link. Will be one of the following:
866
867 - EN_CVPIPE
868 - EN_PIPE
869 - EN_PUMP
870 - EN_PRV
871 - EN_PSV
872 - EN_PBV
873 - EN_FCV
874 - EN_TCV
875 - EN_GPV
876 """
877 return self.getlinktype(link_idx)
878
[docs]
879 def get_curve(self, curve_id: str) -> list[tuple[float, float]]:
880 """
881 Returns the values/points of a given curve.
882
883 Parameters
884 ----------
885 curve_id : `str`
886 ID of the curve.
887
888 Returns
889 -------
890 `list[tuple[float, float]]`
891 List of all values/points of the curve.
892 """
893 r = []
894
895 curve_idx = self.getcurveindex(curve_id)
896 for i in range(self.getcurvelen(curve_idx)):
897 x, y = self.getcurvevalue(curve_idx, i+1)
898 r.append((x, y))
899
900 return r
901
[docs]
902 def add_curve(self, curve_id: str, values: list[tuple[float, float]]) -> None:
903 """
904 Adds a new curve -- e.g., a head curve for a pump or a volume curve
905 for a (non-cylindric) tank.
906
907 Parameters
908 ----------
909 curve_id : `str`
910 ID of the curve.
911 values : `list[tuple[float, float]]`
912 Curve values/points.
913 """
914 self.addcurve(curve_id)
915 curve_idx = self.getcurveindex(curve_id)
916 for i, (x, y) in enumerate(values):
917 self.setcurvevalue(curve_idx, i+1, x, y)
918
[docs]
919 def remove_curve(self, curve_id: str) -> None:
920 """
921 Deletes a given curve.
922
923 Parameters
924 ----------
925 curve_id : `str`
926 ID of the curve.
927 """
928 curve_idx = self.getcurveindex(curve_id)
929 self.deletecurve(curve_idx)
930
[docs]
931 def get_quality_info(self) -> dict:
932 """
933 Returns the water quality analysis parameters.
934
935 Returns
936 -------
937 `dict`
938 Water quality analysis information as a dictionary with the following entries:
939
940 - 'qualType': type of quality analysis (EN_NONE, EN_CHEM, EN_AGE, or EN_TRACE);
941 - 'chemName': name of chemical constituent;
942 - 'chemUnits': concentration units of constituent;
943 - 'traceNode': ID of node being traced (if applicable,
944 only if 'qualType' = EN_TRACE);
945 """
946 r = dict(zip(["qualType", "chemName", "chemUnits", "traceNode"], self.getqualinfo()))
947 if r["qualType"] == EpanetConstants.EN_AGE:
948 r["chemUnits"] = "hrs"
949
950 if r["qualType"] == EpanetConstants.EN_TRACE:
951 r["traceNode"] = self.get_node_id(r["traceNode"])
952 else:
953 r["traceNode"] = ""
954
955 return r
956
[docs]
957 def get_quality_type(self) -> dict:
958 """
959 Returns the type of quality analysis.
960
961 Returns
962 -------
963 `dict`
964 Dictioanry containing the type of quality analysis and the
965 ID of the node being traced (if applicable):
966
967 - 'qualType': type of quality analysis (EN_NONE, EN_CHEM, EN_AGE, or EN_TRACE);
968 - 'traceNode': ID of node being traced (if applicable, only if 'qualType' = EN_TRACE);
969 """
970 r = dict(zip(["qualType", "traceNode"], self.getqualtype()))
971 if r["qualType"] == EpanetConstants.EN_TRACE:
972 r["traceNode"] = self.get_node_id(r["traceNode"])
973 else:
974 r["traceNode"] = ""
975
976 return r
977
[docs]
978 def set_quality_type(self, qual_code: int, chem_name: str, chem_units: str,
979 tracenode_id: str) -> None:
980 """
981 Specifies the water quality analysis parameters.
982
983 Parameters
984 ----------
985 qual_code : `int`
986 Type of quality analysis. Must be one of the following:
987
988 - EN_NONE
989 - EN_CHEM
990 - EN_AGE
991 - EN_TRACE
992 chem_name : `str`
993 Name of chemical constituent
994 chem_units : `str`
995 Concentration units of constituent
996 tracenode_id : `str`
997 ID of node being traced (if applicable, only if 'qualType' = EN_TRACE).
998 """
999 self.setqualtype(qual_code, chem_name, chem_units, tracenode_id)
1000
[docs]
1001 def get_num_controls(self) -> int:
1002 """
1003 Returns the number of controls.
1004
1005 Returns
1006 -------
1007 `int`
1008 Number of controls.
1009 """
1010 return self.getcount(EpanetConstants.EN_CONTROLCOUNT)
1011
[docs]
1012 def add_control(self, control_type: int, link_index: int, setting: float, node_index: int,
1013 level: float) -> None:
1014 """
1015 Adds a control.
1016
1017 Parameters
1018 ----------
1019 control_type : `int`
1020 Type of control. Must be one of the following:
1021
1022 - EN_LOWLEVEL
1023 - EN_HILEVEL
1024 - EN_TIMER
1025 - EN_TIMEOFDAY
1026 link_index : `int`
1027 Index of the link (i.e., valve or pump) that is being controlled.
1028 setting : `float`
1029 Link control setting (e.g., pump speed).
1030 node_index : `int`
1031 Index of the node that is controlling the link.
1032 level : `float`
1033 Control activation level -- pressure for junction nodes, water level for tank nodes
1034 or time value for time-based control.
1035 """
1036 self.addcontrol(control_type, link_index, setting, node_index, level)
1037
[docs]
1038 def remove_all_controls(self) -> None:
1039 """
1040 Removes all controls.
1041 """
1042 while self.getcount(EpanetConstants.EN_CONTROLCOUNT) > 0:
1043 self.deletecontrol(1)
1044
[docs]
1045 def get_num_rules(self) -> int:
1046 """
1047 Returns the numer of rules.
1048
1049 Returns
1050 -------
1051 `int`
1052 Number of rules.
1053 """
1054 return self.getcount(EpanetConstants.EN_RULECOUNT)
1055
[docs]
1056 def get_all_rules_id(self) -> list[str]:
1057 """
1058 Returns the IDs of all rules.
1059
1060 Returns
1061 -------
1062 `list[str]`
1063 List of rule IDs -- ordered by their index.
1064 """
1065 return [self.getruleid(i + 1) for i in range(self.getcount(EpanetConstants.EN_RULECOUNT))]
1066
[docs]
1067 def remove_all_rules(self) -> None:
1068 """
1069 Removes all rules.
1070 """
1071 while self.getcount(EpanetConstants.EN_RULECOUNT) > 0:
1072 self.deleterule(1)
1073
[docs]
1074 def get_hydraulic_time_step(self) -> int:
1075 """
1076 Returns the hydraulic time step in seconds.
1077
1078 Returns
1079 -------
1080 `int`
1081 Hydraulic time step.
1082 """
1083 return self.gettimeparam(EpanetConstants.EN_HYDSTEP)
1084
[docs]
1085 def set_hydraulic_time_step(self, time_step: int) -> None:
1086 """
1087 Specifies the hydraulic time step.
1088
1089 Parameters
1090 ----------
1091 time_step : `int`
1092 Hydraulic time step in seconds.
1093 """
1094 self.settimeparam(EpanetConstants.EN_HYDSTEP, time_step)
1095
[docs]
1096 def get_pattern_time_step(self) -> int:
1097 """
1098 Returns the pattern time step in seconds.
1099
1100 Returns
1101 -------
1102 `int`
1103 Pattern time step.
1104 """
1105 return self.gettimeparam(EpanetConstants.EN_PATTERNSTEP)
1106
[docs]
1107 def set_pattern_time_step(self, time_step: int) -> None:
1108 """
1109 Specifies the pattern time step.
1110
1111 Parameters
1112 ----------
1113 time_step : `int`
1114 Pattern time step in seconds.
1115 """
1116 self.settimeparam(EpanetConstants.EN_PATTERNSTEP, time_step)
1117
[docs]
1118 def get_pattern_start_time(self) -> int:
1119 """
1120 Returns the pattern offset in seconds, which will start to be applied at simulation start.
1121
1122 Returns
1123 -------
1124 `int`
1125 Pattern offset time.
1126 """
1127 return self.gettimeparam(EpanetConstants.EN_PATTERNSTART)
1128
[docs]
1129 def set_pattern_start_time(self, start_time: int) -> None:
1130 """
1131 Specifies the pattern offset in seconds, which will start to be applied at simulation start.
1132
1133 Parameters
1134 ----------
1135 start_time : `int`
1136 Pattern offset time step in seconds since simulation start.
1137 """
1138 self.settimeparam(EpanetConstants.EN_PATTERNSTART, start_time)
1139
[docs]
1140 def get_quality_time_step(self) -> int:
1141 """
1142 Returns the quality time step in seconds.
1143
1144 Returns
1145 -------
1146 `int`
1147 Quality time step.
1148 """
1149 return self.gettimeparam(EpanetConstants.EN_QUALSTEP)
1150
[docs]
1151 def set_quality_time_step(self, time_step: int) -> None:
1152 """
1153 Specifies the water quality time step.
1154
1155 Parameters
1156 ----------
1157 time_step : `int`
1158 Water quality time step in seconds.
1159 """
1160 self.settimeparam(EpanetConstants.EN_QUALSTEP, time_step)
1161
[docs]
1162 def get_reporting_time_step(self) -> int:
1163 """
1164 Returns the reporting time step in seconds.
1165
1166 Returns
1167 -------
1168 `int`
1169 Reporting time step.
1170 """
1171 return self.gettimeparam(EpanetConstants.EN_REPORTSTEP)
1172
[docs]
1173 def set_reporting_time_step(self, time_step: int) -> None:
1174 """
1175 Specifies the reporting time step.
1176
1177 Parameters
1178 ----------
1179 time_step : `int`
1180 Reporting time step in seconds.
1181 """
1182 self.settimeparam(EpanetConstants.EN_REPORTSTEP, time_step)
1183
[docs]
1184 def get_reporting_start_time(self) -> int:
1185 """
1186 Returns the reporting start time in seconds since the simulation start.
1187
1188 Returns
1189 -------
1190 `int`
1191 Reporting start time.
1192 """
1193 return self.gettimeparam(EpanetConstants.EN_REPORTSTART)
1194
[docs]
1195 def set_reporting_start_time(self, start_time: int) -> None:
1196 """
1197 Specifies the start time of reporting.
1198
1199 Parameters
1200 ----------
1201 start_time : `int`
1202 Reporting start time step in seconds since simulation start.
1203 """
1204 self.settimeparam(EpanetConstants.EN_REPORTSTART, start_time)
1205
[docs]
1206 def get_simulation_duration(self) -> int:
1207 """
1208 Returns the simulation duration in seconds.
1209
1210 Returns
1211 -------
1212 `int`
1213 Simulation duration.
1214 """
1215 return self.gettimeparam(EpanetConstants.EN_DURATION)
1216
[docs]
1217 def set_simulation_duration(self, duration: int) -> None:
1218 """
1219 Sets the simulation duration.
1220
1221 Parameters
1222 ----------
1223 duration : `int`
1224 Simulation duration in seconds.
1225 """
1226 self.settimeparam(EpanetConstants.EN_DURATION, duration)
1227
[docs]
1228 def get_demand_model(self) -> dict:
1229 """
1230 Returns the specifications of the demand model.
1231
1232 Returns
1233 -------
1234 `dict`
1235 Dictionary contains the specifications of the demand model:
1236
1237 - 'type': type of demand model (either EN_DDA or EN_PDA);
1238 - 'pmin': minimum pressure for any demand;
1239 - 'preq': required pressure for full demand;
1240 - 'pexp': exponent in pressure dependent demand formula;
1241 """
1242 return dict(zip(["type", "pmin", "preq", "pexp"], self.getdemandmodel()))
1243
[docs]
1244 def set_demand_model(self, model_type: int, pmin: float, preq: float, pexp: float) -> None:
1245 """
1246 Specifies the demand model.
1247
1248 Parameters
1249 ----------
1250 model_type : `int`
1251 Type of demand model. Must be one of the following:
1252
1253 - EN_DDA
1254 - EN_PDA
1255 pmin : `float`
1256 Minimum pressure for any demand.
1257 preq : `float`
1258 Required pressure for full demand.
1259 pexp : `float`
1260 Exponent in pressure dependent demand formula.
1261 """
1262 self.setdemandmodel(model_type, pmin, preq, pexp)
1263
[docs]
1264 def get_link_diameter(self, link_idx: int) -> float:
1265 """
1266 Returns the diameter of a given link.
1267
1268 Parameters
1269 ----------
1270 link_idx : `int`
1271 Index of the link.
1272
1273 Returns
1274 -------
1275 `float`
1276 Diameter of the link.
1277 """
1278 return self.getlinkvalue(link_idx, EpanetConstants.EN_DIAMETER)
1279
[docs]
1280 def get_link_length(self, link_idx: int) -> float:
1281 """
1282 Returns the length of a given link.
1283
1284 Parameters
1285 ----------
1286 link_idx : `int`
1287 Index of the link.
1288
1289 Returns
1290 -------
1291 `float`
1292 Length of the link.
1293 """
1294 return self.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH)
1295
[docs]
1296 def get_link_roughness(self, link_idx: int) -> dict:
1297 """
1298 Returns the roughness coefficient of a given link.
1299
1300 Parameters
1301 ----------
1302 link_idx : `int`
1303 Index of the link.
1304
1305 Returns
1306 -------
1307 `float`
1308 Roughness coefficient of the link.
1309 """
1310 return self.getlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS)
1311
[docs]
1312 def get_link_minorloss(self, link_idx: int) -> float:
1313 """
1314 Returns the minor loss coefficient of a given link.
1315
1316 Parameters
1317 ----------
1318 link_idx : `int`
1319 Index of the link.
1320
1321 Returns
1322 -------
1323 `float`
1324 Minor loss coefficient of the link.
1325 """
1326 return self.getlinkvalue(link_idx, EpanetConstants.EN_MINORLOSS)
1327
[docs]
1328 def get_link_init_status(self, link_idx: int) -> int:
1329 """
1330 Returns the initial status (open or closed) of a given link.
1331
1332 Parameters
1333 ----------
1334 link_idx : `int`
1335 Index of the link.
1336
1337 Returns
1338 -------
1339 `int`
1340 Initial status of the link. Will be one of the following:
1341
1342 - EN_CLOSED
1343 - EN_OPEN
1344 """
1345 return int(self.getlinkvalue(link_idx, EpanetConstants.EN_INITSTATUS))
1346
[docs]
1347 def get_link_init_setting(self, link_idx: int) -> float:
1348 """
1349 Returns the initial setting of a given link.
1350
1351 Parameters
1352 ----------
1353 link_idx : `int`
1354 Index of the link.
1355
1356 Returns
1357 -------
1358 `float`
1359 Initial setting.
1360 """
1361 return self.getlinkvalue(link_idx, EpanetConstants.EN_INITSETTING)
1362
[docs]
1363 def get_link_bulk_decay(self, link_idx: int) -> float:
1364 """
1365 Returns the bulk decay rate at a given link.
1366
1367 Parameters
1368 ----------
1369 link_idx : `int`
1370 Index of the link.
1371
1372 Returns
1373 -------
1374 `float`
1375 Bulk decay rate.
1376 """
1377 return self.getlinkvalue(link_idx, EpanetConstants.EN_KBULK)
1378
[docs]
1379 def get_link_wall_decay(self, link_idx: int) -> float:
1380 """
1381 Returns the wall decay rate at a given link.
1382
1383 Parameters
1384 ----------
1385 link_idx : `int`
1386 Index of the link.
1387
1388 Returns
1389 -------
1390 `float`
1391 Wall decay rate.
1392 """
1393 return self.getlinkvalue(link_idx, EpanetConstants.EN_KWALL)
1394
1410
[docs]
1411 def get_node_elevation(self, node_idx: int) -> float:
1412 """
1413 Returns the evelvation of a given node.
1414
1415 Parameters
1416 ----------
1417 node_idx : `int`
1418 Index of the node.
1419
1420 Returns
1421 -------
1422 `float`
1423 Elevation.
1424 """
1425 return self.getnodevalue(node_idx, EpanetConstants.EN_ELEVATION)
1426
[docs]
1427 def get_node_emitter_coeff(self, node_idx: int) -> float:
1428 """
1429 Returns the roughness coefficient of a given node.
1430
1431 Parameters
1432 ----------
1433 node_idx : `int`
1434 Index of the node.
1435
1436 Returns
1437 -------
1438 `float`
1439 Emitter coefficient of the node.
1440 """
1441 return self.getnodevalue(node_idx, EpanetConstants.EN_EMITTER)
1442
[docs]
1443 def get_node_init_qual(self, node_idx: int) -> float:
1444 """
1445 Returns the initial quality value/state of a given node.
1446
1447 Parameters
1448 ----------
1449 node_idx : `int`
1450 Index of the node.
1451
1452 Returns
1453 -------
1454 `float`
1455 Initial quality state/value.
1456 """
1457 return self.getnodevalue(node_idx, EpanetConstants.EN_INITQUAL)
1458
[docs]
1459 def get_node_source_qual(self, node_idx: int) -> float:
1460 """
1461 Returns the current quality state/value at a given node.
1462
1463 Parameters
1464 ----------
1465 node_idx : `int`
1466 Index of the node.
1467
1468 Returns
1469 -------
1470 `float`
1471 Current quality state/value.
1472 """
1473 return self.getnodevalue(node_idx, EpanetConstants.EN_SOURCEQUAL)
1474
[docs]
1475 def get_node_source_type(self, node_idx: int) -> int:
1476 """
1477 Returns the type of the water quality source at a given node.
1478
1479 Parameters
1480 ----------
1481 node_idx : `int`
1482 Index of the node.
1483
1484 Returns
1485 -------
1486 `int`
1487 Type of the water quality source. Will be one of the following:
1488
1489 - EN_CONCEN
1490 - EN_MASS
1491 - EN_SETPOINT
1492 - EN_FLOWPACED
1493 """
1494 return int(self.getnodevalue(node_idx, EpanetConstants.EN_SOURCETYPE))
1495
[docs]
1496 def get_node_source_pattern_idx(self, node_idx: int) -> int:
1497 """
1498 Returns the index of the quality source pattern at a given node.
1499
1500 Parameters
1501 ----------
1502 node_idx : `int`
1503 Index of the node.
1504
1505 Returns
1506 -------
1507 `int`
1508 Index of the pattern.
1509 """
1510 return int(self.getnodevalue(node_idx, EpanetConstants.EN_SOURCEPAT))
1511
[docs]
1512 def get_node_pattern_idx(self, node_idx: int) -> float:
1513 """
1514 Returns the index of the primary demand pattern of a given node.
1515
1516 Parameters
1517 ----------
1518 node_idx : `int`
1519 Index of the node.
1520
1521 Returns
1522 -------
1523 `int`
1524 Index of the primary demand pattern.
1525 """
1526 return int(self.getnodevalue(node_idx, EpanetConstants.EN_PATTERN))
1527
[docs]
1528 def get_node_base_demand(self, node_idx: int) -> float:
1529 """
1530 Returns the primary base demand of a given node.
1531
1532 Parameters
1533 ----------
1534 node_idx : `int`
1535 Index of the node.
1536
1537 Returns
1538 -------
1539 `int`
1540 Primary base demand.
1541 """
1542 return self.getnodevalue(node_idx, EpanetConstants.EN_BASEDEMAND)
1543
[docs]
1544 def get_node_base_demands(self, node_idx: int) -> list[float]:
1545 """
1546 Returns all base demands of a given node.
1547
1548 Parameters
1549 ----------
1550 node_idx : `int`
1551 Index of the node.
1552
1553 Returns
1554 -------
1555 `list[int]`
1556 List of base demands.
1557 """
1558 r = []
1559
1560 for i in range(self.getnumdemands(node_idx)):
1561 r.append(self.getbasedemand(node_idx, i + 1))
1562
1563 return r
1564
[docs]
1565 def get_node_demand_patterns_idx(self, node_idx: int) -> list[int]:
1566 """
1567 Returns the index of all demand patterns of a given node.
1568
1569 Parameters
1570 ----------
1571 node_idx : `int`
1572 Index of the node.
1573
1574 Returns
1575 -------
1576 `list[int]`
1577 List of indices of the demand patterns.
1578 """
1579 r = []
1580
1581 for i in range(self.getnumdemands(node_idx)):
1582 r.append(self.getdemandpattern(node_idx, i + 1))
1583
1584 return r
1585
[docs]
1586 def get_tank_init_vol(self, tank_idx) -> float:
1587 """
1588 Return the inital water volume in a given tank.
1589
1590 Parameters
1591 ----------
1592 tank_idx : `int`
1593 Index of the tank.
1594
1595 Returns
1596 -------
1597 `float`
1598 Initial water volume.
1599 """
1600 return self.getnodevalue(tank_idx, EpanetConstants.EN_INITVOLUME)
1601
[docs]
1602 def get_tank_level(self, tank_idx: int) -> float:
1603 """
1604 Returns the current water level in a given tank.
1605
1606 Parameters
1607 ----------
1608 tank_idx : `int`
1609 Index of the tank.
1610
1611 Returns
1612 -------
1613 `float`
1614 Current water level.
1615 """
1616 return self.getnodevalue(tank_idx, EpanetConstants.EN_TANKLEVEL)
1617
[docs]
1618 def get_tank_volume(self, tank_idx: int) -> float:
1619 """
1620 Returns the current water volume inside a given tank.
1621
1622 Parameters
1623 ----------
1624 tank_idx : `int`
1625 Index of the tank.
1626
1627 Returns
1628 -------
1629 `float`
1630 Current water volume.
1631 """
1632 return self.getnodevalue(tank_idx, EpanetConstants.EN_TANKVOLUME)
1633
[docs]
1634 def get_tank_mix_model(self, tank_idx: int) -> int:
1635 """
1636 Returns the mixing model of a given tank.
1637
1638 Parameters
1639 ----------
1640 tank_idx : `int`
1641 Index of the tank.
1642
1643 Returns
1644 -------
1645 `int`
1646 Type of mixing model. Will be one of the following:
1647
1648 - EN_MIX1
1649 - EN_MIX2
1650 - EN_FIFO
1651 - EN_LIFO
1652 """
1653 return int(self.getnodevalue(tank_idx, EpanetConstants.EN_MIXMODEL))
1654
[docs]
1655 def get_tank_mix_zone_vol(self, tank_idx: int) -> float:
1656 """
1657 Returns the mixing zone volume of a given tank.
1658
1659 Parameters
1660 ----------
1661 tank_idx : `int`
1662 Index of the tank.
1663
1664 Returns
1665 -------
1666 `float`
1667 Tank mixing zone valume.
1668 """
1669 return self.getnodevalue(tank_idx, EpanetConstants.EN_MIXZONEVOL)
1670
[docs]
1671 def get_tank_diameter(self, tank_idx: int) -> float:
1672 """
1673 Return the diameter of given tank.
1674
1675 Parameters
1676 ----------
1677 tank_idx : `int`
1678 Index of the tank.
1679
1680 Returns
1681 -------
1682 `float`
1683 Diameter of the tank.
1684 """
1685 return self.getnodevalue(tank_idx, EpanetConstants.EN_TANKDIAM)
1686
[docs]
1687 def get_tank_min_vol(self, tank_idx: int) -> float:
1688 """
1689 Return the minimum volume of given tank.
1690
1691 Parameters
1692 ----------
1693 tank_idx : `int`
1694 Index of the tank.
1695
1696 Returns
1697 -------
1698 `float`
1699 Minimum volume.
1700 """
1701 return self.getnodevalue(tank_idx, EpanetConstants.EN_MINVOLUME)
1702
[docs]
1703 def get_tank_max_vol(self, tank_idx: int) -> float:
1704 """
1705 Return the maxmium volume of given tank.
1706
1707 Parameters
1708 ----------
1709 tank_idx : `int`
1710 Index of the tank.
1711
1712 Returns
1713 -------
1714 `float`
1715 Maximum volume.
1716 """
1717 return self.getnodevalue(tank_idx, EpanetConstants.EN_MAXVOLUME)
1718
[docs]
1719 def get_tank_vol_curve_idx(self, tank_idx: int) -> int:
1720 """
1721 Returns the index of the volume curve of a given tank.
1722
1723 Parameters
1724 ----------
1725 tank_idx : `int`
1726 Index of the tank.
1727
1728 Returns
1729 -------
1730 `int`
1731 Index of the volume curve.
1732 """
1733 return int(self.getnodevalue(tank_idx, EpanetConstants.EN_VOLCURVE))
1734
[docs]
1735 def get_tank_min_level(self, tank_idx: int) -> float:
1736 """
1737 Returns the minimum water level of a given tank.
1738
1739 Parameters
1740 ----------
1741 tank_idx : `int`
1742 Index of the tank.
1743
1744 Returns
1745 -------
1746 `float`
1747 Minimum water level.
1748 """
1749 return self.getnodevalue(tank_idx, EpanetConstants.EN_MINLEVEL)
1750
[docs]
1751 def get_tank_max_level(self, tank_idx: int) -> float:
1752 """
1753 Returns the maximum water level of a given tank.
1754
1755 Parameters
1756 ----------
1757 tank_idx : `int`
1758 Index of the tank.
1759
1760 Returns
1761 -------
1762 `float`
1763 Maximum water level.
1764 """
1765 return self.getnodevalue(tank_idx, EpanetConstants.EN_MAXLEVEL)
1766
[docs]
1767 def get_tank_mix_fraction(self, tank_idx: int) -> float:
1768 """
1769 Returns the mixing fraction of a given tank.
1770
1771 Parameters
1772 ----------
1773 tank_idx : `int`
1774 Index of the tank.
1775
1776 Returns
1777 -------
1778 `float`
1779 Mixing fraction.
1780 """
1781 return self.getnodevalue(tank_idx, EpanetConstants.EN_MIXFRACTION)
1782
[docs]
1783 def get_tank_bulk_decacy(self, tank_idx: int) -> float:
1784 """
1785 Returns the bulk decay rate in a given tank.
1786
1787 Parameters
1788 ----------
1789 tank_idx : `int`
1790 Index of the tank.
1791
1792 Returns
1793 -------
1794 `float`
1795 Bulk decay rate.
1796 """
1797 return self.getnodevalue(tank_idx, EpanetConstants.EN_TANK_KBULK)
1798
[docs]
1799 def can_tank_overflow(self, tank_idx: int) -> bool:
1800 """
1801 Checks if a given tank can overflow or not.
1802
1803 Parameters
1804 ----------
1805 tank_idx : `int`
1806 Index of the tank.
1807
1808 Returns
1809 -------
1810 `bool`
1811 True if the tank can overflow, False otherwise.
1812 """
1813 return bool(self.getnodevalue(tank_idx, EpanetConstants.EN_CANOVERFLOW))
1814
[docs]
1815 def get_pump_type(self, pump_idx: int) -> int:
1816 """
1817 Returns the type (type of pump curve) of a given pump.
1818
1819 Parameters
1820 ----------
1821 pump_idx : `int`
1822 Index of the pump.
1823
1824 Returns
1825 -------
1826 `int`
1827 Pump curve type. Will be one of the following:
1828
1829 - EN_CONST_HP
1830 - EN_POWER_FUNC
1831 - EN_CUSTOM
1832 - EN_NOCURVE
1833 """
1834 return self.getpumptype(pump_idx)
1835
[docs]
1836 def get_pump_energy_price_pattern(self, pump_idx) -> int:
1837 """
1838 Returns the index of the energy price pattern of a given pump.
1839
1840 Parameters
1841 ----------
1842 pump_idx : `int`
1843 Index of the pump.
1844
1845 Returns
1846 -------
1847 `int`
1848 Pattern index.
1849 """
1850 return int(self.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_EPAT))
1851
[docs]
1852 def set_pump_energy_price_pattern(self, pump_idx: int, pattern_idx: int) -> None:
1853 """
1854 Sets the energy price pattern of a given pump.
1855
1856 Parameters
1857 ----------
1858 pump_idx : `int`
1859 Index of the pump.
1860 pattern_idx : `int`
1861 Index of the pattern.
1862 """
1863 self.setlinkvalue(pump_idx, EpanetConstants.EN_PUMP_EPAT, pattern_idx)
1864
[docs]
1865 def get_all_patterns_id(self) -> list[str]:
1866 """
1867 Returns a list of all pattern IDs.
1868
1869 Returns
1870 -------
1871 `list[str]`
1872 List of IDs.
1873 """
1874 r = []
1875
1876 for idx in range(1, self.getcount(EpanetConstants.EN_PATCOUNT) + 1):
1877 r.append(self.getpatternid(idx))
1878
1879 return r
1880
[docs]
1881 def get_pattern(self, pattern_idx: int) -> list[float]:
1882 """
1883 Returns the values of a given pattern.
1884
1885 Parameters
1886 ----------
1887 pattern_idx : `int`
1888 Index of the pattern.
1889
1890 Returns
1891 -------
1892 `list[float]`
1893 Pattern values.
1894 """
1895 r = []
1896
1897 for t in range(self.getpatternlen(pattern_idx)):
1898 r.append(self.getpatternvalue(pattern_idx, t + 1))
1899
1900 return r
1901
[docs]
1902 def set_pattern(self, pattern_idx: int, pattern_values: list[float]) -> None:
1903 """
1904 Set the values of a given pattern.
1905
1906 Parameters
1907 ----------
1908 pattern_idx : `int`
1909 Index of the pattern.
1910 pattern_values : `list[float]`
1911 New pattern values.
1912 """
1913 self.setpattern(pattern_idx, pattern_values, len(pattern_values))
1914
[docs]
1915 def add_pattern(self, pattern_id: str, pattern_values: list[float]) -> None:
1916 """
1917 Adds a new pattern.
1918
1919 Parameters
1920 ----------
1921 pattern_id : `str`
1922 ID of the pattern.
1923 pattern_values : `list[float]`
1924 Pattern values.
1925 """
1926 self.addpattern(pattern_id)
1927
1928 idx = self.getpatternindex(pattern_id)
1929 self.setpattern(idx, pattern_values, len(pattern_values))
1930
[docs]
1931 def get_all_links_connecting_nodes_id(self) -> list[tuple[str]]:
1932 """
1933 Returns a list of all connecting node IDs for each link in the network.
1934
1935 Returns
1936 -------
1937 `list[tuple[str]]`
1938 List of tuple of connecting node IDs of all links.
1939 """
1940 r = []
1941
1942 for link_idx in range(self.getcount(EpanetConstants.EN_LINKCOUNT)):
1943 node1_idx, node2_idx = self.getlinknodes(link_idx + 1)
1944 r.append((self.get_node_id(node1_idx), self.get_node_id(node2_idx)))
1945
1946 return r
1947
[docs]
1948 def get_pump_avg_energy_price(self, pump_idx: int) -> float:
1949 """
1950 Returns the average energy price of a given pump.
1951
1952 Parameters
1953 ----------
1954 pump_idx : `int`
1955 Index of the pump
1956
1957 Returns
1958 -------
1959 `float`
1960 Average energy price.
1961 """
1962 return self.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_ECOST)
1963
[docs]
1964 def set_pump_avg_energy_price(self, pump_idx: int, price: float) -> float:
1965 """
1966 Specifies the average energy price of a given pump.
1967
1968 Parameters
1969 ----------
1970 pump_idx : `int`
1971 Index of the pump
1972 price : `float`
1973 Average energy price.
1974 """
1975 self.setlinkvalue(pump_idx, EpanetConstants.EN_PUMP_ECOST, price)
1976
[docs]
1977 def get_pump_pattern(self, pump_idx: int) -> int:
1978 """
1979 Returns the pattern of a given pump.
1980
1981 Parameters
1982 ----------
1983 pump_idx : `int`
1984 Index of the pump
1985
1986 Returns
1987 -------
1988 `int`
1989 Index of the pump pattern.
1990 """
1991 return int(self.getlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN))
1992
[docs]
1993 def set_pump_pattern(self, pump_idx: int, pattern_idx: int) -> None:
1994 """
1995 Specifies the pattern of a given pump.
1996
1997 Parameters
1998 ----------
1999 pump_idx : `int`
2000 Index of the pump
2001 pattern_idx : `int`
2002 Index of the pattern.
2003 """
2004 self.setlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN, pattern_idx)
2005
[docs]
2006 def set_node_data(self, node_idx: int, elev: float, base_demand: float,
2007 demand_pattern_id: str) -> None:
2008 """
2009 Specifies some properties, such as elevation and demand, of a given node.
2010
2011 Parameters
2012 ----------
2013 node_idx : `int`
2014 Index of the node.
2015 elev : `float`
2016 Elevation of the node.
2017 base_demand : `float`
2018 Base demand of the node.
2019 demand_pattern_id : `str`
2020 ID of the primary demand pattern of the node.
2021 """
2022 self.setjuncdata(node_idx, elev, base_demand, demand_pattern_id)
2023
[docs]
2024 def set_node_source(self, node_idx: int, source_type: int, source_strengh: float,
2025 pattern_idx: int) -> None:
2026 """
2027 Specifies the quality source of a given node.
2028
2029 Parameters
2030 ----------
2031 node_idx : `int`
2032 Index of the node.
2033 source_type : `int`
2034 Type of the source. Must be one of the following:
2035
2036 - EN_CONCEN
2037 - EN_MASS
2038 - EN_SETPOINT
2039 - EN_FLOWPACED
2040 source_strength : `float`
2041 Source strength.
2042 pattern_idx : `int`
2043 Index of the source pattern.
2044 """
2045 self.setnodevalue(node_idx, EpanetConstants.EN_SOURCETYPE, source_type)
2046 self.setnodevalue(node_idx, EpanetConstants.EN_SOURCEQUAL, source_strengh)
2047
2048 if pattern_idx is not None:
2049 self.setnodevalue(node_idx, EpanetConstants.EN_SOURCEPAT, pattern_idx)
2050
[docs]
2051 def set_node_source_quality(self, node_idx, source_strength: float) -> None:
2052 """
2053 Specifies the strength of a node quality source.
2054
2055 Parameters
2056 ----------
2057 node_idx : `int`
2058 Index of the node.
2059 source_strength : `float`
2060 Source strength.
2061 """
2062 self.setnodevalue(node_idx, EpanetConstants.EN_SOURCEQUAL, source_strength)
2063
[docs]
2064 def get_node_init_quality(self, node_idx: int) -> float:
2065 """
2066 Returns the initial quality (e.g., concentration) of a given node.
2067
2068 Parameters
2069 ----------
2070 node_idx : `int`
2071 Index of the node.
2072
2073 Returns
2074 -------
2075 `float`
2076 Initial quality.
2077 """
2078 return self.getnodevalue(node_idx, EpanetConstants.EN_INITQUAL)
2079
[docs]
2080 def set_node_init_quality(self, node_idx: int, init_qual: float) -> None:
2081 """
2082 Specifies the initial quality (e.g., concentration) of a given node.
2083
2084 Parameters
2085 ----------
2086 node_idx : `int`
2087 Index of the node.
2088 init_qual : `float`
2089 Initial quality.
2090 """
2091 self.setnodevalue(node_idx, EpanetConstants.EN_INITQUAL, init_qual)
2092
[docs]
2093 def get_pipe_wall_reaction_order(self) -> int:
2094 """
2095 Returns the pipe wall reaction order.
2096
2097 Returns
2098 -------
2099 `int`
2100 Reaction oder.
2101 """
2102 return int(self.getoption(EpanetConstants.EN_WALLORDER))
2103
[docs]
2104 def get_pipe_bulk_reaction_order(self) -> int:
2105 """
2106 Returns the the pipe bulk reaction order.
2107
2108 Returns
2109 -------
2110 `int`
2111 Reaction order.
2112 """
2113 return int(self.getoption(EpanetConstants.EN_BULKORDER))
2114
[docs]
2115 def get_link_wall_reaction_coeff(self, link_idx: int) -> float:
2116 """
2117 Returns the wall reaction coefficient of a given link.
2118
2119 Parameters
2120 ----------
2121 link_idx : `int`
2122 Index of the link.
2123
2124 Returns
2125 -------
2126 `float`
2127 Wall reaction coefficient.
2128 """
2129 return self.getlinkvalue(link_idx, EpanetConstants.EN_KWALL)
2130
[docs]
2131 def get_link_bulk_reaction_coeff(self, link_idx: int) -> float:
2132 """
2133 Returns the bulk reaction coefficient of a link.
2134
2135 Parameters
2136 ----------
2137 link_idx : `int`
2138 Index of the link.
2139
2140 Returns
2141 -------
2142 `float`
2143 Bulk reaction coefficient.
2144 """
2145 return self.getlinkvalue(link_idx, EpanetConstants.EN_KBULK)
2146
[docs]
2147 def get_tank_bulk_reaction_coeff(self, tank_idx: int) -> float:
2148 """
2149 Returns the bulk reaction coefficient of a given tank.
2150
2151 Parameters
2152 ----------
2153 tank_idx : `int`
2154 Index of the tank.
2155
2156 Returns
2157 -------
2158 `float`
2159 Bulk reaction coefficient.
2160 """
2161 return self.getnodevalue(tank_idx, EpanetConstants.EN_TANK_KBULK)
2162
[docs]
2163 def get_limiting_concentration(self) -> float:
2164 """
2165 Returns the limiting concentration in reactions.
2166
2167 Returns
2168 -------
2169 `float`
2170 Limiting concentration.
2171 """
2172 return self.getoption(EpanetConstants.EN_CONCENLIMIT)
2173
[docs]
2174 def get_quality_tolerance(self) -> float:
2175 """
2176 Returns the water quality tolerance.
2177
2178 Returns
2179 -------
2180 `float`
2181 Water quality tolerance.
2182 """
2183 return self.getoption(EpanetConstants.EN_TOLERANCE)
2184
[docs]
2185 def get_specific_diffusivity(self) -> float:
2186 """
2187 Returns the specific diffusivity.
2188
2189 Returns
2190 -------
2191 `float`
2192 Specific diffusivity.
2193 """
2194 return self.getoption(EpanetConstants.EN_SP_DIFFUS)
2195
[docs]
2196 def get_specific_gravity(self) -> float:
2197 """
2198 Returns the specific gravity.
2199
2200 Returns
2201 -------
2202 `float`
2203 Specific gravity.
2204 """
2205 return self.getoption(EpanetConstants.EN_SP_GRAVITY)
2206
[docs]
2207 def get_specific_viscosity(self) -> float:
2208 """
2209 Returns the specific viscosity.
2210
2211 Returns
2212 -------
2213 `float`
2214 Specific viscosity.
2215 """
2216 return self.getoption(EpanetConstants.EN_SP_VISCOS)
2217
[docs]
2218 def get_tank_bulk_reaction_order(self) -> int:
2219 """
2220 Returns the bulk reaction order in tanks.
2221
2222 Returns
2223 -------
2224 `int`
2225 Bulk reaction order.
2226 """
2227 return int(self.getoption(EpanetConstants.EN_TANKORDER))
2228
[docs]
2229 def get_node_quality(self, node_idx: int) -> float:
2230 """
2231 Returns the current quality value at a given node.
2232
2233 Parameters
2234 ----------
2235 node_idx : `int`
2236 Index of the node.
2237
2238 Returns
2239 -------
2240 `float`
2241 Current node quality value.
2242 """
2243 return self.getnodevalue(node_idx, EpanetConstants.EN_QUALITY)
2244
[docs]
2245 def get_link_quality(self, link_idx: int) -> float:
2246 """
2247 Returns the current quality value (e.g., concentration, age, ...) at a given link.
2248
2249 Parameters
2250 ----------
2251 `link_idx`
2252 Index of the link.
2253
2254 Returns
2255 -------
2256 `float`
2257 Current link quality value.
2258 """
2259 return self.getlinkvalue(link_idx, EpanetConstants.EN_LINKQUAL)
2260
[docs]
2261 def get_node_pressure(self, node_idx: int) -> float:
2262 """
2263 Returns the current pressure at a given node.
2264
2265 Parameters
2266 ----------
2267 node_idx : `int`
2268 Index of the node.
2269
2270 Returns
2271 -------
2272 `float`
2273 Current pressure.
2274 """
2275 return self.getnodevalue(node_idx, EpanetConstants.EN_PRESSURE)
2276
[docs]
2277 def get_node_head(self, node_idx: int) -> float:
2278 """
2279 Returns the current hydraulic head at a given node.
2280
2281 Parameters
2282 ----------
2283 node_idx : `int`
2284 Index of the node.
2285
2286 Returns
2287 -------
2288 `float`
2289 Current hydraulic head.
2290 """
2291 return self.getnodevalue(node_idx, EpanetConstants.EN_HEAD)
2292
[docs]
2293 def get_node_demand(self, node_idx: int) -> float:
2294 """
2295 Returns the current demand at a given node.
2296
2297 Parameters
2298 ----------
2299 node_idx : `int`
2300 Index of the node.
2301
2302 Returns
2303 -------
2304 `float`
2305 Current demand.
2306 """
2307 return self.getnodevalue(node_idx, EpanetConstants.EN_DEMAND)
2308
[docs]
2309 def get_link_flow(self, link_idx: int) -> float:
2310 """
2311 Returns the current flow rate at a given link.
2312
2313 Parameters
2314 ----------
2315 `link_idx`
2316 Index of the link.
2317
2318 Returns
2319 -------
2320 `float`
2321 Current flow rate.
2322 """
2323 return self.getlinkvalue(link_idx, EpanetConstants.EN_FLOW)
2324
[docs]
2325 def get_pump_status(self, pump_idx: int) -> int:
2326 """
2327 Returns the current pump status.
2328
2329 Parameters
2330 ----------
2331 pump_idx : `int`
2332 Index of the pump.
2333
2334 Returns
2335 -------
2336 `int`
2337 Current pump status. Will be one of the following:
2338
2339 - EN_PUMP_XHEAD
2340 - EN_PUMP_CLOSED
2341 - EN_PUMP_OPEN
2342 - EN_PUMP_XFLOW
2343 """
2344 return int(self.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_STATE))
2345
[docs]
2346 def set_pump_status(self, pump_idx: int, status: int) -> None:
2347 """
2348 Sets the the current status of a given pump.
2349
2350 Parameters
2351 ----------
2352 pump_idx : `int`
2353 Index of the pump
2354 status : `int`
2355 Pump status. Must be one of the following:
2356
2357 - EN_CLOSED
2358 - EN_OPEN
2359 """
2360 self.setlinkvalue(pump_idx, EpanetConstants.EN_STATUS, status)
2361
[docs]
2362 def get_pump_energy_usage(self, pump_idx: int) -> float:
2363 """
2364 Returns the current energy usage of a given pump.
2365
2366 Parameters
2367 ----------
2368 pump_idx : `int`
2369 Index of the pump
2370
2371 Returns
2372 -------
2373 `float`
2374 Current energy usage.
2375 """
2376 return self.getlinkvalue(pump_idx, EpanetConstants.EN_ENERGY)
2377
[docs]
2378 def get_pump_efficiency(self, pump_idx: int) -> float:
2379 """
2380 Returns the current effciency of a given pump.
2381
2382 Parameters
2383 ----------
2384 pump_idx : `int`
2385 Index of the pump
2386
2387 Returns
2388 -------
2389 `float`
2390 Current pump effciency.
2391 """
2392 return self.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_EFFIC)
2393
[docs]
2394 def get_valve_status(self, valve_idx: int) -> int:
2395 """
2396 Returns the current status of a given valve.
2397
2398 Parameters
2399 ----------
2400 valve_idx : `int`
2401 Index of the valve.
2402
2403 Returns
2404 -------
2405 `int`
2406 Current status. Will be one of the following:
2407
2408 - EN_CLOSED
2409 - EN_OPEN
2410 """
2411 return int(self.getlinkvalue(valve_idx, EpanetConstants.EN_STATUS))
2412
[docs]
2413 def set_valve_status(self, valve_idx, status: int):
2414 """
2415 Sets the current status of a given valve.
2416
2417 Parameters
2418 ----------
2419 valve_idx : `int`
2420 Index of the valve.
2421 status : `int`
2422 New status of the valve. Must be one of the following:
2423
2424 - EN_CLOSED
2425 - EN_OPEN
2426 """
2427 self.setlinkvalue(valve_idx, EpanetConstants.EN_STATUS, status)
2428
[docs]
2429 def split_pipe(self, pipe_id: str, new_pipe_id: str, new_node_id: str) -> None:
2430 """
2431 Splits a pipe (pipeID), creating two new pipes (pipeID and newPipeID) and adds a
2432 junction/node (newNodeID) in between. If the pipe is linear
2433 the pipe is splitted in half, otherwisw the middle point of
2434 the vertice array elemnts is taken as the split point.
2435 The two new pipes have the same properties as the one which is splitted.
2436 The new node's properties are the same with the nodes on the left and right
2437 and New Node Elevation and Initial quality is the average of the two.
2438
2439 Note that this code is taken from EPyT -- slightly modified to fit into this toolkit.
2440
2441 Parameters
2442 ----------
2443 pipe_id : `str`
2444 ID of the pipe to be split.
2445 new_pipe_id : `str`
2446 ID of the new pipe.
2447 new_node_id : `str`
2448 ID of the new node, placed in the middle of the splitted pipe.
2449 """
2450 # Find the coordinates of the Nodes connected with the link/pipe
2451 pipeIndex = self.get_link_idx(pipe_id)
2452 nodesIndex = self.getlinknodes(pipeIndex)
2453 leftNodeIndex = nodesIndex[0]
2454 rightNodeIndex = nodesIndex[1]
2455 coordNode1 = self.getcoord(leftNodeIndex)
2456 coordNode2 = self.getcoord(rightNodeIndex)
2457
2458 if coordNode1[0] == 0 and coordNode1[1] == 0 \
2459 and coordNode2[0] == 0 and coordNode2[1] == 0:
2460 raise ValueError('Some nodes have zero values for coordinates')
2461
2462 if self.getvertexcount(pipeIndex) == 0:
2463 # Calculate mid position of the link/pipe based on nodes
2464 midX = (coordNode1[0] + coordNode2[0]) / 2
2465 midY = (coordNode1[1] + coordNode2[1]) / 2
2466 else:
2467 # Calculate mid position based on vertices pick midpoint of vertices
2468 xVert = []
2469 yVert = []
2470 for i in range(self.getvertexcount(pipeIndex)):
2471 x, y = self.getvertex(pipeIndex, i)
2472 xVert.append(x)
2473 yVert.append(y)
2474
2475 xMidPos = int(len(xVert) / 2)
2476 midX = xVert[xMidPos]
2477 midY = yVert[xMidPos]
2478
2479 # Add the new node between the link/pipe and add the same properties
2480 # as the left node (the elevation is the average of left-right nodes)
2481 index = self.addnode(new_node_id, EpanetConstants.EN_JUNCTION)
2482 self.setcoord(index, midX, midY)
2483
2484 newNodeIndex = self.get_node_idx(new_node_id)
2485 midElev = (self.get_node_elevation(leftNodeIndex) +
2486 self.get_node_elevation(rightNodeIndex)) / 2
2487 self.setjuncdata(newNodeIndex, midElev, 0, "")
2488 self.setnodevalue(newNodeIndex, EpanetConstants.EN_EMITTER,
2489 self.get_node_emitter_coeff(leftNodeIndex))
2490 if self.getqualtype()[0] > 0:
2491 midInitQual = (self.get_node_init_quality(leftNodeIndex) +
2492 self.get_node_init_quality(rightNodeIndex)) / 2
2493 self.set_node_init_quality(newNodeIndex, midInitQual)
2494 self.set_node_source_quality(newNodeIndex, self.get_node_source_qual(leftNodeIndex))
2495 self.setnodevalue(newNodeIndex, EpanetConstants.EN_SOURCEPAT,
2496 self.getnodevalue(leftNodeIndex, EpanetConstants.EN_SOURCEPAT))
2497 if self.getnodevalue(leftNodeIndex, EpanetConstants.EN_SOURCETYPE) != 0:
2498 self.setnodevalue(newNodeIndex, EpanetConstants.EN_SOURCETYPE,
2499 self.getnodevalue(leftNodeIndex, EpanetConstants.EN_SOURCETYPE))
2500
2501 # Access link properties
2502 linkDia = self.get_link_diameter(pipeIndex)
2503 linkLength = self.get_link_length(pipeIndex)
2504 linkRoughnessCoeff = self.get_link_roughness(pipeIndex)
2505 linkMinorLossCoeff = self.get_link_minorloss(pipeIndex)
2506 linkInitialStatus = self.get_link_init_status(pipeIndex)
2507 linkInitialSetting = self.get_link_init_setting(pipeIndex)
2508 linkBulkReactionCoeff = self.get_link_bulk_reaction_coeff(pipeIndex)
2509 linkWallReactionCoeff = self.get_link_wall_reaction_coeff(pipeIndex)
2510
2511 # Delete the link/pipe that is splitted
2512 self.deletelink(pipeIndex, 0)
2513
2514 # Add two new pipes
2515 # d.addLinkPipe(pipeID, fromNode, toNode)
2516 # Add the Left Pipe and add the same properties as the deleted link
2517 leftNodeID = self.get_node_id(leftNodeIndex)
2518 leftPipeIndex = self.addlink(pipe_id, EpanetConstants.EN_PIPE, leftNodeID, new_node_id)
2519 self.setlinknodes(leftPipeIndex, leftNodeIndex, newNodeIndex)
2520 self.setpipedata(leftPipeIndex, linkLength, linkDia, linkRoughnessCoeff, linkMinorLossCoeff)
2521 self.setlinkvalue(leftPipeIndex, EpanetConstants.EN_INITSTATUS, linkInitialStatus)
2522 self.setlinkvalue(leftPipeIndex, EpanetConstants.EN_INITSETTING, linkInitialSetting)
2523 self.setlinkvalue(leftPipeIndex, EpanetConstants.EN_KBULK, linkBulkReactionCoeff)
2524 self.setlinkvalue(leftPipeIndex, EpanetConstants.EN_KWALL, linkWallReactionCoeff)
2525
2526 # Add the Right Pipe and add the same properties as the deleted link
2527 rightNodeID = self.get_node_id(rightNodeIndex)
2528 rightPipeIndex = self.addlink(new_pipe_id, EpanetConstants.EN_PIPE, new_node_id, rightNodeID)
2529 self.setlinknodes(rightPipeIndex, newNodeIndex, rightNodeIndex)
2530 self.setpipedata(rightPipeIndex, linkLength, linkDia, linkRoughnessCoeff,
2531 linkMinorLossCoeff)
2532 self.setlinkvalue(rightPipeIndex, EpanetConstants.EN_INITSTATUS, linkInitialStatus)
2533 self.setlinkvalue(rightPipeIndex, EpanetConstants.EN_INITSETTING, linkInitialSetting)
2534 self.setlinkvalue(rightPipeIndex, EpanetConstants.EN_KBULK, linkBulkReactionCoeff)
2535 self.setlinkvalue(rightPipeIndex, EpanetConstants.EN_KWALL, linkWallReactionCoeff)
2536
2537 def _parse_msx_file(self) -> dict:
2538 if self._msx_file is None:
2539 raise ValueError("No .msx file loaded")
2540
2541 # Code for parsing .msx files taken from EPyT
2542 keys = ["AREA_UNITS", "RATE_UNITS", "SOLVER", "COUPLING", "TIMESTEP", "ATOL", "RTOL",
2543 "COMPILER", "SEGMENTS", "PECLET"]
2544 float_values = ["TIMESTEP", "ATOL", "RTOL", "SEGMENTS", "PECLET"]
2545 values = {key: None for key in keys}
2546
2547 # Flag to determine if we're in the [OPTIONS] section
2548 in_options = False
2549
2550 # Open and read the file
2551 with open(self._msx_file, 'r') as file:
2552 for line in file:
2553 # Check for [OPTIONS] section
2554 if "[OPTIONS]" in line:
2555 in_options = True
2556 elif "[" in line and "]" in line:
2557 in_options = False # We've reached a new section
2558
2559 if in_options:
2560 # Pattern to match the keys and extract values, ignoring comments and whitespace
2561 pattern = re.compile(r'^\s*(' + '|'.join(keys) + r')\s+(.*?)\s*(?:;.*)?$')
2562 match = pattern.search(line)
2563 if match:
2564 key, value = match.groups()
2565 if key in float_values:
2566 values[key] = float(value)
2567 else:
2568 values[key] = value
2569 return values
2570
[docs]
2571 def get_msx_time_step(self) -> int:
2572 """
2573 Returns the MSX time step.
2574
2575 Returns
2576 -------
2577 `int`
2578 Time step.
2579 """
2580 return int(self._parse_msx_file()["TIMESTEP"])
2581
[docs]
2582 def set_msx_time_step(self, time_step: int) -> None:
2583 """
2584 Specifies the MSX time step.
2585
2586 Parameters
2587 ----------
2588 time_step : `int`
2589 New MSX time step.
2590 """
2591 temp_folder = tempfile.gettempdir()
2592 file_name = os.path.basename(self._msx_file)
2593 temp_file = os.path.join(temp_folder, file_name)
2594
2595 self.MSXsavemsxfile(temp_file)
2596 self.MSXclose()
2597
2598 with open(temp_file, 'r+') as f: # Code taken from EPyT -- workaround for missing functions
2599 lines = f.readlines()
2600 options_index = -1
2601 flag = 0
2602 for i, line in enumerate(lines):
2603 if line.strip() == '[OPTIONS]':
2604 options_index = i
2605 elif line.strip().startswith("TIMESTEP"):
2606 lines[i] = "TIMESTEP" + "\t" + str(time_step) + "\n"
2607 flag = 1
2608 if flag == 0 and options_index != -1:
2609 lines.insert(options_index + 1, "TIMESTEP" + "\t" + str(time_step) + "\n")
2610 f.seek(0)
2611 f.writelines(lines)
2612 f.truncate()
2613
2614 self.MSXopen(temp_file)
2615 self._msx_file = temp_file
2616
[docs]
2617 def get_msx_options(self) -> dict:
2618 """
2619 Returns the MSX options as specified in the .msx file.
2620
2621 Returns
2622 -------
2623 `dict`
2624 Dictionary of MSX options as specified in the .msx file -- note that not all
2625 options might be specified.
2626 Possible options (dictinary keys) are: REA_UNITS, RATE_UNITS, SOLVER, COUPLING,
2627 TIMESTEP, ATOL, RTOL, COMPILER, SEGMENTS, PECLET
2628 """
2629 return self._parse_msx_file()
2630
[docs]
2631 def add_msx_pattern(self, pattern_id: str, pattern_mult: list[float]) -> None:
2632 """
2633 Adds a new MSX pattern.
2634
2635 Parameters
2636 ----------
2637 pattern_id : `str`
2638 ID of the new pattern.
2639 pattern_mult : `list[float]`
2640 Pattern values (i.e., multipliers).
2641 """
2642 self.MSXaddpattern(pattern_id)
2643 pattern_idx = self.MSXgetindex(EpanetConstants.MSX_PATTERN, pattern_id)
2644 self.MSXsetpattern(pattern_idx, pattern_mult, len(pattern_mult))
2645
[docs]
2646 def set_msx_source(self, node_id: str, species_id: str, source_type: int,
2647 source_concentration: float, msx_pattern_id: str) -> None:
2648 """
2649 Adds a species source (i.e., injection of a given species) at a given node.
2650
2651 Parameters
2652 ----------
2653 node_id : `str`
2654 ID of the node where the species in injected into the network.
2655 species_id : `str`
2656 ID of the species to be injected.
2657 source_type : `int`
2658 Type of injection/source. Must be one of the following:
2659
2660 - MSX_NOSOURCE = -1 for no source,
2661 - MSX_CONCEN = 0 for a concentration source,
2662 - MSX_MASS = 1 for a mass booster source,
2663 - MSX_SETPOINT = 2 for a setpoint source,
2664 - MSX_FLOWPACED = 3 for a flow paced source;
2665 source_concentration : `float`
2666 Injetion concentration -- can change over time according the the pattern of multiplies.
2667 msx_pattern_id : `str`
2668 ID of the injection pattern -- i.e., multipliers.
2669 """
2670 node_idx = self.get_node_idx(node_id)
2671 species_idx = self.get_msx_species_idx(species_id)
2672 msx_pattern_idx = self.MSXgetindex(EpanetConstants.MSX_PATTERN, msx_pattern_id)
2673
2674 self.MSXsetsource(node_idx, species_idx, source_type, source_concentration, msx_pattern_idx)
2675
[docs]
2676 def get_msx_species_init_concentration(self, obj_type: int, obj_index: int,
2677 species_idx: int) -> float:
2678 """
2679 Returns the initial concentration of a given species at a given location in the network.
2680
2681 Parameters
2682 ----------
2683 obj_type : `int`
2684 Type of the location (i.e., node or link). Must be one of the following:
2685
2686 - MSX_NODE
2687 - MSX_LINK
2688 obj_index : `int`
2689 Index of the link or node.
2690 species_idx : `int`
2691 Index of the species.
2692
2693 Returns
2694 -------
2695 `float`
2696 Initial concentration.
2697 """
2698 return self.MSXgetinitqual(obj_type, obj_index, species_idx)
2699
[docs]
2700 def get_msx_species_concentration(self, obj_type: int, obj_index: int,
2701 species_idx: int) -> float:
2702 """
2703 Returns the current concentration of a given species at a given location in the network.
2704
2705 Parameters
2706 ----------
2707 obj_type : `int`
2708 Type of the location (i.e., node or link). Must be one of the following:
2709
2710 - MSX_NODE
2711 - MSX_LINK
2712 obj_index : `int`
2713 Index of the link or node.
2714 species_idx : `int`
2715 Index of the species.
2716
2717 Returns
2718 -------
2719 `float`
2720 Species concentration.
2721 """
2722 return self.MSXgetqual(obj_type, obj_index, species_idx)
2723
[docs]
2724 def get_all_msx_species_id(self) -> list[str]:
2725 """
2726 Returns a list of all species IDs.
2727
2728 Returns
2729 -------
2730 `list[str]`
2731 List of all species IDs.
2732 """
2733 return [self.MSXgetID(EpanetConstants.MSX_SPECIES, i + 1)
2734 for i in range(self.MSXgetcount(EpanetConstants.MSX_SPECIES))]
2735
[docs]
2736 def get_msx_species_idx(self, species_id) -> int:
2737 """
2738 Returns the index of a given species.
2739
2740 Parameters
2741 ----------
2742 species_id : `str`
2743 ID of the species.
2744
2745 Returns
2746 -------
2747 `int`
2748 Index of the species.
2749 """
2750 return self.MSXgetindex(EpanetConstants.MSX_SPECIES, species_id)
2751
[docs]
2752 def get_num_msx_species(self) -> int:
2753 """
2754 Returns the total number of bulk and wall species.
2755
2756 Returns
2757 -------
2758 `int`
2759 Number of species.
2760 """
2761 return self.MSXgetcount(EpanetConstants.MSX_SPECIES)
2762
[docs]
2763 def get_msx_species_info(self, species_idx: int) -> dict:
2764 """
2765 Returns information about a given species.
2766
2767 Parameters
2768 ----------
2769 species_idx : `int`
2770 Index of the species.
2771
2772 Returns
2773 -------
2774 `dict`
2775 Information as a dictionary. Will contains the following entries:
2776
2777 - 'type': MSX_BULK for a bulk flow species or MSX_WALL for a surface species;
2778 - 'units': mass units;
2779 - 'atol': absolute concentration tolerance (concentration units);
2780 - 'rtol': relative concentration tolerance (unitless);
2781 """
2782 return dict(zip(["type", "units", "atol", "rtol"], self.MSXgetspecies(species_idx)))
2783
[docs]
2784 def get_all_msx_species_info(self) -> list[dict]:
2785 """
2786 Returns information about all species.
2787
2788 Returns
2789 -------
2790 `list[dict]`
2791 List of species information -- ordered by species index.#
2792 Each entry in the list contains a dictionary with the following entries:
2793
2794 - 'type': MSX_BULK for a bulk flow species or MSX_WALL for a surface species;
2795 - 'units': mass units;
2796 - 'atol': absolute concentration tolerance (concentration units);
2797 - 'rtol': relative concentration tolerance (unitless);
2798 """
2799 return [self.get_msx_species_info(i + 1)
2800 for i in range(self.MSXgetcount(EpanetConstants.MSX_SPECIES))]
2801
[docs]
2802 def get_all_bulk_species_id(self) -> list[str]:
2803 """
2804 Returns the IDs of all bulk species.
2805
2806 Returns
2807 -------
2808 `list[int]`
2809 List of IDs.
2810 """
2811 r = []
2812
2813 for i in range(self.MSXgetcount(EpanetConstants.MSX_SPECIES)):
2814 if self.MSXgetspecies(i + 1)[0] == EpanetConstants.MSX_BULK:
2815 r.append(self.get_msx_species_idx(i + 1))
2816
2817 return r
2818
[docs]
2819 def get_all_bulk_species_idx(self) -> list[int]:
2820 """
2821 Returns the indices of all bulk species.
2822
2823 Returns
2824 -------
2825 `list[int]`
2826 List of indices.
2827 """
2828 r = []
2829
2830 for i in range(self.MSXgetcount(EpanetConstants.MSX_SPECIES)):
2831 if self.MSXgetspecies(i + 1)[0] == EpanetConstants.MSX_BULK:
2832 r.append(i + 1)
2833
2834 return r
2835
[docs]
2836 def get_all_wall_species_id(self) -> list[str]:
2837 """
2838 Returns the IDs of all wall species.
2839
2840 Returns
2841 -------
2842 `list[int]`
2843 List of IDs.
2844 """
2845 r = []
2846
2847 for i in range(self.MSXgetcount(EpanetConstants.MSX_SPECIES)):
2848 if self.MSXgetspecies(i + 1)[0] == EpanetConstants.MSX_WALL:
2849 r.append(self.get_msx_species_idx(i + 1))
2850
2851 return r
2852
[docs]
2853 def get_all_wall_species_idx(self) -> list[int]:
2854 """
2855 Returns the indices of all wall species.
2856
2857 Returns
2858 -------
2859 `list[int]`
2860 List of indices.
2861 """
2862 r = []
2863
2864 for i in range(self.MSXgetcount(EpanetConstants.MSX_SPECIES)):
2865 if self.MSXgetspecies(i + 1)[0] == EpanetConstants.MSX_WALL:
2866 r.append(i + 1)
2867
2868 return r
2869
[docs]
2870 def get_msx_pattern(self, pattern_idx: int) -> list[float]:
2871 """
2872 Returns a particular MSX pattern -- i.e., returns the multipliers.
2873
2874 Parameters
2875 ----------
2876 pattern_idx: `int`
2877 Index of the pattern.
2878
2879 Returns
2880 -------
2881 `list[float]`
2882 Pattern multipliers.
2883 """
2884 r = []
2885
2886 pattern_length = self.MSXgetpatternlen(pattern_idx)
2887 for idx in range(1, pattern_length + 1):
2888 r.append(self.MSXgetpatternvalue(pattern_idx, idx))
2889
2890 return r
2891
[docs]
2892 def get_all_msx_pattern_id(self) -> list[str]:
2893 """
2894 Returns a list of the IDs of all MSX patterns.
2895
2896 Returns
2897 -------
2898 `list[str]`
2899 List of patterns (IDs).
2900 """
2901 r = []
2902
2903 n_msx_patterns = self.MSXgetcount(EpanetConstants.MSX_PATTERN)
2904 for pattern_idx in range(1, n_msx_patterns + 1):
2905 r.append(self.MSXgetID(EpanetConstants.MSX_PATTERN, pattern_idx))
2906
2907 return r