exerpy 0.0.2__py3-none-any.whl → 0.0.4__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.
Files changed (48) hide show
  1. exerpy/__init__.py +2 -4
  2. exerpy/analyses.py +849 -304
  3. exerpy/components/__init__.py +3 -0
  4. exerpy/components/combustion/base.py +53 -35
  5. exerpy/components/component.py +8 -8
  6. exerpy/components/heat_exchanger/base.py +188 -121
  7. exerpy/components/heat_exchanger/condenser.py +98 -62
  8. exerpy/components/heat_exchanger/simple.py +237 -137
  9. exerpy/components/heat_exchanger/steam_generator.py +46 -41
  10. exerpy/components/helpers/cycle_closer.py +61 -34
  11. exerpy/components/helpers/power_bus.py +117 -0
  12. exerpy/components/nodes/deaerator.py +176 -58
  13. exerpy/components/nodes/drum.py +50 -39
  14. exerpy/components/nodes/flash_tank.py +218 -43
  15. exerpy/components/nodes/mixer.py +249 -69
  16. exerpy/components/nodes/splitter.py +173 -0
  17. exerpy/components/nodes/storage.py +130 -0
  18. exerpy/components/piping/valve.py +311 -115
  19. exerpy/components/power_machines/generator.py +105 -38
  20. exerpy/components/power_machines/motor.py +111 -39
  21. exerpy/components/turbomachinery/compressor.py +214 -68
  22. exerpy/components/turbomachinery/pump.py +215 -68
  23. exerpy/components/turbomachinery/turbine.py +182 -74
  24. exerpy/cost_estimation/__init__.py +65 -0
  25. exerpy/cost_estimation/turton.py +1260 -0
  26. exerpy/data/cost_correlations/cepci_index.json +135 -0
  27. exerpy/data/cost_correlations/component_mapping.json +450 -0
  28. exerpy/data/cost_correlations/material_factors.json +428 -0
  29. exerpy/data/cost_correlations/pressure_factors.json +206 -0
  30. exerpy/data/cost_correlations/turton2008.json +726 -0
  31. exerpy/data/cost_correlations/turton2008_design_analysis_synthesis_components_tables.pdf +0 -0
  32. exerpy/data/cost_correlations/turton2008_design_analysis_synthesis_components_theory.pdf +0 -0
  33. exerpy/functions.py +389 -264
  34. exerpy/parser/from_aspen/aspen_config.py +57 -48
  35. exerpy/parser/from_aspen/aspen_parser.py +373 -280
  36. exerpy/parser/from_ebsilon/__init__.py +2 -2
  37. exerpy/parser/from_ebsilon/check_ebs_path.py +15 -19
  38. exerpy/parser/from_ebsilon/ebsilon_config.py +328 -226
  39. exerpy/parser/from_ebsilon/ebsilon_functions.py +205 -38
  40. exerpy/parser/from_ebsilon/ebsilon_parser.py +392 -255
  41. exerpy/parser/from_ebsilon/utils.py +16 -11
  42. exerpy/parser/from_tespy/tespy_config.py +33 -1
  43. exerpy/parser/from_tespy/tespy_parser.py +151 -0
  44. {exerpy-0.0.2.dist-info → exerpy-0.0.4.dist-info}/METADATA +43 -2
  45. exerpy-0.0.4.dist-info/RECORD +57 -0
  46. exerpy-0.0.2.dist-info/RECORD +0 -44
  47. {exerpy-0.0.2.dist-info → exerpy-0.0.4.dist-info}/WHEEL +0 -0
  48. {exerpy-0.0.2.dist-info → exerpy-0.0.4.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) -> Optional[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 = (pipe.Kind-1000)
61
+ fd.FluidType = pipe.Kind - 1000
65
62
 
66
63
  if fd.FluidType == 3 or fd.FluidType == 4: # steam or water
67
- t_sat = CP('T', 'P', pressure, 'Q', 0, 'water')
68
- if temperature > t_sat:
69
- fd.FluidType = 3 # steam
70
- fd.SteamTable = EpSteamTable.epSteamTableFromSuperiorModel
71
- fdAnalysis = app.NewFluidAnalysis()
72
- else:
73
- fd.FluidType == 4 # water
74
- fdAnalysis = app.NewFluidAnalysis()
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 ['S', 'H']:
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 == 'S': # Entropy
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 == 'H': # Enthalpy
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('h', pipe.H.Value, unit_id_to_string.get(pipe.H.Dimension, "Unknown")) # in SI unit [J / kg]
148
- s_i = convert_to_SI('s', pipe.S.Value, unit_id_to_string.get(pipe.S.Dimension, "Unknown")) # in SI unit [J / kgK]
149
- h_A = calc_X_from_PT(app, pipe, 'H', pressure, Tamb) # in SI unit [J / kg]
150
- s_A = calc_X_from_PT(app, pipe, 'S', pressure, Tamb) # in SI unit [J / kgK]
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('e', pipe.E.Value, unit_id_to_string.get(pipe.E.Dimension, "Unknown")) - calc_eT(app, pipe, pressure, Tamb, pamb)
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 eM
348
+ return properties