exerpy 0.0.2__py3-none-any.whl → 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exerpy/__init__.py +2 -4
- exerpy/analyses.py +597 -297
- exerpy/components/__init__.py +3 -0
- exerpy/components/combustion/base.py +53 -35
- exerpy/components/component.py +8 -8
- exerpy/components/heat_exchanger/base.py +186 -119
- exerpy/components/heat_exchanger/condenser.py +96 -60
- exerpy/components/heat_exchanger/simple.py +237 -137
- exerpy/components/heat_exchanger/steam_generator.py +46 -41
- exerpy/components/helpers/cycle_closer.py +61 -34
- exerpy/components/helpers/power_bus.py +117 -0
- exerpy/components/nodes/deaerator.py +176 -58
- exerpy/components/nodes/drum.py +50 -39
- exerpy/components/nodes/flash_tank.py +218 -43
- exerpy/components/nodes/mixer.py +249 -69
- exerpy/components/nodes/splitter.py +173 -0
- exerpy/components/nodes/storage.py +130 -0
- exerpy/components/piping/valve.py +311 -115
- exerpy/components/power_machines/generator.py +105 -38
- exerpy/components/power_machines/motor.py +111 -39
- exerpy/components/turbomachinery/compressor.py +181 -63
- exerpy/components/turbomachinery/pump.py +182 -63
- exerpy/components/turbomachinery/turbine.py +182 -74
- exerpy/functions.py +388 -263
- exerpy/parser/from_aspen/aspen_config.py +57 -48
- exerpy/parser/from_aspen/aspen_parser.py +373 -280
- exerpy/parser/from_ebsilon/__init__.py +2 -2
- exerpy/parser/from_ebsilon/check_ebs_path.py +15 -19
- exerpy/parser/from_ebsilon/ebsilon_config.py +328 -226
- exerpy/parser/from_ebsilon/ebsilon_functions.py +205 -38
- exerpy/parser/from_ebsilon/ebsilon_parser.py +392 -255
- exerpy/parser/from_ebsilon/utils.py +16 -11
- exerpy/parser/from_tespy/tespy_config.py +32 -1
- exerpy/parser/from_tespy/tespy_parser.py +151 -0
- {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/METADATA +43 -2
- exerpy-0.0.3.dist-info/RECORD +48 -0
- exerpy-0.0.2.dist-info/RECORD +0 -44
- {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/WHEEL +0 -0
- {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,30 +1,25 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Any
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
4
|
from CoolProp.CoolProp import PropsSI as CP
|
|
6
5
|
|
|
7
6
|
from . import __ebsilon_available__
|
|
8
|
-
from .utils import EpGasTableStub
|
|
9
|
-
from .utils import EpSteamTableStub
|
|
10
|
-
from .utils import require_ebsilon
|
|
7
|
+
from .utils import EpGasTableStub, EpSteamTableStub, require_ebsilon
|
|
11
8
|
|
|
12
9
|
# Import Ebsilon classes if available
|
|
13
10
|
if __ebsilon_available__:
|
|
14
|
-
from EbsOpen import EpGasTable
|
|
15
|
-
from EbsOpen import EpSteamTable
|
|
11
|
+
from EbsOpen import EpGasTable, EpSteamTable
|
|
16
12
|
else:
|
|
17
13
|
EpSteamTable = EpSteamTableStub
|
|
18
14
|
EpGasTable = EpGasTableStub
|
|
19
15
|
|
|
20
16
|
from exerpy.functions import convert_to_SI
|
|
21
17
|
|
|
22
|
-
from .ebsilon_config import substance_mapping
|
|
23
|
-
from .ebsilon_config import unit_id_to_string
|
|
18
|
+
from .ebsilon_config import substance_mapping, unit_id_to_string
|
|
24
19
|
|
|
25
20
|
|
|
26
21
|
@require_ebsilon
|
|
27
|
-
def calc_X_from_PT(app: Any, pipe: Any, property: str, pressure: float, temperature: float) ->
|
|
22
|
+
def calc_X_from_PT(app: Any, pipe: Any, property: str, pressure: float, temperature: float) -> float | None:
|
|
28
23
|
"""
|
|
29
24
|
Calculate a thermodynamic property (enthalpy or entropy) for a given stream based on pressure and temperature.
|
|
30
25
|
|
|
@@ -53,35 +48,29 @@ def calc_X_from_PT(app: Any, pipe: Any, property: str, pressure: float, temperat
|
|
|
53
48
|
|
|
54
49
|
Raises
|
|
55
50
|
------
|
|
51
|
+
ValueError
|
|
52
|
+
If Ebsilon returns the sentinel value -999.0 indicating a failed calculation.
|
|
56
53
|
Exception
|
|
57
|
-
Logs an error and returns None if any exception occurs during property calculation.
|
|
54
|
+
Logs an error and returns None if any other exception occurs during property calculation.
|
|
58
55
|
"""
|
|
59
56
|
|
|
60
57
|
# Create a new FluidData object
|
|
61
58
|
fd = app.NewFluidData()
|
|
62
59
|
|
|
63
60
|
# Retrieve the fluid type from the stream
|
|
64
|
-
fd.FluidType =
|
|
61
|
+
fd.FluidType = pipe.Kind - 1000
|
|
65
62
|
|
|
66
63
|
if fd.FluidType == 3 or fd.FluidType == 4: # steam or water
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
elif fd.FluidType == 15: # 2PhaseLiquid
|
|
77
|
-
fd.Medium = pipe.FMED.Value
|
|
78
|
-
fdAnalysis = app.NewFluidAnalysis()
|
|
79
|
-
|
|
80
|
-
elif fd.FluidType == 16: # 2PhaseGaseous
|
|
81
|
-
fd.Medium = pipe.FMED.Value
|
|
82
|
-
fdAnalysis = app.NewFluidAnalysis()
|
|
83
|
-
|
|
84
|
-
elif fd.FluidType == 17: # Salt water
|
|
64
|
+
t_sat = CP("T", "P", pressure, "Q", 0, "water")
|
|
65
|
+
if temperature > t_sat:
|
|
66
|
+
fd.FluidType = 3 # steam
|
|
67
|
+
fd.SteamTable = EpSteamTable.epSteamTableFromSuperiorModel
|
|
68
|
+
fdAnalysis = app.NewFluidAnalysis()
|
|
69
|
+
else:
|
|
70
|
+
fd.FluidType = 4 # water
|
|
71
|
+
fdAnalysis = app.NewFluidAnalysis()
|
|
72
|
+
|
|
73
|
+
elif fd.FluidType == 15 or fd.FluidType == 16 or fd.FluidType == 17 or fd.FluidType == 20: # 2PhaseLiquid
|
|
85
74
|
fd.Medium = pipe.FMED.Value
|
|
86
75
|
fdAnalysis = app.NewFluidAnalysis()
|
|
87
76
|
|
|
@@ -101,22 +90,31 @@ def calc_X_from_PT(app: Any, pipe: Any, property: str, pressure: float, temperat
|
|
|
101
90
|
fd.SetAnalysis(fdAnalysis)
|
|
102
91
|
|
|
103
92
|
# Validate property input
|
|
104
|
-
if property not in [
|
|
93
|
+
if property not in ["S", "H"]:
|
|
105
94
|
logging.error('Invalid property selected. You can choose between "H" (enthalpy) and "S" (entropy).')
|
|
106
95
|
return None
|
|
107
96
|
|
|
108
97
|
try:
|
|
109
98
|
# Calculate the property based on the input property type
|
|
110
|
-
if property ==
|
|
99
|
+
if property == "S": # Entropy
|
|
111
100
|
res = fd.PropertyS_OF_PT(pressure * 1e-5, temperature - 273.15) # Ebsilon works with °C and bar
|
|
112
101
|
res_SI = res * 1e3 # Convert kJ/kgK to J/kgK
|
|
113
|
-
elif property ==
|
|
102
|
+
elif property == "H": # Enthalpy
|
|
114
103
|
res = fd.PropertyH_OF_PT(pressure * 1e-5, temperature - 273.15) # Ebsilon works with °C and bar
|
|
115
104
|
res_SI = res * 1e3 # Convert kJ/kg to J/kg
|
|
116
105
|
|
|
106
|
+
if res == -999.0:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Calculation with Ebsilon property failed: {property} = -999.0 "
|
|
109
|
+
f"for fluid {fd.Medium} at p={pressure} Pa and T={temperature} K. "
|
|
110
|
+
f"It may helpful to set split_physical_exergy=False in the ExergyAnalysis constructor."
|
|
111
|
+
)
|
|
112
|
+
|
|
117
113
|
return res_SI
|
|
118
114
|
|
|
119
115
|
except Exception as e:
|
|
116
|
+
if isinstance(e, ValueError):
|
|
117
|
+
raise
|
|
120
118
|
logging.error(f"An error occurred during property calculation: {e}")
|
|
121
119
|
return None
|
|
122
120
|
|
|
@@ -144,10 +142,10 @@ def calc_eT(app: Any, pipe: Any, pressure: float, Tamb: float, pamb: float) -> f
|
|
|
144
142
|
float
|
|
145
143
|
The thermal exergy component (in J/kg).
|
|
146
144
|
"""
|
|
147
|
-
h_i = convert_to_SI(
|
|
148
|
-
s_i = convert_to_SI(
|
|
149
|
-
h_A = calc_X_from_PT(app, pipe,
|
|
150
|
-
s_A = calc_X_from_PT(app, pipe,
|
|
145
|
+
h_i = convert_to_SI("h", pipe.H.Value, unit_id_to_string.get(pipe.H.Dimension, "Unknown")) # in SI unit [J / kg]
|
|
146
|
+
s_i = convert_to_SI("s", pipe.S.Value, unit_id_to_string.get(pipe.S.Dimension, "Unknown")) # in SI unit [J / kgK]
|
|
147
|
+
h_A = calc_X_from_PT(app, pipe, "H", pressure, Tamb) # in SI unit [J / kg]
|
|
148
|
+
s_A = calc_X_from_PT(app, pipe, "S", pressure, Tamb) # in SI unit [J / kgK]
|
|
151
149
|
eT = h_i - h_A - Tamb * (s_i - s_A) # in SI unit [J / kg]
|
|
152
150
|
|
|
153
151
|
return eT
|
|
@@ -176,6 +174,175 @@ def calc_eM(app: Any, pipe: Any, pressure: float, Tamb: float, pamb: float) -> f
|
|
|
176
174
|
float
|
|
177
175
|
The mechanical exergy component (in J/kg).
|
|
178
176
|
"""
|
|
179
|
-
eM = convert_to_SI(
|
|
177
|
+
eM = convert_to_SI("e", pipe.E.Value, unit_id_to_string.get(pipe.E.Dimension, "Unknown")) - calc_eT(
|
|
178
|
+
app, pipe, pressure, Tamb, pamb
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return eM
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def calc_eph_from_min(pipe: Any, Tamb: float) -> float | None:
|
|
185
|
+
"""
|
|
186
|
+
Calculate physical exergy using the minimum-valid-temperature reference state.
|
|
187
|
+
|
|
188
|
+
Formula:
|
|
189
|
+
e_PH = h - h_min - Tamb * (s - s_min)
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
pipe : Any
|
|
194
|
+
The Ebsilon pipe object containing thermodynamic properties.
|
|
195
|
+
Tamb : float
|
|
196
|
+
Ambient (reference) temperature in K.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
Optional[float]
|
|
201
|
+
Physical exergy in J/kg, or None if calculation fails.
|
|
202
|
+
|
|
203
|
+
Notes
|
|
204
|
+
-----
|
|
205
|
+
- This function accesses H_Min and S_Min from the ThermoliquidExtension.
|
|
206
|
+
- The minimum state (h_min, s_min) represents the enthalpy and entropy at the
|
|
207
|
+
lowest valid temperature for the fluid.
|
|
208
|
+
- The ambient temperature Tamb is used as the reference temperature for the
|
|
209
|
+
exergy calculation, while h_min and s_min provide the reference state properties.
|
|
210
|
+
- This approach is for now only suitable for ThermoLiquid fluids where conventional
|
|
211
|
+
dead state properties may not be available or valid.
|
|
212
|
+
"""
|
|
213
|
+
import logging
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
# 1) Get current state from the pipe
|
|
217
|
+
h_i = convert_to_SI("h", pipe.H.Value, unit_id_to_string.get(pipe.H.Dimension, "Unknown")) # J/kg
|
|
218
|
+
s_i = convert_to_SI("s", pipe.S.Value, unit_id_to_string.get(pipe.S.Dimension, "Unknown")) # J/kgK
|
|
219
|
+
|
|
220
|
+
# 2) Get minimum state from ThermoliquidExtension
|
|
221
|
+
fluid_data = pipe.FluidData()
|
|
222
|
+
tl_ext = fluid_data.ThermoliquidExtension
|
|
223
|
+
|
|
224
|
+
# Get minimum values (assuming these are in Ebsilon's standard units)
|
|
225
|
+
h_min = convert_to_SI("h", tl_ext.H_Min, unit_id_to_string.get(pipe.H.Dimension, "Unknown")) # J/kg
|
|
226
|
+
s_min = convert_to_SI("s", tl_ext.S_Min, unit_id_to_string.get(pipe.S.Dimension, "Unknown")) # J/kgK
|
|
227
|
+
t_min = convert_to_SI("T", tl_ext.T_Min, unit_id_to_string.get(pipe.T.Dimension, "Unknown")) # K
|
|
228
|
+
|
|
229
|
+
# 3) Calculate physical exergy using Tamb as reference temperature
|
|
230
|
+
e_ph = h_i - h_min - Tamb * (s_i - s_min)
|
|
231
|
+
|
|
232
|
+
logging.info(
|
|
233
|
+
f"e_PH(min) for {getattr(pipe, 'Name', '<unknown>')}: "
|
|
234
|
+
f"T_min={t_min-273.15:.2f} °C, h_min={h_min:.1f} J/kg, s_min={s_min:.3f} J/kgK, "
|
|
235
|
+
f"Tamb={Tamb-273.15:.2f} °C, e_PH={e_ph:.1f} J/kg"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return e_ph
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logging.error(f"Failed to calculate e_PH from min for {getattr(pipe, 'Name', '<unknown>')}: {e}")
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@require_ebsilon
|
|
246
|
+
def debug_substance_detection(app, pipe):
|
|
247
|
+
"""
|
|
248
|
+
Debug helper to understand what substance/medium Ebsilon is using for a pipe.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
app : IApplication
|
|
253
|
+
Ebsilon application instance
|
|
254
|
+
pipe : IPipe
|
|
255
|
+
Pipe object to debug
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
dict
|
|
260
|
+
Information about the substance detection
|
|
261
|
+
"""
|
|
262
|
+
fluid_type_index = {
|
|
263
|
+
1: "IAPWS-IF97 (Water/Steam)",
|
|
264
|
+
3: "Ideal gas",
|
|
265
|
+
4: "Real gas",
|
|
266
|
+
6: "Numeric value",
|
|
267
|
+
9: "Binary mixture",
|
|
268
|
+
10: "Power/Mechanical",
|
|
269
|
+
13: "Logic",
|
|
270
|
+
20: "ThermoLiquid (substance 120)",
|
|
271
|
+
1122: "Molten Salt (substance 122)",
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
result = {
|
|
275
|
+
"pipe_name": pipe.Name,
|
|
276
|
+
"fluid_type": pipe.FluidType,
|
|
277
|
+
"fluid_type_name": fluid_type_index.get(pipe.FluidType, "Unknown"),
|
|
278
|
+
"FMED": pipe.FMED.Value,
|
|
279
|
+
"T": pipe.T.Value,
|
|
280
|
+
"p": pipe.P.Value,
|
|
281
|
+
"substance_attempts": {},
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Try to get substance properties
|
|
285
|
+
try:
|
|
286
|
+
# Try substance 120 (default for ThermoLiquid)
|
|
287
|
+
sub120 = app.GetSubstance(120)
|
|
288
|
+
result["substance_attempts"]["120"] = {
|
|
289
|
+
"exists": True,
|
|
290
|
+
"name": sub120.Name if hasattr(sub120, "Name") else "N/A",
|
|
291
|
+
}
|
|
292
|
+
except Exception as e:
|
|
293
|
+
result["substance_attempts"]["120"] = {"exists": False, "error": str(e)}
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
# Try substance 122 (Molten Salt)
|
|
297
|
+
sub122 = app.GetSubstance(122)
|
|
298
|
+
result["substance_attempts"]["122"] = {
|
|
299
|
+
"exists": True,
|
|
300
|
+
"name": sub122.Name if hasattr(sub122, "Name") else "N/A",
|
|
301
|
+
}
|
|
302
|
+
except Exception as e:
|
|
303
|
+
result["substance_attempts"]["122"] = {"exists": False, "error": str(e)}
|
|
304
|
+
|
|
305
|
+
# Try to change FMED and see what happens
|
|
306
|
+
try:
|
|
307
|
+
original_fmed = pipe.FMED.Value
|
|
308
|
+
pipe.FMED.Value = 122 # Try to set to molten salt
|
|
309
|
+
result["fmed_change_test"] = {
|
|
310
|
+
"original": original_fmed,
|
|
311
|
+
"attempted": 122,
|
|
312
|
+
"success": pipe.FMED.Value == 122,
|
|
313
|
+
"actual_value": pipe.FMED.Value,
|
|
314
|
+
}
|
|
315
|
+
# Restore original
|
|
316
|
+
pipe.FMED.Value = original_fmed
|
|
317
|
+
except Exception as e:
|
|
318
|
+
result["fmed_change_test"] = {"error": str(e)}
|
|
319
|
+
|
|
320
|
+
return result
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _get_stream_properties(pipe, T0, p0):
|
|
324
|
+
"""
|
|
325
|
+
Get thermodynamic properties from an Ebsilon pipe.
|
|
326
|
+
For molten salt and other ThermoLiquid, use Ebsilon's calculated values.
|
|
327
|
+
"""
|
|
328
|
+
properties = {}
|
|
329
|
+
|
|
330
|
+
# Get current state from pipe
|
|
331
|
+
properties["T"] = pipe.T.Value # °C
|
|
332
|
+
properties["p"] = pipe.P.Value # bar
|
|
333
|
+
properties["m"] = pipe.M.Value # kg/s
|
|
334
|
+
|
|
335
|
+
# For ThermoLiquid (molten salt, thermoil, etc.), Ebsilon calculates h and s
|
|
336
|
+
if pipe.FluidType == 20:
|
|
337
|
+
# Use Ebsilon's enthalpy and entropy directly
|
|
338
|
+
properties["h"] = pipe.H.Value # kJ/kg
|
|
339
|
+
properties["s"] = pipe.S.Value # kJ/kgK
|
|
340
|
+
|
|
341
|
+
# Reference state properties - need to be calculated at T0, p0
|
|
342
|
+
# We'll handle this separately
|
|
343
|
+
|
|
344
|
+
elif pipe.FluidType == 1: # Water/Steam
|
|
345
|
+
properties["h"] = pipe.H.Value
|
|
346
|
+
properties["s"] = pipe.S.Value
|
|
180
347
|
|
|
181
|
-
return
|
|
348
|
+
return properties
|