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.
Files changed (39) hide show
  1. exerpy/__init__.py +2 -4
  2. exerpy/analyses.py +597 -297
  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 +186 -119
  7. exerpy/components/heat_exchanger/condenser.py +96 -60
  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 +181 -63
  22. exerpy/components/turbomachinery/pump.py +182 -63
  23. exerpy/components/turbomachinery/turbine.py +182 -74
  24. exerpy/functions.py +388 -263
  25. exerpy/parser/from_aspen/aspen_config.py +57 -48
  26. exerpy/parser/from_aspen/aspen_parser.py +373 -280
  27. exerpy/parser/from_ebsilon/__init__.py +2 -2
  28. exerpy/parser/from_ebsilon/check_ebs_path.py +15 -19
  29. exerpy/parser/from_ebsilon/ebsilon_config.py +328 -226
  30. exerpy/parser/from_ebsilon/ebsilon_functions.py +205 -38
  31. exerpy/parser/from_ebsilon/ebsilon_parser.py +392 -255
  32. exerpy/parser/from_ebsilon/utils.py +16 -11
  33. exerpy/parser/from_tespy/tespy_config.py +32 -1
  34. exerpy/parser/from_tespy/tespy_parser.py +151 -0
  35. {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/METADATA +43 -2
  36. exerpy-0.0.3.dist-info/RECORD +48 -0
  37. exerpy-0.0.2.dist-info/RECORD +0 -44
  38. {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/WHEEL +0 -0
  39. {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -2,8 +2,7 @@ import logging
2
2
 
3
3
  import numpy as np
4
4
 
5
- from exerpy.components.component import Component
6
- from exerpy.components.component import component_registry
5
+ from exerpy.components.component import Component, component_registry
7
6
 
8
7
 
9
8
  @component_registry
@@ -282,7 +281,7 @@ class SimpleHeatExchanger(Component):
282
281
  If required inlet or outlet are missing.
283
282
  """
284
283
  # Validate the number of inlets and outlets
285
- if not hasattr(self, 'inl') or not hasattr(self, 'outl') or len(self.inl) < 1 or len(self.outl) < 1:
284
+ if not hasattr(self, "inl") or not hasattr(self, "outl") or len(self.inl) < 1 or len(self.outl) < 1:
286
285
  msg = "SimpleHeatExchanger requires at least one inlet and one outlet as well as one heat flow."
287
286
  logging.error(msg)
288
287
  raise ValueError(msg)
@@ -296,7 +295,7 @@ class SimpleHeatExchanger(Component):
296
295
  outlet = self.outl[0]
297
296
 
298
297
  # Calculate heat transfer Q
299
- Q = outlet['m'] * outlet['h'] - inlet['m'] * inlet['h']
298
+ Q = outlet["m"] * outlet["h"] - inlet["m"] * inlet["h"]
300
299
 
301
300
  # Initialize E_P and E_F
302
301
  self.E_P = 0.0
@@ -304,78 +303,87 @@ class SimpleHeatExchanger(Component):
304
303
 
305
304
  # Case 1: Heat is released (Q < 0)
306
305
  if Q < 0:
307
- if inlet['T'] >= T0 and outlet['T'] >= T0:
306
+ if inlet["T"] >= T0 and outlet["T"] >= T0:
308
307
  if split_physical_exergy:
309
- self.E_P = np.nan if getattr(self, 'dissipative', False) else inlet['m'] * (inlet['e_T'] - outlet['e_T'])
308
+ self.E_P = (
309
+ np.nan if getattr(self, "dissipative", False) else inlet["m"] * (inlet["e_T"] - outlet["e_T"])
310
+ )
310
311
  else:
311
- self.E_P = np.nan if getattr(self, 'dissipative', False) else inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
312
- self.E_F = inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
312
+ self.E_P = (
313
+ np.nan if getattr(self, "dissipative", False) else inlet["m"] * (inlet["e_PH"] - outlet["e_PH"])
314
+ )
315
+ self.E_F = inlet["m"] * (inlet["e_PH"] - outlet["e_PH"])
313
316
 
314
- elif inlet['T'] >= T0 and outlet['T'] < T0:
317
+ elif inlet["T"] >= T0 and outlet["T"] < T0:
315
318
  if split_physical_exergy:
316
- self.E_P = outlet['m'] * outlet['e_T']
317
- self.E_F = (inlet['m'] * inlet['e_T'] + outlet['m'] * outlet['e_T'] +
318
- (inlet['m'] * inlet['e_M'] - outlet['m'] * outlet['e_M']))
319
+ self.E_P = outlet["m"] * outlet["e_T"]
320
+ self.E_F = (
321
+ inlet["m"] * inlet["e_T"]
322
+ + outlet["m"] * outlet["e_T"]
323
+ + (inlet["m"] * inlet["e_M"] - outlet["m"] * outlet["e_M"])
324
+ )
319
325
  else:
320
- self.E_P = outlet['m'] * outlet['e_PH']
321
- self.E_F = inlet['m'] * inlet['e_PH']
326
+ self.E_P = outlet["m"] * outlet["e_PH"]
327
+ self.E_F = inlet["m"] * inlet["e_PH"]
322
328
 
323
- elif inlet['T'] <= T0 and outlet['T'] <= T0:
329
+ elif inlet["T"] <= T0 and outlet["T"] < T0:
324
330
  if split_physical_exergy:
325
- self.E_P = outlet['m'] * (outlet['e_T'] - inlet['e_T'])
326
- self.E_F = self.E_P + inlet['m'] * (inlet['e_M'] - outlet['m'] * outlet['e_M'])
331
+ self.E_P = outlet["m"] * (outlet["e_T"] - inlet["e_T"])
332
+ self.E_F = self.E_P + inlet["m"] * (inlet["e_M"] - outlet["m"] * outlet["e_M"])
327
333
  else:
328
- self.E_P = np.nan if getattr(self, 'dissipative', False) else \
329
- outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
330
- self.E_F = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
334
+ self.E_P = (
335
+ np.nan
336
+ if getattr(self, "dissipative", False)
337
+ else outlet["m"] * (outlet["e_PH"] - inlet["e_PH"])
338
+ )
339
+ self.E_F = outlet["m"] * (outlet["e_PH"] - inlet["e_PH"])
331
340
 
332
341
  else:
333
342
  # Unimplemented corner case
334
- logging.warning(
335
- "SimpleHeatExchanger: unimplemented case (Q < 0, T_in < T0 < T_out?)."
336
- )
343
+ logging.warning("SimpleHeatExchanger: unimplemented case (Q < 0, T_in < T0 < T_out?).")
337
344
  self.E_P = np.nan
338
345
  self.E_F = np.nan
339
346
 
340
347
  # Case 2: Heat is added (Q > 0)
341
348
  elif Q > 0:
342
- if inlet['T'] >= T0 and outlet['T'] >= T0:
349
+ if inlet["T"] >= T0 and outlet["T"] >= T0:
343
350
  if split_physical_exergy:
344
- self.E_P = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
345
- self.E_F = outlet['m'] * (outlet['e_T'] - inlet['e_T'])
351
+ self.E_P = outlet["m"] * (outlet["e_PH"] - inlet["e_PH"])
352
+ self.E_F = outlet["m"] * (outlet["e_T"] - inlet["e_T"])
346
353
  else:
347
- self.E_P = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
348
- self.E_F = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
349
- elif inlet['T'] < T0 and outlet['T'] > T0:
354
+ self.E_P = outlet["m"] * (outlet["e_PH"] - inlet["e_PH"])
355
+ self.E_F = outlet["m"] * (outlet["e_PH"] - inlet["e_PH"])
356
+ elif inlet["T"] < T0 and outlet["T"] >= T0:
350
357
  if split_physical_exergy:
351
- self.E_P = outlet['m'] * (outlet['e_T'] + inlet['e_T'])
352
- self.E_F = (inlet['m'] * inlet['e_T'] +
353
- (inlet['m'] * inlet['e_M'] - outlet['m'] * outlet['e_M']))
358
+ self.E_P = outlet["m"] * (outlet["e_T"] + inlet["e_T"])
359
+ self.E_F = inlet["m"] * inlet["e_T"] + (inlet["m"] * inlet["e_M"] - outlet["m"] * outlet["e_M"])
354
360
  else:
355
- self.E_P = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
356
- self.E_F = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
361
+ self.E_P = outlet["m"] * (outlet["e_PH"] - inlet["e_PH"])
362
+ self.E_F = outlet["m"] * (outlet["e_PH"] - inlet["e_PH"])
357
363
 
358
- elif inlet['T'] < T0 and outlet['T'] < T0:
364
+ elif inlet["T"] < T0 and outlet["T"] <= T0:
359
365
  if split_physical_exergy:
360
- self.E_P = np.nan if getattr(self, 'dissipative', False) else \
361
- inlet['m'] * (inlet['e_T'] - outlet['e_T']) + \
362
- (outlet['m'] * outlet['e_M'] - inlet['m'] * inlet['e_M'])
363
- self.E_F = inlet['m'] * (inlet['e_T'] - outlet['e_T'])
366
+ self.E_P = (
367
+ np.nan
368
+ if getattr(self, "dissipative", False)
369
+ else inlet["m"] * (inlet["e_T"] - outlet["e_T"])
370
+ + (outlet["m"] * outlet["e_M"] - inlet["m"] * inlet["e_M"])
371
+ )
372
+ self.E_F = inlet["m"] * (inlet["e_T"] - outlet["e_T"])
364
373
  else:
365
- self.E_P = np.nan if getattr(self, 'dissipative', False) else \
366
- inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
367
- self.E_F = inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
374
+ self.E_P = (
375
+ np.nan if getattr(self, "dissipative", False) else inlet["m"] * (inlet["e_PH"] - outlet["e_PH"])
376
+ )
377
+ self.E_F = inlet["m"] * (inlet["e_PH"] - outlet["e_PH"])
368
378
  else:
369
- logging.warning(
370
- "SimpleHeatExchanger: unimplemented case (Q > 0, T_in > T0 > T_out?)."
371
- )
379
+ logging.warning("SimpleHeatExchanger: unimplemented case (Q > 0, T_in > T0 > T_out?).")
372
380
  self.E_P = np.nan
373
381
  self.E_F = np.nan
374
382
 
375
383
  # Case 3: Fully dissipative or Q == 0
376
384
  else:
377
385
  self.E_P = np.nan
378
- self.E_F = inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
386
+ self.E_F = inlet["m"] * (inlet["e_PH"] - outlet["e_PH"])
379
387
 
380
388
  # Calculate exergy destruction
381
389
  if np.isnan(self.E_P):
@@ -388,93 +396,112 @@ class SimpleHeatExchanger(Component):
388
396
 
389
397
  # Log the results
390
398
  logging.info(
391
- f"SimpleHeatExchanger exergy balance calculated: "
399
+ f"Exergy balance of SimpleHeatExchanger {self.name} calculated: "
392
400
  f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
393
401
  f"Efficiency={self.epsilon:.2%}"
394
402
  )
395
403
 
396
-
397
404
  def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
398
405
  r"""
399
406
  This function must be implemented in the future.
400
407
 
401
408
  The exergoeconomic analysis of SimpleHeatExchanger is not implemented yet.
402
409
  """
403
-
404
- logging.error(
405
- "The exergoeconomic analysis of SimpleHeatExchanger is not implemented yet. "
406
- "This method will be implemented in a future release."
407
- )
408
- r"""
409
- Add auxiliary cost equations for the heat exchanger.
410
-
411
- For all cases:
412
-
413
- .. math::
414
- -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out}}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out}}
415
- + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in}}}\,\dot{C}^{\mathrm{Tc}}_{\mathrm{in}}
416
- = 0
417
410
 
418
- F rule for mechanical exergy:
419
-
420
- .. math::
421
- -\frac{1}{\dot{E}^{\mathrm{M}}_{\mathrm{out}}\,\dot{C}^{\mathrm{M}}_{\mathrm{out}}
422
- + \frac{1}{\dot{E}^{\mathrm{M}}_{\mathrm{in}}\,\dot{C}^{\mathrm{M}}_{\mathrm{in}}
423
- = 0
411
+ # Extract inlet and outlet
412
+ inlet = self.inl[0]
413
+ outlet = self.outl[0]
424
414
 
425
- F rule for chemical exergy:
415
+ # Calculate heat transfer Q
416
+ Q = outlet["m"] * outlet["h"] - inlet["m"] * inlet["h"]
417
+
418
+ # Extract temperatures
419
+ T_in = inlet["T"]
420
+ T_out = outlet["T"]
421
+
422
+ # Equality equation for mechanical exergy costs (c_M,in = c_M,out)
423
+ A[counter, inlet["CostVar_index"]["M"]] = 1 / inlet["E_M"] if inlet["e_M"] != 0 else 1
424
+ A[counter, outlet["CostVar_index"]["M"]] = -1 / outlet["E_M"] if outlet["e_M"] != 0 else -1
425
+ equations[counter] = {
426
+ "kind": "aux_equality",
427
+ "objects": [self.name, inlet["name"], outlet["name"]],
428
+ "property": "c_M",
429
+ }
430
+ b[counter] = 0
431
+ counter += 1
426
432
 
427
- .. math::
428
- -\frac{1}{\dot{E}^{\mathrm{CH}}_{\mathrm{out}}}\,\dot{C}^{\mathrm{CH}}_{\mathrm{out}}
429
- + \frac{1}{\dot{E}^{\mathrm{CH}}_{\mathrm{in}}}\,\dot{C}^{\mathrm{CH}}_{\mathrm{in}}
430
- = 0
433
+ # Equality equation for chemical exergy costs (c_CH,in = c_CH,out)
434
+ if chemical_exergy_enabled:
435
+ A[counter, inlet["CostVar_index"]["CH"]] = 1 / inlet["E_CH"] if inlet["e_CH"] != 0 else 1
436
+ A[counter, outlet["CostVar_index"]["CH"]] = -1 / outlet["E_CH"] if outlet["e_CH"] != 0 else -1
437
+ equations[counter] = {
438
+ "kind": "aux_equality",
439
+ "objects": [self.name, inlet["name"], outlet["name"]],
440
+ "property": "c_CH",
441
+ }
442
+ b[counter] = 0
443
+ counter += 1
444
+
445
+ # Thermal exergy cost equations
431
446
 
432
- """
433
- """# For all cases: c_Tin = c_Tout
434
- A[counter, self.inl[0]["CostVar_index"]["T"]] = (1 / self.inl[0]["e_T"]
435
- if self.inl[0]["e_T"] != 0 else 1)
436
- A[counter, self.outl[0]["CostVar_index"]["T"]] = (-1 / self.outl[0]["e_T"]
437
- if self.outl[0]["e_T"] != 0 else -1)
438
- equations[counter] = f"aux_equality_therm_{self.outl[0]['name']}"
447
+ # Case 1: Heat is released (Q < 0)
448
+ if Q < 0:
449
+ # Case 1.1: Both streams above ambient temperature
450
+ if T_in >= T0 and T_out >= T0:
451
+ # Apply F-rule to thermal exergy (c_T,in = c_T,out)
452
+ A[counter, inlet["CostVar_index"]["T"]] = 1 / inlet["E_T"] if inlet["e_T"] != 0 else 1
453
+ A[counter, outlet["CostVar_index"]["T"]] = -1 / outlet["E_T"] if outlet["e_T"] != 0 else -1
454
+ equations[counter] = {
455
+ "kind": "aux_f_rule",
456
+ "objects": [self.name, inlet["name"], outlet["name"]],
457
+ "property": "c_T",
458
+ }
459
+ b[counter] = 0
460
+ counter += 1
461
+
462
+ elif T_in >= T0 and T_out < T0:
463
+ # Tricky case: inlet above T0, outlet below T0
464
+ logging.warning(
465
+ f"SimpleHeatExchanger '{self.name}': Stream crossing ambient temperature "
466
+ f"during heat release not implemented in exergoeconomics yet!"
467
+ )
439
468
 
440
- b[counter] = 0
469
+ else:
470
+ # Tricky case: both streams below T0 while heat is released
471
+ logging.warning(
472
+ f"SimpleHeatExchanger '{self.name}': Both streams below T0 during heat release "
473
+ f"not implemented in exergoeconomics yet!"
474
+ )
441
475
 
442
- # For alle cases: c_Min = c_Mout
443
- A[counter+1, self.inl[0]["CostVar_index"]["M"]] = (1 / self.inl[0]["e_M"]
444
- if self.inl[0]["e_M"] != 0 else 1)
445
- A[counter+1, self.outl[0]["CostVar_index"]["M"]] = (-1 / self.outl[0]["e_M"]
446
- if self.outl[0]["e_M"] != 0 else 1)
447
- equations[counter+1] = f"aux_equality_mech_{self.outl[0]['name']}"
448
- b[counter+1] = 0
476
+ # Case 2: Heat is added (Q > 0)
477
+ elif Q > 0:
478
+ # Case 2.1: Both streams below ambient temperature
479
+ if T_in < T0 and T_out < T0:
480
+ # No auxiliary equation needed for thermal exergy
481
+ # The cost balance will determine c_T,out based on c_T,in and c_heat
482
+ pass
483
+
484
+ elif T_in < T0 and T_out >= T0:
485
+ # Tricky case: inlet below T0, outlet above T0
486
+ logging.warning(
487
+ f"SimpleHeatExchanger '{self.name}': Stream crossing ambient temperature "
488
+ f"during heat absorption not implemented in exergoeconomics yet!"
489
+ )
449
490
 
450
- # For all cases: c_CHin = c_CHout
451
- if chemical_exergy_enabled:
452
- A[counter+2, self.inl[0]["CostVar_index"]["CH"]] = (1 / self.inl[0]["e_CH"]
453
- if self.inl[0]["e_CH"] != 0 else 1)
454
- A[counter+2, self.outl[0]["CostVar_index"]["CH"]] = (-1 / self.outl[0]["e_CH"]
455
- if self.outl[0]["e_CH"] != 0 else 1)
456
- equations[counter+2] = f"aux_equality_chem_{self.outl[0]['name']}"
457
- b[counter+2] = 0
458
- counter += 3
459
- else:
460
- counter += 2
491
+ # Case 2.2: Both streams above ambient temperature
492
+ elif T_in >= T0 and T_out >= T0:
493
+ # No auxiliary equation needed for thermal exergy
494
+ # The cost balance will determine c_T,out based on c_T,in and c_heat
495
+ pass
461
496
 
462
- return A, b, counter, equations"""
463
-
464
- def exergoeconomic_balance(self, T0):
465
- r"""
466
- This function must be implemented in the future.
497
+ return A, b, counter, equations
467
498
 
468
- The exergoeconomic analysis of SimpleHeatExchanger is not implemented yet.
469
- """
470
-
471
- logging.error(
472
- "The exergoeconomic analysis of SimpleHeatExchanger is not implemented yet. "
473
- "This method will be implemented in a future release."
474
- )
499
+ def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False):
475
500
  r"""
476
501
  Perform exergoeconomic cost balance for the simple heat exchanger.
477
502
 
503
+ The general exergoeconomic balance equation is:
504
+
478
505
  .. math::
479
506
  \dot{C}^{\mathrm{T}}_{\mathrm{in}}
480
507
  + \dot{C}^{\mathrm{M}}_{\mathrm{in}}
@@ -483,30 +510,25 @@ class SimpleHeatExchanger(Component):
483
510
  + \dot{Z}
484
511
  = 0
485
512
 
486
- In case the chemical exergy of the streams is know:
513
+ In case the chemical exergy of the streams is known:
487
514
 
488
515
  .. math::
489
- \dot{C}^{\mathrm{CH}}_{\mathrm{in},1} =
490
- \dot{C}^{\mathrm{CH}}_{\mathrm{out},1}
516
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in}} =
517
+ \dot{C}^{\mathrm{CH}}_{\mathrm{out}}
491
518
 
492
- .. math::
493
- \dot{C}^{\mathrm{CH}}_{\mathrm{in},2} =
494
- \dot{C}^{\mathrm{CH}}_{\mathrm{out},2}
519
+ This method computes cost rates for product and fuel, and derives
520
+ exergoeconomic indicators based on the operating conditions.
495
521
 
496
- This method computes cost coefficients and ratios:
497
-
498
- **Heat release** :math:\dot{Q}<0
522
+ **Heat release** (:math:`\dot{Q} < 0`)
499
523
 
500
524
  Case 1: Both streams above ambient temperature
501
525
 
502
526
  .. math::
503
-
504
527
  \dot{E}_{\mathrm{P}}
505
528
  = \dot{E}^{\mathrm{T}}_{\mathrm{out}}
506
529
  - \dot{E}^{\mathrm{T}}_{\mathrm{in}}
507
530
 
508
531
  .. math::
509
-
510
532
  \dot{E}_{\mathrm{F}}
511
533
  = \dot{E}^{\mathrm{PH}}_{\mathrm{in}}
512
534
  - \dot{E}^{\mathrm{PH}}_{\mathrm{out}}
@@ -514,12 +536,10 @@ class SimpleHeatExchanger(Component):
514
536
  Case 2: Inlet above and outlet below ambient temperature
515
537
 
516
538
  .. math::
517
-
518
539
  \dot{E}_{\mathrm{P}}
519
540
  = \dot{E}^{\mathrm{T}}_{\mathrm{out}}
520
541
 
521
542
  .. math::
522
-
523
543
  \dot{E}_{\mathrm{F}}
524
544
  = \dot{E}^{\mathrm{T}}_{\mathrm{in}}
525
545
  + \dot{E}^{\mathrm{T}}_{\mathrm{out}}
@@ -529,31 +549,27 @@ class SimpleHeatExchanger(Component):
529
549
  Case 3: Both streams below ambient temperature
530
550
 
531
551
  .. math::
532
-
533
552
  \dot{E}_{\mathrm{P}}
534
553
  = \dot{E}^{\mathrm{T}}_{\mathrm{out}}
535
554
  - \dot{E}^{\mathrm{T}}_{\mathrm{in}}
536
555
 
537
556
  .. math::
538
-
539
557
  \dot{E}_{\mathrm{F}}
540
558
  = \bigl(\dot{E}^{\mathrm{T}}_{\mathrm{out}}
541
559
  - \dot{E}^{\mathrm{T}}_{\mathrm{in}}\bigr)
542
560
  + \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{in}}
543
561
  - \dot{E}^{\mathrm{M}}_{\mathrm{out}}\bigr)
544
562
 
545
- **Heat injection** :math:\dot{Q}
563
+ **Heat injection** (:math:`\dot{Q} > 0`)
546
564
 
547
565
  Case 1: Both streams above ambient temperature
548
566
 
549
567
  .. math::
550
-
551
568
  \dot{E}_{\mathrm{P}}
552
569
  = \dot{E}^{\mathrm{PH}}_{\mathrm{out}}
553
570
  - \dot{E}^{\mathrm{PH}}_{\mathrm{in}}
554
571
 
555
572
  .. math::
556
-
557
573
  \dot{E}_{\mathrm{F}}
558
574
  = \dot{E}^{\mathrm{T}}_{\mathrm{out}}
559
575
  - \dot{E}^{\mathrm{T}}_{\mathrm{in}}
@@ -561,13 +577,11 @@ class SimpleHeatExchanger(Component):
561
577
  Case 2: Inlet below and outlet above ambient temperature
562
578
 
563
579
  .. math::
564
-
565
580
  \dot{E}_{\mathrm{P}}
566
581
  = \dot{E}^{\mathrm{T}}_{\mathrm{out}}
567
582
  + \dot{E}^{\mathrm{T}}_{\mathrm{in}}
568
583
 
569
584
  .. math::
570
-
571
585
  \dot{E}_{\mathrm{F}}
572
586
  = \dot{E}^{\mathrm{T}}_{\mathrm{in}}
573
587
  + \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{in}}
@@ -576,7 +590,6 @@ class SimpleHeatExchanger(Component):
576
590
  Case 3: Both streams below ambient temperature
577
591
 
578
592
  .. math::
579
-
580
593
  \dot{E}_{\mathrm{P}}
581
594
  = \dot{E}^{\mathrm{T}}_{\mathrm{in}}
582
595
  - \dot{E}^{\mathrm{T}}_{\mathrm{out}}
@@ -584,25 +597,112 @@ class SimpleHeatExchanger(Component):
584
597
  - \dot{E}^{\mathrm{M}}_{\mathrm{in}}\bigr)
585
598
 
586
599
  .. math::
587
-
588
600
  \dot{E}_{\mathrm{F}}
589
601
  = \dot{E}^{\mathrm{T}}_{\mathrm{in}}
590
602
  - \dot{E}^{\mathrm{T}}_{\mathrm{out}}
591
603
 
592
- Fully dissipative or :math:\dot{Q}=0
604
+ **Fully dissipative or** :math:`\dot{Q} = 0`
593
605
 
594
606
  .. math::
595
-
596
607
  \dot{E}_{\mathrm{P}} = \mathrm{NaN}
597
608
 
598
609
  .. math::
599
-
600
610
  \dot{E}_{\mathrm{F}}
601
611
  = \dot{E}^{\mathrm{PH}}_{\mathrm{in}}
602
612
  - \dot{E}^{\mathrm{PH}}_{\mathrm{out}}
603
-
613
+
614
+ **Calculated exergoeconomic indicators:**
615
+
616
+ .. math::
617
+ c_{\mathrm{F}} = \frac{\dot{C}_{\mathrm{F}}}{\dot{E}_{\mathrm{F}}}
618
+
619
+ .. math::
620
+ c_{\mathrm{P}} = \frac{\dot{C}_{\mathrm{P}}}{\dot{E}_{\mathrm{P}}}
621
+
622
+ .. math::
623
+ \dot{C}_{\mathrm{D}} = c_{\mathrm{F}} \cdot \dot{E}_{\mathrm{D}}
624
+
625
+ .. math::
626
+ r = \frac{c_{\mathrm{P}} - c_{\mathrm{F}}}{c_{\mathrm{F}}}
627
+
628
+ .. math::
629
+ f = \frac{\dot{Z}}{\dot{Z} + \dot{C}_{\mathrm{D}}}
630
+
604
631
  Parameters
605
632
  ----------
606
633
  T0 : float
607
634
  Ambient temperature (K).
635
+ chemical_exergy_enabled : bool, optional
636
+ If True, chemical exergy is considered in the calculations.
637
+ Default is False.
638
+
639
+ Attributes Set
640
+ --------------
641
+ C_P : float
642
+ Cost rate of product (currency/time).
643
+ C_F : float
644
+ Cost rate of fuel (currency/time).
645
+ c_P : float
646
+ Specific cost of product (currency/energy).
647
+ c_F : float
648
+ Specific cost of fuel (currency/energy).
649
+ C_D : float
650
+ Cost rate of exergy destruction (currency/time).
651
+ r : float
652
+ Relative cost difference (dimensionless).
653
+ f : float
654
+ Exergoeconomic factor (dimensionless).
608
655
  """
656
+ inlet = self.inl[0]
657
+ outlet = self.outl[0]
658
+
659
+ # Determine heat transfer direction
660
+ Q = outlet["m"] * outlet["h"] - inlet["m"] * inlet["h"]
661
+
662
+ # Case 1: Heat is released (Q < 0)
663
+ if Q < 0:
664
+ if inlet["T"] >= T0 and outlet["T"] >= T0:
665
+ # Both streams above ambient
666
+ self.C_P = outlet["C_T"] - inlet["C_T"]
667
+ self.C_F = inlet["C_PH"] - outlet["C_PH"]
668
+ elif inlet["T"] >= T0 and outlet["T"] < T0:
669
+ # Inlet above, outlet below ambient
670
+ self.C_P = outlet["C_T"]
671
+ self.C_F = inlet["C_T"] + outlet["C_T"] + (inlet["C_M"] - outlet["C_M"])
672
+ elif inlet["T"] <= T0 and outlet["T"] < T0:
673
+ # Both streams below ambient
674
+ self.C_P = outlet["C_T"] - inlet["C_T"]
675
+ self.C_F = self.C_P + (inlet["C_M"] - outlet["C_M"])
676
+ else:
677
+ self.C_P = np.nan
678
+ self.C_F = np.nan
679
+
680
+ # Case 2: Heat is added (Q > 0)
681
+ elif Q > 0:
682
+ if inlet["T"] >= T0 and outlet["T"] >= T0:
683
+ # Both streams above ambient
684
+ self.C_P = outlet["C_PH"] - inlet["C_PH"]
685
+ self.C_F = outlet["C_T"] - inlet["C_T"]
686
+ elif inlet["T"] < T0 and outlet["T"] >= T0:
687
+ # Inlet below, outlet above ambient
688
+ self.C_P = outlet["C_T"] + inlet["C_T"]
689
+ self.C_F = inlet["C_T"] + (inlet["C_M"] - outlet["C_M"])
690
+ elif inlet["T"] < T0 and outlet["T"] <= T0:
691
+ # Both streams below ambient
692
+ self.C_P = inlet["C_T"] - outlet["C_T"] + (outlet["C_M"] - inlet["C_M"])
693
+ self.C_F = inlet["C_T"] - outlet["C_T"]
694
+ else:
695
+ self.C_P = np.nan
696
+ self.C_F = np.nan
697
+
698
+ # Case 3: Fully dissipative or Q == 0
699
+ else:
700
+ self.C_P = np.nan
701
+ self.C_F = inlet["C_PH"] - outlet["C_PH"]
702
+
703
+ # Calculate specific costs and exergoeconomic indicators
704
+ self.c_F = self.C_F / self.E_F if self.E_F else np.nan
705
+ self.c_P = self.C_P / self.E_P if self.E_P else np.nan
706
+ self.C_D = self.c_F * self.E_D if self.E_D else np.nan
707
+ self.r = (self.c_P - self.c_F) / self.c_F if self.c_F else np.nan
708
+ self.f = self.Z_costs / (self.Z_costs + self.C_D) if self.C_D else np.nan