exerpy 0.0.1__py3-none-any.whl → 0.0.2__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.
@@ -9,17 +9,11 @@ from exerpy.components.component import component_registry
9
9
  @component_registry
10
10
  class HeatExchanger(Component):
11
11
  r"""
12
- Class for exergy analysis of heat exchangers.
12
+ Class for exergy and exergoeconomic analysis of heat exchangers.
13
13
 
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.
14
+ This class performs exergy and exergoeconomic analysis calculations for heat exchanger components,
15
+ accounting for two inlet and two outlet streams across various temperature regimes, including
16
+ above and below ambient temperature, and optional dissipative behavior.
23
17
 
24
18
  Attributes
25
19
  ----------
@@ -32,133 +26,228 @@ class HeatExchanger(Component):
32
26
  epsilon : float
33
27
  Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
34
28
  inl : dict
35
- Dictionary containing inlet streams data (0: hot stream, 1: cold stream) with
36
- temperature, mass flows, and specific exergies.
29
+ Dictionary containing inlet stream data with mass flows and specific exergies.
37
30
  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
31
+ Dictionary containing outlet stream data with mass flows and specific exergies.
32
+ Z_costs : float
33
+ Investment cost rate of the component in currency/h.
34
+ C_P : float
35
+ Cost of product stream :math:`\dot{C}_P` in currency/h.
36
+ C_F : float
37
+ Cost of fuel stream :math:`\dot{C}_F` in currency/h.
38
+ C_D : float
39
+ Cost of exergy destruction :math:`\dot{C}_D` in currency/h.
40
+ c_P : float
41
+ Specific cost of product stream (currency per unit exergy).
42
+ c_F : float
43
+ Specific cost of fuel stream (currency per unit exergy).
44
+ r : float
45
+ Relative cost difference, :math:`(c_P - c_F)/c_F`.
46
+ f : float
47
+ Exergoeconomic factor, :math:`\dot{Z}/(\dot{Z} + \dot{C}_D)`.
48
+ Ex_C_col : dict
49
+ Custom cost coefficients collection passed via `kwargs`.
122
50
  """
123
51
 
124
52
  def __init__(self, **kwargs):
125
- r"""Initialize heat exchanger component with given parameters."""
53
+ r"""
54
+ Initialize the heat exchanger component.
55
+
56
+ Parameters
57
+ ----------
58
+ **kwargs : dict
59
+ Arbitrary keyword arguments. Recognized keys:
60
+ - dissipative (bool): whether component has dissipative behavior, default False
61
+ - Ex_C_col (dict): custom cost coefficients, default {}
62
+ - Z_costs (float): investment cost rate in currency/h, default 0.0
63
+ """
126
64
  self.dissipative = False
127
65
  super().__init__(**kwargs)
128
66
 
129
67
  def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
130
68
  r"""
131
- Calculate the exergy balance of the heat exchanger.
69
+ Compute the exergy balance of the heat exchanger.
70
+
71
+ Case 1: All streams above ambient temperature
72
+
73
+ If `split_physical_exergy=True`:
74
+
75
+ .. math::
76
+
77
+ \dot{E}_{\mathrm{P}}
78
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
79
+ - \dot{E}^{\mathrm{T}}_{\mathrm{in},2}
80
+
81
+ .. math::
82
+
83
+ \dot{E}_{\mathrm{F}}
84
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
85
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
86
+ + \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{in},2}
87
+ - \dot{E}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
88
+
89
+ Else:
90
+
91
+ .. math::
92
+
93
+ \dot{E}_{\mathrm{P}}
94
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
95
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
96
+
97
+ .. math::
98
+
99
+ \dot{E}_{\mathrm{F}}
100
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
101
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
102
+
103
+ Case 2: All streams below or equal to ambient temperature
104
+
105
+ If `split_physical_exergy=True`:
106
+
107
+ .. math::
108
+ \dot{E}_{\mathrm{P}}
109
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
110
+ - \dot{E}^{\mathrm{T}}_{\mathrm{in},1}
111
+
112
+ .. math::
113
+
114
+ \dot{E}_{\mathrm{F}}
115
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
116
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
117
+ + \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{in},1}
118
+ - \dot{E}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
119
+
120
+ Else
121
+
122
+ .. math::
123
+ \dot{E}_{\mathrm{P}}
124
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
125
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
126
+
127
+ .. math::
128
+ \dot{E}_{\mathrm{F}}
129
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
130
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
131
+
132
+ Case 3: Both stream crossing ambient temperature
133
+
134
+ If `split_physical_exergy=True`:
135
+
136
+ .. math::
137
+ \dot{E}_{\mathrm{P}}
138
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
139
+ + \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
140
+
141
+ .. math::
142
+ \dot{E}_{\mathrm{F}}
143
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
144
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
145
+ - \bigl(\dot{E}^{\mathrm{M}}_{\mathrm{out},1}
146
+ + \dot{E}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
132
147
 
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.
148
+ Else:
149
+
150
+ .. math::
151
+ \dot{E}_{\mathrm{P}}
152
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
153
+ + \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
154
+
155
+ .. math::
156
+ \dot{E}_{\mathrm{F}}
157
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
158
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
159
+
160
+ Case 4: Only the hot inlet above ambient temperature
161
+
162
+ If `split_physical_exergy=True`:
163
+
164
+ .. math::
165
+ \dot{E}_{\mathrm{P}}
166
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},1}
167
+
168
+ .. math::
169
+ \dot{E}_{\mathrm{F}}
170
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
171
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}\bigr)
172
+ - \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
173
+ + \dot{E}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
174
+
175
+ Else:
176
+
177
+ .. math::
178
+ \dot{E}_{\mathrm{P}}
179
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}
180
+
181
+ .. math::
182
+ \dot{E}_{\mathrm{F}}
183
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
184
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}\bigr)
185
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
186
+
187
+ Case 5: Only the cold inlet below ambient temperature
188
+
189
+ If `split_physical_exergy=True`:
190
+
191
+ .. math::
192
+ \dot{E}_{\mathrm{P}}
193
+ = \dot{E}^{\mathrm{T}}_{\mathrm{out},2}
194
+
195
+ .. math::
196
+ \dot{E}_{\mathrm{F}}
197
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
198
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
199
+ + \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
200
+ - \dot{E}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
201
+
202
+ Else:
203
+
204
+ .. math::
205
+ \dot{E}_{\mathrm{P}}
206
+ = \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
207
+
208
+ .. math::
209
+ \dot{E}_{\mathrm{F}}
210
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
211
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
212
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
213
+
214
+ Case 6: Hot stream always above and cold stream always below ambiente temperature (dissipative case):
215
+
216
+ .. math::
217
+ \dot{E}_{\mathrm{P}} = \mathrm{NaN}
218
+
219
+ .. math::
220
+ \dot{E}_{\mathrm{F}}
221
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
222
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
223
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
224
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
225
+
226
+ If `dissipative` is `True`, the component is treated as dissipative:
227
+
228
+ .. math::
229
+ \dot{E}_{\mathrm{P}} = \mathrm{NaN}
230
+
231
+ .. math::
232
+ \dot{E}_{\mathrm{F}}
233
+ = \bigl(\dot{E}^{\mathrm{PH}}_{\mathrm{in},1}
234
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
235
+ - \dot{E}^{\mathrm{PH}}_{\mathrm{out},2}
236
+ + \dot{E}^{\mathrm{PH}}_{\mathrm{in},2}
136
237
 
137
238
  Parameters
138
239
  ----------
139
240
  T0 : float
140
- Ambient temperature in :math:`\mathrm{K}`.
241
+ Ambient temperature (K).
141
242
  p0 : float
142
- Ambient pressure in :math:`\mathrm{Pa}`.
243
+ Ambient pressure (Pa).
143
244
  split_physical_exergy : bool
144
- Flag indicating whether physical exergy is split into thermal and mechanical components.
245
+ Whether to split thermal and mechanical exergy.
145
246
 
146
247
  Raises
147
248
  ------
148
249
  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.
250
+ If required inlets or outlets are missing.
162
251
  """
163
252
  # Ensure that the component has both inlet and outlet streams
164
253
  if len(self.inl) < 2 or len(self.outl) < 2:
@@ -190,7 +279,7 @@ class HeatExchanger(Component):
190
279
  self.E_P = self.outl[0]['m'] * self.outl[0]['e_PH'] - self.inl[0]['m'] * self.inl[0]['e_PH']
191
280
  self.E_F = self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_PH']
192
281
 
193
- # Case 3: Hot stream from above to lower ambient, cold stream from lower to above ambient
282
+ # Case 3: Both stream crossing T0 (hot inlet and cold outlet > T0, hot outlet and cold inlet <= T0)
194
283
  elif (self.inl[0]['T'] > T0 and self.outl[1]['T'] > T0 and
195
284
  self.outl[0]['T'] <= T0 and self.inl[1]['T'] <= T0):
196
285
  if split_physical_exergy:
@@ -203,7 +292,7 @@ class HeatExchanger(Component):
203
292
  self.E_P = self.outl[0]['m'] * self.outl[0]['e_PH'] + self.outl[1]['m'] * self.outl[1]['e_PH']
204
293
  self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] + self.inl[1]['m'] * self.inl[1]['e_PH']
205
294
 
206
- # Case 4: Hot stream inlet above ambient, all others below or equal to ambient
295
+ # Case 4: Only hot inlet > T0
207
296
  elif (self.inl[0]['T'] > T0 and self.inl[1]['T'] <= T0 and
208
297
  self.outl[0]['T'] <= T0 and self.outl[1]['T'] <= T0):
209
298
  if split_physical_exergy:
@@ -217,15 +306,9 @@ class HeatExchanger(Component):
217
306
  self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] + (
218
307
  self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_PH'])
219
308
 
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:
309
+ # Case 5: Only cold inlet <= T0
310
+ elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
311
+ self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0):
229
312
  if split_physical_exergy:
230
313
  self.E_P = self.outl[1]['m'] * self.outl[1]['e_T']
231
314
  self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH'] + (
@@ -236,6 +319,21 @@ class HeatExchanger(Component):
236
319
  self.E_P = self.outl[1]['m'] * self.outl[1]['e_PH']
237
320
  self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH'] + (
238
321
  self.inl[1]['m'] * self.inl[1]['e_PH'])
322
+
323
+ # Case 6: hot stream always above T0, cold stream always below T0 (dissipative case)
324
+ elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
325
+ self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0):
326
+ self.E_P = np.nan
327
+ self.E_F = self.inl[0]['m'] * self.inl[0]['e_PH'] - self.outl[0]['m'] * self.outl[0]['e_PH'] + (
328
+ self.inl[1]['m'] * self.inl[1]['e_PH'] - self.outl[1]['m'] * self.outl[1]['e_PH'])
329
+
330
+ logging.warning(f"Component {self.name} is dissipative. This component should be " \
331
+ "handled with the `dissipative` flag set to True.")
332
+
333
+ # Case 7: Not implemented case
334
+ else:
335
+ logging.error(f"The heat exchanger {self.name} has an unexpected temperature configuration. "
336
+ "Please check the inlet and outlet temperatures.")
239
337
 
240
338
  else:
241
339
  self.E_F = (
@@ -261,34 +359,106 @@ class HeatExchanger(Component):
261
359
 
262
360
 
263
361
  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.
362
+ r"""
363
+ Add auxiliary cost equations for the heat exchanger.
364
+
365
+ This method appends rows to the cost matrix to enforce:
366
+
367
+ Case 1: All streams above ambient temperature
368
+
369
+ F rule for thermal exergy of the hot stream:
370
+
371
+ .. math::
372
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},1}
373
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},1}
374
+ = 0
375
+
376
+ Case 2: All streams below or equal to ambient temperature
377
+
378
+ F rule for thermal exergy of the cold stream:
379
+
380
+ .. math::
381
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},2}
382
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},2}
383
+ = 0
384
+
385
+ Case 3: Both stream crossing ambient temperature
386
+
387
+ P rule for thermal exergy of both outlets:
388
+
389
+ .. math::
390
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},1}
391
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},2}
392
+ = 0
393
+
394
+ Case 4: Only the hot inlet above ambient temperature
395
+
396
+ F rule for thermal exergy of the cold stream:
397
+
398
+ .. math::
399
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},2}
400
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},2}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},2}
401
+ = 0
402
+
403
+ Case 5: Only the cold inlet below ambient temperature
404
+
405
+ F rule for thermal exergy of the hot stream:
406
+
407
+ .. math::
408
+ -\frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{out},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{out},1}
409
+ + \frac{1}{\dot{E}^{\mathrm{T}}_{\mathrm{in},1}}\,\dot{C}^{\mathrm{T}}_{\mathrm{in},1}
410
+ = 0
411
+
412
+ Case 6: Hot stream always above and cold stream always below ambiente temperature (dissipative case):
413
+
414
+ The dissipative is not handeld here!
415
+
416
+ For all cases, the mechanical and chemical exergy costs are handled as follows:
417
+
418
+ F rule for mechanical exergy of the hot stream:
419
+
420
+ .. math::
421
+ -\frac{1}{\dot{E}^{\mathrm{M}}_{\mathrm{out},i}}\,\dot{C}^{\mathrm{M}}_{\mathrm{out},i}
422
+ + \frac{1}{\dot{E}^{\mathrm{M}}_{\mathrm{in},i}}\,\dot{C}^{\mathrm{M}}_{\mathrm{in},i}
423
+ = 0
424
+
425
+ F rule for chemical exergy on hot branch:
426
+
427
+ .. math::
428
+ -\frac{1}{\dot{E}^{\mathrm{CH}}_{\mathrm{out},i}}\,\dot{C}^{\mathrm{CH}}_{\mathrm{out},i}
429
+ + \frac{1}{\dot{E}^{\mathrm{CH}}_{\mathrm{in},i}}\,\dot{C}^{\mathrm{CH}}_{\mathrm{in},i}
430
+ = 0
431
+
268
432
  Parameters
269
433
  ----------
270
434
  A : numpy.ndarray
271
- Coefficient matrix for the linear equation system.
435
+ Current cost matrix.
272
436
  b : numpy.ndarray
273
- Right-hand side vector of the linear equation system.
437
+ Current RHS vector.
274
438
  counter : int
275
- Current row counter in the linear equation system.
439
+ Starting row index for auxiliary equations.
276
440
  T0 : float
277
- Reference temperature for exergy calculations.
278
- equations : dict
279
- Dictionary to store equation descriptions.
441
+ Ambient temperature (K).
442
+ equations : dict or list
443
+ Structure for equation labels.
280
444
  chemical_exergy_enabled : bool
281
- Flag indicating whether chemical exergy analysis is enabled.
445
+ Must be True to include chemical exergy mixing.
446
+
282
447
  Returns
283
448
  -------
284
449
  A : numpy.ndarray
285
- Updated coefficient matrix.
450
+ Updated cost matrix.
286
451
  b : numpy.ndarray
287
- Updated right-hand side vector.
452
+ Updated RHS vector.
288
453
  counter : int
289
- Updated row counter.
290
- equations : dict
291
- Updated dictionary of equation descriptions.
454
+ Updated row index after adding equations.
455
+ equations : dict or list
456
+ Updated labels.
457
+
458
+ Raises
459
+ ------
460
+ ValueError
461
+ If required cost variable indices are missing.
292
462
  """
293
463
  # Equality equation for mechanical and chemical exergy costs.
294
464
  def set_equal(A, row, in_item, out_item, var):
@@ -351,30 +521,31 @@ class HeatExchanger(Component):
351
521
  elif all([c["T"] <= T0 for c in list(self.inl.values()) + list(self.outl.values())]):
352
522
  set_thermal_f_cold(A, counter + 0)
353
523
  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.
524
+ # Case 3: Both stream crossing T0 (hot inlet and cold outlet > T0, hot outlet and cold inlet <= T0)
355
525
  elif (self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and
356
526
  self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0):
357
527
  set_thermal_p_rule(A, counter + 0)
358
528
  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.
529
+ # Case 4: Only hot inlet > T0
360
530
  elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
361
531
  self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0):
362
532
  set_thermal_f_cold(A, counter + 0)
363
533
  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.
534
+ # Case 5: Only cold inlet <= T0
365
535
  elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
366
536
  self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0):
367
537
  set_thermal_f_hot(A, counter + 0)
368
538
  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.
539
+ # Case 6: hot stream always above T0, cold stream always below T0 (dissipative case)
370
540
  elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
371
541
  self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0):
372
- print("you shouldn't see this")
542
+ logging.warning(f"Component {self.name} is dissipative. This component should be " \
543
+ "handled with the `dissipative` flag set to True.")
373
544
  return
374
- # Case 7: Default case.
375
- else:
376
- set_thermal_f_hot(A, counter + 0)
377
- equations[counter] = f"aux_f_rule_hot_{self.name}"
545
+ # Case 7: Not implemented case
546
+ else:
547
+ logging.error(f"The heat exchanger {self.name} has an unexpected temperature configuration. "
548
+ "Please check the inlet and outlet temperatures.")
378
549
 
379
550
  # Mechanical equations (always added)
380
551
  set_equal(A, counter + 1, self.inl[0], self.outl[0], "M")
@@ -399,48 +570,147 @@ class HeatExchanger(Component):
399
570
  return A, b, counter + num_aux_eqs, equations
400
571
 
401
572
  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)
573
+ r"""
574
+ Perform exergoeconomic cost balance for the heat exchanger.
575
+
576
+ .. math::
577
+ \dot{C}^{\mathrm{T}}_{\mathrm{in},1}
578
+ + \dot{C}^{\mathrm{M}}_{\mathrm{in},1}
579
+ + \dot{C}^{\mathrm{T}}_{\mathrm{in},2}
580
+ + \dot{C}^{\mathrm{M}}_{\mathrm{in},2}
581
+ - \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
582
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},1}
583
+ - \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
584
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},2}
585
+ + \dot{Z}
586
+ = 0
587
+
588
+ In case the chemical exergy of the streams is know:
589
+
590
+ .. math::
591
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in},1} =
592
+ \dot{C}^{\mathrm{CH}}_{\mathrm{out},1}
593
+
594
+ .. math::
595
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in},2} =
596
+ \dot{C}^{\mathrm{CH}}_{\mathrm{out},2}
597
+
598
+ This method computes cost coefficients and ratios:
599
+
600
+ Case 1: All streams above ambient temperature
601
+
602
+ .. math::
603
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
604
+ - \dot{C}^{\mathrm{T}}_{\mathrm{in},2}
605
+
606
+ .. math::
607
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
608
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},1}
609
+ + \bigl(\dot{C}^{\mathrm{M}}_{\mathrm{in},2}
610
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
611
+
612
+ Case 2: All streams below or equal to ambient temperature
613
+
614
+ .. math::
615
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
616
+ - \dot{C}^{\mathrm{T}}_{\mathrm{in},1}
617
+
618
+ .. math::
619
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
620
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},2}
621
+ + \bigl(\dot{C}^{\mathrm{M}}_{\mathrm{in},1}
622
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
623
+
624
+ Case 3: Both stream crossing ambient temperature
625
+
626
+ .. math::
627
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
628
+ + \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
629
+
630
+ .. math::
631
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
632
+ + \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
633
+ - \bigl(\dot{C}^{\mathrm{M}}_{\mathrm{out},1}
634
+ + \dot{C}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
635
+
636
+ Case 4: Only the hot inlet above ambient temperature
637
+
638
+ .. math::
639
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},1}
640
+
641
+ .. math::
642
+ \dot{C}_F = \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
643
+ + \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}\bigr)
644
+ - \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{out},2}
645
+ + \dot{C}^{\mathrm{M}}_{\mathrm{out},1}\bigr)
646
+
647
+ Case 5: Only the cold inlet below ambient temperature
648
+
649
+ .. math::
650
+ \dot{C}_P = \dot{C}^{\mathrm{T}}_{\mathrm{out},2}
651
+
652
+ .. math::
653
+ \dot{C}_F = \dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
654
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},1}
655
+ + \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
656
+ - \dot{C}^{\mathrm{M}}_{\mathrm{out},2}\bigr)
657
+
658
+ Case 6: Hot stream always above and cold stream always below ambient temperature (dissipative case):
659
+
660
+ .. math::
661
+ \dot{C}_P = \mathrm{NaN}
662
+
663
+ .. math::
664
+ \dot{C}_F = \bigl(\dot{C}^{\mathrm{PH}}_{\mathrm{in},1}
665
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},1}\bigr)
666
+ - \dot{C}^{\mathrm{PH}}_{\mathrm{out},2}
667
+ + \dot{C}^{\mathrm{PH}}_{\mathrm{in},2}
411
668
 
412
669
  Parameters
413
670
  ----------
414
671
  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.
672
+ Ambient temperature (K).
421
673
  """
674
+ # Case 1: All streams are above the ambient temperature
422
675
  if all([c["T"] > T0 for c in list(self.inl.values()) + list(self.outl.values())]):
423
676
  self.C_P = self.outl[1]["C_T"] - self.inl[1]["C_T"]
424
677
  self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (
425
678
  self.inl[1]["C_M"] - self.outl[1]["C_M"])
679
+ # Case 2: All streams are below or equal to the ambient temperature
426
680
  elif all([c["T"] <= T0 for c in list(self.inl.values()) + list(self.outl.values())]):
427
681
  self.C_P = self.outl[0]["C_T"] - self.inl[0]["C_T"]
428
682
  self.C_F = self.inl[1]["C_PH"] - self.outl[1]["C_PH"] + (
429
683
  self.inl[0]["C_M"] - self.outl[0]["C_M"])
684
+ # Case 3: Both stream crossing T0 (hot inlet and cold outlet > T0, hot outlet and cold inlet <= T0)
430
685
  elif (self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and
431
686
  self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0):
432
687
  self.C_P = self.outl[0]["C_T"] + self.outl[1]["C_T"]
433
688
  self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (
434
689
  self.outl[0]["C_M"] + self.outl[1]["C_M"])
690
+ # Case 4: Only hot inlet > T0
435
691
  elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
436
692
  self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0):
437
693
  self.C_P = self.outl[0]["C_T"]
438
694
  self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (
439
695
  self.outl[1]["C_PH"] + self.outl[0]["C_M"])
440
- else:
696
+ # Case 5: Only cold inlet <= T0
697
+ elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
698
+ self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0):
441
699
  self.C_P = self.outl[1]["C_T"]
442
700
  self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (
443
701
  self.inl[1]["C_PH"] - self.outl[1]["C_M"])
702
+ # Case 6: hot stream always above T0, cold stream always below T0 (dissipative case)
703
+ elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
704
+ self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0):
705
+ logging.warning(f"Component {self.name} is dissipative. This component should be " \
706
+ "handled with the `dissipative` flag set to True.")
707
+ self.C_P = np.nan
708
+ self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (
709
+ self.inl[1]["C_PH"] - self.outl[1]["C_PH"])
710
+ # Case 7: Not implemented case
711
+ else:
712
+ logging.error(f"The heat exchanger {self.name} has an unexpected temperature configuration. "
713
+ "Please check the inlet and outlet temperatures.")
444
714
 
445
715
  self.c_F = self.C_F / self.E_F
446
716
  self.c_P = self.C_P / self.E_P