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