exerpy 0.0.1__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 +157 -114
  5. exerpy/components/component.py +8 -8
  6. exerpy/components/heat_exchanger/base.py +593 -256
  7. exerpy/components/heat_exchanger/condenser.py +353 -166
  8. exerpy/components/heat_exchanger/simple.py +575 -225
  9. exerpy/components/heat_exchanger/steam_generator.py +153 -123
  10. exerpy/components/helpers/cycle_closer.py +61 -34
  11. exerpy/components/helpers/power_bus.py +117 -0
  12. exerpy/components/nodes/deaerator.py +221 -102
  13. exerpy/components/nodes/drum.py +50 -39
  14. exerpy/components/nodes/flash_tank.py +218 -43
  15. exerpy/components/nodes/mixer.py +296 -115
  16. exerpy/components/nodes/splitter.py +173 -0
  17. exerpy/components/nodes/storage.py +130 -0
  18. exerpy/components/piping/valve.py +351 -139
  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 +329 -227
  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.1.dist-info → exerpy-0.0.3.dist-info}/METADATA +45 -4
  36. exerpy-0.0.3.dist-info/RECORD +48 -0
  37. exerpy-0.0.1.dist-info/RECORD +0 -44
  38. {exerpy-0.0.1.dist-info → exerpy-0.0.3.dist-info}/WHEEL +0 -0
  39. {exerpy-0.0.1.dist-info → exerpy-0.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -2,24 +2,17 @@ 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
10
9
  class HeatExchanger(Component):
11
10
  r"""
12
- Class for exergy analysis of heat exchangers.
11
+ Class for exergy and exergoeconomic analysis of heat exchangers.
13
12
 
14
- This class performs exergy analysis calculations for heat exchangers, considering
15
- different temperature regimes relative to the ambient temperature. The exergy
16
- product and fuel definitions vary based on the temperature levels of the streams.
17
- Stream 0 represents the hot stream and stream 1 represents the cold stream.
18
-
19
- Parameters
20
- ----------
21
- **kwargs : dict
22
- Arbitrary keyword arguments passed to parent class.
13
+ This class performs exergy and exergoeconomic analysis calculations for heat exchanger components,
14
+ accounting for two inlet and two outlet streams across various temperature regimes, including
15
+ above and below ambient temperature, and optional dissipative behavior.
23
16
 
24
17
  Attributes
25
18
  ----------
@@ -32,133 +25,228 @@ class HeatExchanger(Component):
32
25
  epsilon : float
33
26
  Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
34
27
  inl : dict
35
- Dictionary containing inlet streams data (0: hot stream, 1: cold stream) with
36
- temperature, mass flows, and specific exergies.
28
+ Dictionary containing inlet stream data with mass flows and specific exergies.
37
29
  outl : dict
38
- Dictionary containing outlet streams data (0: hot stream, 1: cold stream) with
39
- temperature, mass flows, and specific exergies.
40
-
41
- Notes
42
- -----
43
- The exergy analysis considers six different cases based on stream temperatures
44
- relative to ambient temperature :math:`T_0`. Stream indices refer to hot (0) and
45
- cold (1) streams:
46
-
47
- Case 1 - **All streams above ambient temperature**:
48
-
49
- .. math::
50
- \dot{E}_\mathrm{P} &= \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{T}_{\mathrm{out,1}}
51
- - \dot{m}_{\mathrm{in,1}} \cdot e^\mathrm{T}_{\mathrm{in,1}}\\
52
- \dot{E}_\mathrm{F} &= \dot{m}_{\mathrm{in,0}} \cdot e^\mathrm{PH}_{\mathrm{in,0}}
53
- - \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{PH}_{\mathrm{out,0}} +
54
- \dot{m}_{\mathrm{in,1}} \cdot e^\mathrm{M}_{\mathrm{in,1}}
55
- - \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{M}_{\mathrm{out,1}}
56
-
57
- Case 2 - **All streams below or at ambient temperature**:
58
-
59
- .. math::
60
- \dot{E}_\mathrm{P} &= \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{T}_{\mathrm{out,0}}
61
- - \dot{m}_{\mathrm{in,0}} \cdot e^\mathrm{T}_{\mathrm{in,0}}\\
62
- \dot{E}_\mathrm{F} &= \dot{m}_{\mathrm{in,1}} \cdot e^\mathrm{PH}_{\mathrm{in,1}}
63
- - \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{PH}_{\mathrm{out,1}} +
64
- \dot{m}_{\mathrm{in,0}} \cdot e^\mathrm{M}_{\mathrm{in,0}}
65
- - \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{M}_{\mathrm{out,0}}
66
-
67
- Case 3 - **Hot stream inlet/outlet above ambient, cold stream inlet/outlet below ambient**:
68
-
69
- .. math::
70
- \dot{E}_\mathrm{P} &= \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{T}_{\mathrm{out,0}}
71
- + \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{T}_{\mathrm{out,1}}\\
72
- \dot{E}_\mathrm{F} &= \dot{m}_{\mathrm{in,0}} \cdot e^\mathrm{PH}_{\mathrm{in,0}}
73
- + \dot{m}_{\mathrm{in,1}} \cdot e^\mathrm{PH}_{\mathrm{in,1}}
74
- - (\dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{M}_{\mathrm{out,0}}
75
- + \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{M}_{\mathrm{out,1}})
76
-
77
- Case 4 - **First inlet above ambient, all other streams below or at ambient**:
78
-
79
- .. math::
80
- \dot{E}_\mathrm{P} &= \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{T}_{\mathrm{out,0}}\\
81
- \dot{E}_\mathrm{F} &= \dot{m}_{\mathrm{in,0}} \cdot e^\mathrm{PH}_{\mathrm{in,0}}
82
- + \dot{m}_{\mathrm{in,1}} \cdot e^\mathrm{PH}_{\mathrm{in,1}}
83
- - (\dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{PH}_{\mathrm{out,1}}
84
- + \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{M}_{\mathrm{out,0}})
85
-
86
- Case 5 - **Hot stream inlet/outlet above ambient, cold stream inlet/outlet below or at ambient**:
87
-
88
- .. math::
89
- \dot{E}_\mathrm{P} &= \mathrm{NaN}\\
90
- \dot{E}_\mathrm{F} &= \dot{m}_{\mathrm{in,0}} \cdot e^\mathrm{PH}_{\mathrm{in,0}}
91
- - \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{PH}_{\mathrm{out,0}}
92
- + \dot{m}_{\mathrm{in,1}} \cdot e^\mathrm{PH}_{\mathrm{in,1}}
93
- - \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{PH}_{\mathrm{out,1}}
94
-
95
- Case 6 - **Second outlet above ambient, all others below or at ambient**:
96
-
97
- .. math::
98
- \dot{E}_\mathrm{P} &= \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{T}_{\mathrm{out,1}}\\
99
- \dot{E}_\mathrm{F} &= \dot{m}_{\mathrm{in,0}} \cdot e^\mathrm{PH}_{\mathrm{in,0}}
100
- - \dot{m}_{\mathrm{out,0}} \cdot e^\mathrm{PH}_{\mathrm{out,0}}
101
- + \dot{m}_{\mathrm{in,1}} \cdot e^\mathrm{PH}_{\mathrm{in,1}}
102
- - \dot{m}_{\mathrm{out,1}} \cdot e^\mathrm{M}_{\mathrm{out,1}}
103
-
104
- Note that in Case 5, the exergy product :math:`\dot{E}_\mathrm{P}` is undefined (NaN),
105
- leading to an exergy destruction equal to the exergy fuel:
106
- :math:`\dot{E}_\mathrm{D} = \dot{E}_\mathrm{F}`.
107
-
108
- The exergy destruction is calculated as:
109
-
110
- .. math::
111
- \dot{E}_\mathrm{D} = \dot{E}_\mathrm{F} - \dot{E}_\mathrm{P}
112
-
113
- And the exergetic efficiency as:
114
-
115
- .. math::
116
- \varepsilon = \frac{\dot{E}_\mathrm{P}}{\dot{E}_\mathrm{F}}
117
-
118
- Where:
119
- - :math:`e^\mathrm{T}`: Thermal exergy
120
- - :math:`e^\mathrm{PH}`: Physical exergy
121
- - :math:`e^\mathrm{M}`: Mechanical exergy
30
+ Dictionary containing outlet stream data with mass flows and specific exergies.
31
+ Z_costs : float
32
+ Investment cost rate of the component in currency/h.
33
+ C_P : float
34
+ Cost of product stream :math:`\dot{C}_P` in currency/h.
35
+ C_F : float
36
+ Cost of fuel stream :math:`\dot{C}_F` in currency/h.
37
+ C_D : float
38
+ Cost of exergy destruction :math:`\dot{C}_D` in currency/h.
39
+ c_P : float
40
+ Specific cost of product stream (currency per unit exergy).
41
+ c_F : float
42
+ Specific cost of fuel stream (currency per unit exergy).
43
+ r : float
44
+ Relative cost difference, :math:`(c_P - c_F)/c_F`.
45
+ f : float
46
+ Exergoeconomic factor, :math:`\dot{Z}/(\dot{Z} + \dot{C}_D)`.
47
+ Ex_C_col : dict
48
+ Custom cost coefficients collection passed via `kwargs`.
122
49
  """
123
50
 
124
51
  def __init__(self, **kwargs):
125
- r"""Initialize heat exchanger component with given parameters."""
52
+ r"""
53
+ Initialize the heat exchanger component.
54
+
55
+ Parameters
56
+ ----------
57
+ **kwargs : dict
58
+ Arbitrary keyword arguments. Recognized keys:
59
+ - dissipative (bool): whether component has dissipative behavior, default False
60
+ - Ex_C_col (dict): custom cost coefficients, default {}
61
+ - Z_costs (float): investment cost rate in currency/h, default 0.0
62
+ """
126
63
  self.dissipative = False
127
64
  super().__init__(**kwargs)
128
65
 
129
66
  def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
130
67
  r"""
131
- Calculate the exergy balance of the heat exchanger.
68
+ Compute the exergy balance of the heat exchanger.
69
+
70
+ Case 1: All streams above ambient temperature
71
+
72
+ If `split_physical_exergy=True`:
73
+
74
+ .. math::
75
+
76
+ \dot{E}_{\mathrm{P}}
77
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
78
+ - \dot{E}^{\mathrm{T}}_{\mathrm{in},2}
79
+
80
+ .. math::
81
+
82
+ \dot{E}_{\mathrm{F}}
83
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
84
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
85
+ + \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{in},2}
86
+ - \dot{E}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
87
+
88
+ Else:
89
+
90
+ .. math::
91
+
92
+ \dot{E}_{\mathrm{P}}
93
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
94
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
95
+
96
+ .. math::
97
+
98
+ \dot{E}_{\mathrm{F}}
99
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
100
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
101
+
102
+ Case 2: All streams below or equal to ambient temperature
103
+
104
+ If `split_physical_exergy=True`:
105
+
106
+ .. math::
107
+ \dot{E}_{\mathrm{P}}
108
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
109
+ - \dot{E}^{\mathrm{T}}_{\mathrm{in},1}
110
+
111
+ .. math::
112
+
113
+ \dot{E}_{\mathrm{F}}
114
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
115
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
116
+ + \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{in},1}
117
+ - \dot{E}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
118
+
119
+ Else
120
+
121
+ .. math::
122
+ \dot{E}_{\mathrm{P}}
123
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
124
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
125
+
126
+ .. math::
127
+ \dot{E}_{\mathrm{F}}
128
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
129
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
130
+
131
+ Case 3: Both stream crossing ambient temperature
132
+
133
+ If `split_physical_exergy=True`:
134
+
135
+ .. math::
136
+ \dot{E}_{\mathrm{P}}
137
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
138
+ + \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
139
+
140
+ .. math::
141
+ \dot{E}_{\mathrm{F}}
142
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
143
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
144
+ - \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{out},1}
145
+ + \dot{E}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
146
+
147
+ Else:
132
148
 
133
- Performs exergy balance calculations considering different temperature regimes
134
- relative to the ambient temperature. The method automatically determines the
135
- appropriate case based on stream temperatures.
149
+ .. math::
150
+ \dot{E}_{\mathrm{P}}
151
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
152
+ + \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
153
+
154
+ .. math::
155
+ \dot{E}_{\mathrm{F}}
156
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
157
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
158
+
159
+ Case 4: Only the hot inlet above ambient temperature
160
+
161
+ If `split_physical_exergy=True`:
162
+
163
+ .. math::
164
+ \dot{E}_{\mathrm{P}}
165
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
166
+
167
+ .. math::
168
+ \dot{E}_{\mathrm{F}}
169
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
170
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}\bigr)
171
+ - \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
172
+ + \dot{E}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
173
+
174
+ Else:
175
+
176
+ .. math::
177
+ \dot{E}_{\mathrm{P}}
178
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
179
+
180
+ .. math::
181
+ \dot{E}_{\mathrm{F}}
182
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
183
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}\bigr)
184
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
185
+
186
+ Case 5: Only the cold inlet below ambient temperature
187
+
188
+ If `split_physical_exergy=True`:
189
+
190
+ .. math::
191
+ \dot{E}_{\mathrm{P}}
192
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
193
+
194
+ .. math::
195
+ \dot{E}_{\mathrm{F}}
196
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
197
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
198
+ + \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
199
+ - \dot{E}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
200
+
201
+ Else:
202
+
203
+ .. math::
204
+ \dot{E}_{\mathrm{P}}
205
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
206
+
207
+ .. math::
208
+ \dot{E}_{\mathrm{F}}
209
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
210
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
211
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
212
+
213
+ Case 6: Hot stream always above and cold stream always below ambiente temperature (dissipative case):
214
+
215
+ .. math::
216
+ \dot{E}_{\mathrm{P}} = \mathrm{NaN}
217
+
218
+ .. math::
219
+ \dot{E}_{\mathrm{F}}
220
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
221
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
222
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
223
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
224
+
225
+ If `dissipative` is `True`, the component is treated as dissipative:
226
+
227
+ .. math::
228
+ \dot{E}_{\mathrm{P}} = \mathrm{NaN}
229
+
230
+ .. math::
231
+ \dot{E}_{\mathrm{F}}
232
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
233
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
234
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
235
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
136
236
 
137
237
  Parameters
138
238
  ----------
139
239
  T0 : float
140
- Ambient temperature in :math:`\mathrm{K}`.
240
+ Ambient temperature (K).
141
241
  p0 : float
142
- Ambient pressure in :math:`\mathrm{Pa}`.
242
+ Ambient pressure (Pa).
143
243
  split_physical_exergy : bool
144
- Flag indicating whether physical exergy is split into thermal and mechanical components.
244
+ Whether to split thermal and mechanical exergy.
145
245
 
146
246
  Raises
147
247
  ------
148
248
  ValueError
149
- If the required inlet and outlet streams are not properly defined.
150
-
151
- Notes
152
- -----
153
- This method updates the following component attributes:
154
- - E_P (Exergy product)
155
- - E_F (Exergy fuel)
156
- - E_D (Exergy destruction)
157
- - epsilon (Exergetic efficiency)
158
-
159
- The calculation requires two inlet and two outlet streams, and their
160
- temperature relationships with ambient temperature determine which case
161
- of exergy analysis is applied.
249
+ If required inlets or outlets are missing.
162
250
  """
163
251
  # Ensure that the component has both inlet and outlet streams
164
252
  if len(self.inl) < 2 or len(self.outl) < 2:
@@ -169,80 +257,125 @@ class HeatExchanger(Component):
169
257
 
170
258
  if not self.dissipative:
171
259
  # Case 1: All streams are above the ambient temperature
172
- if all([stream['T'] >= T0 for stream in all_streams]):
260
+ if all([stream["T"] >= T0 for stream in all_streams]):
173
261
  if split_physical_exergy:
174
- self.E_P = self.outl[1]['m'] * self.outl[1]['e_T'] - self.inl[1]['m'] * self.inl[1]['e_T']
175
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH'] + (
176
- self.inl[1]['m'] * self.inl[1]['e_M'] - self.outl[1]['m'] * self.outl[1]['e_M'])
262
+ self.E_P = self.outl[1]["m"] * self.outl[1]["e_T"] - self.inl[1]["m"] * self.inl[1]["e_T"]
263
+ self.E_F = (
264
+ self.inl[0]["m"] * self.inl[0]["e_PH"]
265
+ - self.outl[0]["m"] * self.outl[0]["e_PH"]
266
+ + (self.inl[1]["m"] * self.inl[1]["e_M"] - self.outl[1]["m"] * self.outl[1]["e_M"])
267
+ )
177
268
  else:
178
- self.E_P = self.outl[1]['m'] * self.outl[1]['e_PH'] - self.inl[1]['m'] * self.inl[1]['e_PH']
179
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH']
269
+ self.E_P = self.outl[1]["m"] * self.outl[1]["e_PH"] - self.inl[1]["m"] * self.inl[1]["e_PH"]
270
+ self.E_F = self.inl[0]["m"] * self.inl[0]["e_PH"] - self.outl[0]["m"] * self.outl[0]["e_PH"]
180
271
 
181
272
  # Case 2: All streams are below or equal to the ambient temperature
182
- elif all([stream['T'] <= T0 for stream in all_streams]):
273
+ elif all([stream["T"] <= T0 for stream in all_streams]):
183
274
  if split_physical_exergy:
184
- self.E_P = self.outl[0]['m'] * self.outl[0]['e_T'] - self.inl[0]['m'] * self.inl[0]['e_T']
185
- self.E_F = self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_PH'] + (
186
- self.inl[0]['m'] * self.inl[0]['e_M'] - self.outl[0]['m'] * self.outl[0]['e_M'])
275
+ self.E_P = self.outl[0]["m"] * self.outl[0]["e_T"] - self.inl[0]["m"] * self.inl[0]["e_T"]
276
+ self.E_F = (
277
+ self.inl[1]["m"] * self.inl[1]["e_PH"]
278
+ - self.outl[1]["m"] * self.outl[1]["e_PH"]
279
+ + (self.inl[0]["m"] * self.inl[0]["e_M"] - self.outl[0]["m"] * self.outl[0]["e_M"])
280
+ )
187
281
  else:
188
- logging.warning("While dealing with heat exchnager below ambient temperautre, "
189
- "physical exergy should be split into thermal and mechanical components!")
190
- self.E_P = self.outl[0]['m'] * self.outl[0]['e_PH'] - self.inl[0]['m'] * self.inl[0]['e_PH']
191
- self.E_F = self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_PH']
192
-
193
- # Case 3: Hot stream from above to lower ambient, cold stream from lower to above ambient
194
- elif (self.inl[0]['T'] > T0 and self.outl[1]['T'] > T0 and
195
- self.outl[0]['T'] <= T0 and self.inl[1]['T'] <= T0):
282
+ logging.warning(
283
+ "While dealing with heat exchnager below ambient temperautre, "
284
+ "physical exergy should be split into thermal and mechanical components!"
285
+ )
286
+ self.E_P = self.outl[0]["m"] * self.outl[0]["e_PH"] - self.inl[0]["m"] * self.inl[0]["e_PH"]
287
+ self.E_F = self.inl[1]["m"] * self.inl[1]["e_PH"] - self.outl[1]["m"] * self.outl[1]["e_PH"]
288
+
289
+ # Case 3: Both stream crossing T0 (hot inlet and cold outlet > T0, hot outlet and cold inlet <= T0)
290
+ elif (
291
+ self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0
292
+ ):
196
293
  if split_physical_exergy:
197
- self.E_P = self.outl[0]['m'] * self.outl[0]['e_T'] + self.outl[1]['m'] * self.outl[1]['e_T']
198
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] + self.inl[1]['m'] * self.inl[1]['e_PH'] - (
199
- self.outl[0]['m'] * self.outl[0]['e_M'] + self.outl[1]['m'] * self.outl[1]['e_M'])
294
+ self.E_P = self.outl[0]["m"] * self.outl[0]["e_T"] + self.outl[1]["m"] * self.outl[1]["e_T"]
295
+ self.E_F = (
296
+ self.inl[0]["m"] * self.inl[0]["e_PH"]
297
+ + self.inl[1]["m"] * self.inl[1]["e_PH"]
298
+ - (self.outl[0]["m"] * self.outl[0]["e_M"] + self.outl[1]["m"] * self.outl[1]["e_M"])
299
+ )
200
300
  else:
201
- logging.warning("While dealing with heat exchnager below ambient temperautre, "
202
- "physical exergy should be split into thermal and mechanical components!")
203
- self.E_P = self.outl[0]['m'] * self.outl[0]['e_PH'] + self.outl[1]['m'] * self.outl[1]['e_PH']
204
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] + self.inl[1]['m'] * self.inl[1]['e_PH']
205
-
206
- # Case 4: Hot stream inlet above ambient, all others below or equal to ambient
207
- elif (self.inl[0]['T'] > T0 and self.inl[1]['T'] <= T0 and
208
- self.outl[0]['T'] <= T0 and self.outl[1]['T'] <= T0):
301
+ logging.warning(
302
+ "While dealing with heat exchnager below ambient temperautre, "
303
+ "physical exergy should be split into thermal and mechanical components!"
304
+ )
305
+ self.E_P = self.outl[0]["m"] * self.outl[0]["e_PH"] + self.outl[1]["m"] * self.outl[1]["e_PH"]
306
+ self.E_F = self.inl[0]["m"] * self.inl[0]["e_PH"] + self.inl[1]["m"] * self.inl[1]["e_PH"]
307
+
308
+ # Case 4: Only hot inlet > T0
309
+ elif (
310
+ self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0
311
+ ):
209
312
  if split_physical_exergy:
210
- self.E_P = self.outl[0]['m'] * self.outl[0]['e_T']
211
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] + self.inl[1]['m'] * self.inl[1]['e_PH'] - (
212
- self.outl[1]['m'] * self.outl[1]['e_PH'] + self.outl[0]['m'] * self.outl[0]['e_M'])
313
+ self.E_P = self.outl[0]["m"] * self.outl[0]["e_T"]
314
+ self.E_F = (
315
+ self.inl[0]["m"] * self.inl[0]["e_PH"]
316
+ + self.inl[1]["m"] * self.inl[1]["e_PH"]
317
+ - (self.outl[1]["m"] * self.outl[1]["e_PH"] + self.outl[0]["m"] * self.outl[0]["e_M"])
318
+ )
213
319
  else:
214
- logging.warning("While dealing with heat exchnager below ambient temperautre, "
215
- "physical exergy should be split into thermal and mechanical components!")
216
- self.E_P = self.outl[0]['m'] * self.outl[0]['e_PH']
217
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] + (
218
- self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_PH'])
219
-
220
- # Case 5: Inlets are higher but outlets are below or equal to ambient
221
- elif (self.inl[0]['T'] > T0 and self.outl[0]['T'] > T0 and
222
- self.inl[1]['T'] <= T0 and self.outl[1]['T'] <= T0):
223
- self.E_P = np.nan
224
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH'] + (
225
- self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_PH'])
226
-
227
- # Case 6: Cold inlet is lower ambient, others higher
228
- else:
320
+ logging.warning(
321
+ "While dealing with heat exchnager below ambient temperautre, "
322
+ "physical exergy should be split into thermal and mechanical components!"
323
+ )
324
+ self.E_P = self.outl[0]["m"] * self.outl[0]["e_PH"]
325
+ self.E_F = self.inl[0]["m"] * self.inl[0]["e_PH"] + (
326
+ self.inl[1]["m"] * self.inl[1]["e_PH"] - self.outl[1]["m"] * self.outl[1]["e_PH"]
327
+ )
328
+
329
+ # Case 5: Only cold inlet <= T0
330
+ elif self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0:
229
331
  if split_physical_exergy:
230
- self.E_P = self.outl[1]['m'] * self.outl[1]['e_T']
231
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH'] + (
232
- self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_M'])
332
+ self.E_P = self.outl[1]["m"] * self.outl[1]["e_T"]
333
+ self.E_F = (
334
+ self.inl[0]["m"] * self.inl[0]["e_PH"]
335
+ - self.outl[0]["m"] * self.outl[0]["e_PH"]
336
+ + (self.inl[1]["m"] * self.inl[1]["e_PH"] - self.outl[1]["m"] * self.outl[1]["e_M"])
337
+ )
233
338
  else:
234
- logging.warning("While dealing with heat exchnager below ambient temperautre, "
235
- "physical exergy should be split into thermal and mechanical components!")
236
- self.E_P = self.outl[1]['m'] * self.outl[1]['e_PH']
237
- self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH'] + (
238
- self.inl[1]['m'] * self.inl[1]['e_PH'])
339
+ logging.warning(
340
+ "While dealing with heat exchnager below ambient temperautre, "
341
+ "physical exergy should be split into thermal and mechanical components!"
342
+ )
343
+ self.E_P = self.outl[1]["m"] * self.outl[1]["e_PH"]
344
+ self.E_F = (
345
+ self.inl[0]["m"] * self.inl[0]["e_PH"]
346
+ - self.outl[0]["m"] * self.outl[0]["e_PH"]
347
+ + (self.inl[1]["m"] * self.inl[1]["e_PH"])
348
+ )
349
+
350
+ # Case 6: hot stream always above T0, cold stream always below T0 (dissipative case)
351
+ elif (
352
+ self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0
353
+ ):
354
+ self.E_P = np.nan
355
+ self.E_F = (
356
+ self.inl[0]["m"] * self.inl[0]["e_PH"]
357
+ - self.outl[0]["m"] * self.outl[0]["e_PH"]
358
+ + (self.inl[1]["m"] * self.inl[1]["e_PH"] - self.outl[1]["m"] * self.outl[1]["e_PH"])
359
+ )
360
+
361
+ logging.warning(
362
+ f"Component {self.name} is dissipative. This component should be "
363
+ "handled with the `dissipative` flag set to True."
364
+ )
365
+
366
+ # Case 7: Not implemented case
367
+ else:
368
+ logging.error(
369
+ f"The heat exchanger {self.name} has an unexpected temperature configuration. "
370
+ "Please check the inlet and outlet temperatures."
371
+ )
239
372
 
240
373
  else:
241
374
  self.E_F = (
242
- self.inl[0]['m'] * self.inl[0]['e_PH']
243
- - self.outl[0]['m'] * self.outl[0]['e_PH']
244
- - self.outl[1]['m'] * self.outl[1]['e_PH']
245
- + self.inl[1]['m'] * self.inl[1]['e_PH']
375
+ self.inl[0]["m"] * self.inl[0]["e_PH"]
376
+ - self.outl[0]["m"] * self.outl[0]["e_PH"]
377
+ - self.outl[1]["m"] * self.outl[1]["e_PH"]
378
+ + self.inl[1]["m"] * self.inl[1]["e_PH"]
246
379
  )
247
380
  self.E_P = np.nan
248
381
  # Calculate exergy destruction and efficiency
@@ -254,42 +387,114 @@ class HeatExchanger(Component):
254
387
 
255
388
  # Log the results
256
389
  logging.info(
257
- f"HeatExchanger exergy balance calculated: "
390
+ f"Exergy balance of HeatExchanger {self.name} calculated: "
258
391
  f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
259
392
  f"Efficiency={self.epsilon:.2%}"
260
393
  )
261
394
 
262
-
263
395
  def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
264
- """Set up auxiliary equations for heat exchanger exergy cost analysis.
265
- This method constructs auxiliary equations for thermal, mechanical, and chemical
266
- exergy costs in a heat exchanger. It considers different thermal cases based on the
267
- temperatures of the streams relative to the reference temperature T0.
396
+ r"""
397
+ Add auxiliary cost equations for the heat exchanger.
398
+
399
+ This method appends rows to the cost matrix to enforce:
400
+
401
+ Case 1: All streams above ambient temperature
402
+
403
+ F rule for thermal exergy of the hot stream:
404
+
405
+ .. math::
406
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},1}
407
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},1}
408
+ = 0
409
+
410
+ Case 2: All streams below or equal to ambient temperature
411
+
412
+ F rule for thermal exergy of the cold stream:
413
+
414
+ .. math::
415
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},2}
416
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},2}
417
+ = 0
418
+
419
+ Case 3: Both stream crossing ambient temperature
420
+
421
+ P rule for thermal exergy of both outlets:
422
+
423
+ .. math::
424
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},1}
425
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},2}
426
+ = 0
427
+
428
+ Case 4: Only the hot inlet above ambient temperature
429
+
430
+ F rule for thermal exergy of the cold stream:
431
+
432
+ .. math::
433
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},2}
434
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},2}
435
+ = 0
436
+
437
+ Case 5: Only the cold inlet below ambient temperature
438
+
439
+ F rule for thermal exergy of the hot stream:
440
+
441
+ .. math::
442
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},1}
443
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},1}
444
+ = 0
445
+
446
+ Case 6: Hot stream always above and cold stream always below ambiente temperature (dissipative case):
447
+
448
+ The dissipative is not handeld here!
449
+
450
+ For all cases, the mechanical and chemical exergy costs are handled as follows:
451
+
452
+ F rule for mechanical exergy of the hot stream:
453
+
454
+ .. math::
455
+ -\frac{1}{\dot{E}^{\mathrm{M}}_{\mathrm{out},i}}\,\dot{C}^{\mathrm{M}}_{\mathrm{out},i}
456
+ + \frac{1}{\dot{E}^{\mathrm{M}}_{\mathrm{in},i}}\,\dot{C}^{\mathrm{M}}_{\mathrm{in},i}
457
+ = 0
458
+
459
+ F rule for chemical exergy on hot branch:
460
+
461
+ .. math::
462
+ -\frac{1}{\dot{E}^{\mathrm{CH}}_{\mathrm{out},i}}\,\dot{C}^{\mathrm{CH}}_{\mathrm{out},i}
463
+ + \frac{1}{\dot{E}^{\mathrm{CH}}_{\mathrm{in},i}}\,\dot{C}^{\mathrm{CH}}_{\mathrm{in},i}
464
+ = 0
465
+
268
466
  Parameters
269
467
  ----------
270
468
  A : numpy.ndarray
271
- Coefficient matrix for the linear equation system.
469
+ Current cost matrix.
272
470
  b : numpy.ndarray
273
- Right-hand side vector of the linear equation system.
471
+ Current RHS vector.
274
472
  counter : int
275
- Current row counter in the linear equation system.
473
+ Starting row index for auxiliary equations.
276
474
  T0 : float
277
- Reference temperature for exergy calculations.
278
- equations : dict
279
- Dictionary to store equation descriptions.
475
+ Ambient temperature (K).
476
+ equations : dict or list
477
+ Structure for equation labels.
280
478
  chemical_exergy_enabled : bool
281
- Flag indicating whether chemical exergy analysis is enabled.
479
+ Must be True to include chemical exergy mixing.
480
+
282
481
  Returns
283
482
  -------
284
483
  A : numpy.ndarray
285
- Updated coefficient matrix.
484
+ Updated cost matrix.
286
485
  b : numpy.ndarray
287
- Updated right-hand side vector.
486
+ Updated RHS vector.
288
487
  counter : int
289
- Updated row counter.
290
- equations : dict
291
- Updated dictionary of equation descriptions.
488
+ Updated row index after adding equations.
489
+ equations : dict or list
490
+ Updated labels.
491
+
492
+ Raises
493
+ ------
494
+ ValueError
495
+ If required cost variable indices are missing.
292
496
  """
497
+
293
498
  # Equality equation for mechanical and chemical exergy costs.
294
499
  def set_equal(A, row, in_item, out_item, var):
295
500
  if in_item["e_" + var] != 0 and out_item["e_" + var] != 0:
@@ -346,48 +551,85 @@ class HeatExchanger(Component):
346
551
  # Case 1: All temperatures > T0.
347
552
  if all([c["T"] > T0 for c in list(self.inl.values()) + list(self.outl.values())]):
348
553
  set_thermal_f_hot(A, counter + 0)
349
- equations[counter] = f"aux_f_rule_hot_{self.name}"
554
+ equations[counter] = {
555
+ "kind": "aux_f_rule_hot",
556
+ "objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
557
+ "property": "c_T",
558
+ }
350
559
  # Case 2: All temperatures <= T0.
351
560
  elif all([c["T"] <= T0 for c in list(self.inl.values()) + list(self.outl.values())]):
352
561
  set_thermal_f_cold(A, counter + 0)
353
- equations[counter] = f"aux_f_rule_cold_{self.name}"
354
- # Case 3: Mixed temperatures: inl[0]["T"] > T0 and outl[1]["T"] > T0, while outl[0]["T"] <= T0 and inl[1]["T"] <= T0.
355
- elif (self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and
356
- self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0):
562
+ equations[counter] = {
563
+ "kind": "aux_f_rule_cold",
564
+ "objects": [self.name, self.inl[1]["name"], self.outl[1]["name"]],
565
+ "property": "c_T",
566
+ }
567
+ # Case 3: Both stream crossing T0 (hot inlet and cold outlet > T0, hot outlet and cold inlet <= T0)
568
+ elif self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0:
357
569
  set_thermal_p_rule(A, counter + 0)
358
- equations[counter] = f"aux_p_rule_{self.name}"
359
- # Case 4: Mixed temperatures: inl[0]["T"] > T0, inl[1]["T"] <= T0, and both outl[0]["T"] and outl[1]["T"] <= T0.
360
- elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
361
- self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0):
570
+ equations[counter] = {
571
+ "kind": "aux_p_rule",
572
+ "objects": [self.name, self.outl[0]["name"], self.outl[1]["name"]],
573
+ "property": "c_T",
574
+ }
575
+ # Case 4: Only hot inlet > T0
576
+ elif self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0:
362
577
  set_thermal_f_cold(A, counter + 0)
363
- equations[counter] = f"aux_f_rule_cold_{self.name}"
364
- # Case 5: Mixed temperatures: inl[0]["T"] > T0, inl[1]["T"] <= T0, and both outl[0]["T"] and outl[1]["T"] > T0.
365
- elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
366
- self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0):
578
+ equations[counter] = {
579
+ "kind": "aux_f_rule_cold",
580
+ "objects": [self.name, self.inl[1]["name"], self.outl[1]["name"]],
581
+ "property": "c_T",
582
+ }
583
+ # Case 5: Only cold inlet <= T0
584
+ elif self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0:
367
585
  set_thermal_f_hot(A, counter + 0)
368
- equations[counter] = f"aux_f_rule_hot_{self.name}"
369
- # Case 6: Mixed temperatures (dissipative case): inl[0]["T"] > T0, inl[1]["T"] <= T0, outl[0]["T"] > T0, and outl[1]["T"] <= T0.
370
- elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
371
- self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0):
372
- print("you shouldn't see this")
586
+ equations[counter] = {
587
+ "kind": "aux_f_rule_hot",
588
+ "objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
589
+ "property": "c_T",
590
+ }
591
+ # Case 6: hot stream always above T0, cold stream always below T0 (dissipative case)
592
+ elif self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0:
593
+ logging.warning(
594
+ f"Component {self.name} is dissipative. This component should be "
595
+ "handled with the `dissipative` flag set to True."
596
+ )
373
597
  return
374
- # Case 7: Default case.
598
+ # Case 7: Not implemented case
375
599
  else:
376
- set_thermal_f_hot(A, counter + 0)
377
- equations[counter] = f"aux_f_rule_hot_{self.name}"
378
-
600
+ logging.error(
601
+ f"The heat exchanger {self.name} has an unexpected temperature configuration. "
602
+ "Please check the inlet and outlet temperatures."
603
+ )
604
+
379
605
  # Mechanical equations (always added)
380
606
  set_equal(A, counter + 1, self.inl[0], self.outl[0], "M")
381
607
  set_equal(A, counter + 2, self.inl[1], self.outl[1], "M")
382
- equations[counter + 1] = f"aux_equality_mech_{self.outl[0]['name']}"
383
- equations[counter + 2] = f"aux_equality_mech_{self.outl[1]['name']}"
384
-
608
+ equations[counter + 1] = {
609
+ "kind": "aux_equality",
610
+ "objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
611
+ "property": "c_M",
612
+ }
613
+ equations[counter + 2] = {
614
+ "kind": "aux_equality",
615
+ "objects": [self.name, self.inl[1]["name"], self.outl[1]["name"]],
616
+ "property": "c_M",
617
+ }
618
+
385
619
  # Only add chemical auxiliary equations if chemical exergy is enabled.
386
620
  if chemical_exergy_enabled:
387
621
  set_equal(A, counter + 3, self.inl[0], self.outl[0], "CH")
388
622
  set_equal(A, counter + 4, self.inl[1], self.outl[1], "CH")
389
- equations[counter + 3] = f"aux_equality_chem_{self.outl[0]['name']}"
390
- equations[counter + 4] = f"aux_equality_chem_{self.outl[1]['name']}"
623
+ equations[counter + 3] = {
624
+ "kind": "aux_equality",
625
+ "objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
626
+ "property": "c_CH",
627
+ }
628
+ equations[counter + 4] = {
629
+ "kind": "aux_equality",
630
+ "objects": [self.name, self.inl[1]["name"], self.outl[1]["name"]],
631
+ "property": "c_M",
632
+ }
391
633
  num_aux_eqs = 5
392
634
  else:
393
635
  # Skip chemical auxiliary equations.
@@ -398,52 +640,147 @@ class HeatExchanger(Component):
398
640
 
399
641
  return A, b, counter + num_aux_eqs, equations
400
642
 
401
- def exergoeconomic_balance(self, T0):
402
- """
403
- Perform exergoeconomic balance calculations for the general heat exchanger.
404
-
405
- This method calculates various exergoeconomic parameters including:
406
- - Cost rates of product (C_P) and fuel (C_F)
407
- - Specific cost of product (c_P) and fuel (c_F)
408
- - Cost rate of exergy destruction (C_D)
409
- - Relative cost difference (r)
410
- - Exergoeconomic factor (f)
411
-
643
+ def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False):
644
+ r"""
645
+ Perform exergoeconomic cost balance for the heat exchanger.
646
+
647
+ .. math::
648
+ \dot{C}^{\mathrm{T}}_{\mathrm{in},1}
649
+ + \dot{C}^{\mathrm{M}}_{\mathrm{in},1}
650
+ + \dot{C}^{\mathrm{T}}_{\mathrm{in},2}
651
+ + \dot{C}^{\mathrm{M}}_{\mathrm{in},2}
652
+ - \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
653
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},1}
654
+ - \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
655
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},2}
656
+ + \dot{Z}
657
+ = 0
658
+
659
+ In case the chemical exergy of the streams is know:
660
+
661
+ .. math::
662
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in},1} =
663
+ \dot{C}^{\mathrm{CH}}_{\mathrm{out},1}
664
+
665
+ .. math::
666
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in},2} =
667
+ \dot{C}^{\mathrm{CH}}_{\mathrm{out},2}
668
+
669
+ This method computes cost coefficients and ratios:
670
+
671
+ Case 1: All streams above ambient temperature
672
+
673
+ .. math::
674
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
675
+ - \dot{C}^{\mathrm{T}}_{\mathrm{in},2}
676
+
677
+ .. math::
678
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
679
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},1}
680
+ + \bigl(\dot{C}^{\mathrm{M}}_{\mathrm{in},2}
681
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
682
+
683
+ Case 2: All streams below or equal to ambient temperature
684
+
685
+ .. math::
686
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
687
+ - \dot{C}^{\mathrm{T}}_{\mathrm{in},1}
688
+
689
+ .. math::
690
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
691
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},2}
692
+ + \bigl(\dot{C}^{\mathrm{M}}_{\mathrm{in},1}
693
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
694
+
695
+ Case 3: Both stream crossing ambient temperature
696
+
697
+ .. math::
698
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
699
+ + \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
700
+
701
+ .. math::
702
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
703
+ + \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
704
+ - \bigl(\dot{C}^{\mathrm{M}}_{\mathrm{out},1}
705
+ + \dot{C}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
706
+
707
+ Case 4: Only the hot inlet above ambient temperature
708
+
709
+ .. math::
710
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
711
+
712
+ .. math::
713
+ \dot{C}_F = \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
714
+ + \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}\bigr)
715
+ - \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{out},2}
716
+ + \dot{C}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
717
+
718
+ Case 5: Only the cold inlet below ambient temperature
719
+
720
+ .. math::
721
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
722
+
723
+ .. math::
724
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
725
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},1}
726
+ + \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
727
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
728
+
729
+ Case 6: Hot stream always above and cold stream always below ambient temperature (dissipative case):
730
+
731
+ .. math::
732
+ \dot{C}_P = \mathrm{NaN}
733
+
734
+ .. math::
735
+ \dot{C}_F = \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
736
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
737
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},2}
738
+ + \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
739
+
412
740
  Parameters
413
741
  ----------
414
742
  T0 : float
415
- Ambient temperature
416
-
417
- Notes
418
- -----
419
- The exergoeconomic balance considers thermal (T), chemical (CH),
420
- and mechanical (M) exergy components for the inlet and outlet streams.
743
+ Ambient temperature (K).
744
+ chemical_exergy_enabled : bool, optional
745
+ If True, chemical exergy is considered in the calculations.
421
746
  """
747
+ # Case 1: All streams are above the ambient temperature
422
748
  if all([c["T"] > T0 for c in list(self.inl.values()) + list(self.outl.values())]):
423
749
  self.C_P = self.outl[1]["C_T"] - self.inl[1]["C_T"]
424
- self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (
425
- self.inl[1]["C_M"] - self.outl[1]["C_M"])
750
+ self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (self.inl[1]["C_M"] - self.outl[1]["C_M"])
751
+ # Case 2: All streams are below or equal to the ambient temperature
426
752
  elif all([c["T"] <= T0 for c in list(self.inl.values()) + list(self.outl.values())]):
427
753
  self.C_P = self.outl[0]["C_T"] - self.inl[0]["C_T"]
428
- self.C_F = self.inl[1]["C_PH"] - self.outl[1]["C_PH"] + (
429
- self.inl[0]["C_M"] - self.outl[0]["C_M"])
430
- elif (self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and
431
- self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0):
754
+ self.C_F = self.inl[1]["C_PH"] - self.outl[1]["C_PH"] + (self.inl[0]["C_M"] - self.outl[0]["C_M"])
755
+ # Case 3: Both stream crossing T0 (hot inlet and cold outlet > T0, hot outlet and cold inlet <= T0)
756
+ elif self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0:
432
757
  self.C_P = self.outl[0]["C_T"] + self.outl[1]["C_T"]
433
- self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (
434
- self.outl[0]["C_M"] + self.outl[1]["C_M"])
435
- elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
436
- self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0):
758
+ self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (self.outl[0]["C_M"] + self.outl[1]["C_M"])
759
+ # Case 4: Only hot inlet > T0
760
+ elif self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0:
437
761
  self.C_P = self.outl[0]["C_T"]
438
- self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (
439
- self.outl[1]["C_PH"] + self.outl[0]["C_M"])
440
- else:
762
+ self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (self.outl[1]["C_PH"] + self.outl[0]["C_M"])
763
+ # Case 5: Only cold inlet <= T0
764
+ elif self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0:
441
765
  self.C_P = self.outl[1]["C_T"]
442
- self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (
443
- self.inl[1]["C_PH"] - self.outl[1]["C_M"])
766
+ self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (self.inl[1]["C_PH"] - self.outl[1]["C_M"])
767
+ # Case 6: hot stream always above T0, cold stream always below T0 (dissipative case)
768
+ elif self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0:
769
+ logging.warning(
770
+ f"Component {self.name} is dissipative. This component should be "
771
+ "handled with the `dissipative` flag set to True."
772
+ )
773
+ self.C_P = np.nan
774
+ self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (self.inl[1]["C_PH"] - self.outl[1]["C_PH"])
775
+ # Case 7: Not implemented case
776
+ else:
777
+ logging.error(
778
+ f"The heat exchanger {self.name} has an unexpected temperature configuration. "
779
+ "Please check the inlet and outlet temperatures."
780
+ )
444
781
 
445
782
  self.c_F = self.C_F / self.E_F
446
783
  self.c_P = self.C_P / self.E_P
447
784
  self.C_D = self.c_F * self.E_D
448
785
  self.r = (self.c_P - self.c_F) / self.c_F
449
- self.f = self.Z_costs / (self.Z_costs + self.C_D)
786
+ self.f = self.Z_costs / (self.Z_costs + self.C_D)