epanet-plus 0.1.2__cp312-cp312-macosx_10_13_x86_64.whl

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