Source code for epanet_plus.epanet_toolkit

   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 517 528 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 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 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 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 1279 1295 1311 1327 1346 1362 1378 1394
[docs] 1395 def get_node_comment(self, node_idx: int) -> str: 1396 """ 1397 Returns the comment of a given node. 1398 1399 Parameters 1400 ---------- 1401 node_idx : `int` 1402 Index of the node. 1403 1404 Returns 1405 ------- 1406 `str` 1407 Comment. 1408 """ 1409 return self.getcomment(EpanetConstants.EN_NODE, node_idx)
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 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 2130 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 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 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