exerpy 0.0.1__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 (44) hide show
  1. exerpy/__init__.py +12 -0
  2. exerpy/analyses.py +1711 -0
  3. exerpy/components/__init__.py +16 -0
  4. exerpy/components/combustion/__init__.py +0 -0
  5. exerpy/components/combustion/base.py +248 -0
  6. exerpy/components/component.py +126 -0
  7. exerpy/components/heat_exchanger/__init__.py +0 -0
  8. exerpy/components/heat_exchanger/base.py +449 -0
  9. exerpy/components/heat_exchanger/condenser.py +323 -0
  10. exerpy/components/heat_exchanger/simple.py +358 -0
  11. exerpy/components/heat_exchanger/steam_generator.py +264 -0
  12. exerpy/components/helpers/__init__.py +0 -0
  13. exerpy/components/helpers/cycle_closer.py +104 -0
  14. exerpy/components/nodes/__init__.py +0 -0
  15. exerpy/components/nodes/deaerator.py +318 -0
  16. exerpy/components/nodes/drum.py +164 -0
  17. exerpy/components/nodes/flash_tank.py +89 -0
  18. exerpy/components/nodes/mixer.py +332 -0
  19. exerpy/components/piping/__init__.py +0 -0
  20. exerpy/components/piping/valve.py +394 -0
  21. exerpy/components/power_machines/__init__.py +0 -0
  22. exerpy/components/power_machines/generator.py +168 -0
  23. exerpy/components/power_machines/motor.py +173 -0
  24. exerpy/components/turbomachinery/__init__.py +0 -0
  25. exerpy/components/turbomachinery/compressor.py +318 -0
  26. exerpy/components/turbomachinery/pump.py +310 -0
  27. exerpy/components/turbomachinery/turbine.py +351 -0
  28. exerpy/data/Ahrendts.json +90 -0
  29. exerpy/functions.py +637 -0
  30. exerpy/parser/__init__.py +0 -0
  31. exerpy/parser/from_aspen/__init__.py +0 -0
  32. exerpy/parser/from_aspen/aspen_config.py +61 -0
  33. exerpy/parser/from_aspen/aspen_parser.py +721 -0
  34. exerpy/parser/from_ebsilon/__init__.py +38 -0
  35. exerpy/parser/from_ebsilon/check_ebs_path.py +74 -0
  36. exerpy/parser/from_ebsilon/ebsilon_config.py +1055 -0
  37. exerpy/parser/from_ebsilon/ebsilon_functions.py +181 -0
  38. exerpy/parser/from_ebsilon/ebsilon_parser.py +660 -0
  39. exerpy/parser/from_ebsilon/utils.py +79 -0
  40. exerpy/parser/from_tespy/tespy_config.py +23 -0
  41. exerpy-0.0.1.dist-info/METADATA +158 -0
  42. exerpy-0.0.1.dist-info/RECORD +44 -0
  43. exerpy-0.0.1.dist-info/WHEEL +4 -0
  44. exerpy-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,449 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+
5
+ from exerpy.components.component import Component
6
+ from exerpy.components.component import component_registry
7
+
8
+
9
+ @component_registry
10
+ class HeatExchanger(Component):
11
+ r"""
12
+ Class for exergy analysis of heat exchangers.
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.
23
+
24
+ Attributes
25
+ ----------
26
+ E_F : float
27
+ Exergy fuel of the component :math:`\dot{E}_\mathrm{F}` in :math:`\mathrm{W}`.
28
+ E_P : float
29
+ Exergy product of the component :math:`\dot{E}_\mathrm{P}` in :math:`\mathrm{W}`.
30
+ E_D : float
31
+ Exergy destruction of the component :math:`\dot{E}_\mathrm{D}` in :math:`\mathrm{W}`.
32
+ epsilon : float
33
+ Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
34
+ inl : dict
35
+ Dictionary containing inlet streams data (0: hot stream, 1: cold stream) with
36
+ temperature, mass flows, and specific exergies.
37
+ 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
122
+ """
123
+
124
+ def __init__(self, **kwargs):
125
+ r"""Initialize heat exchanger component with given parameters."""
126
+ self.dissipative = False
127
+ super().__init__(**kwargs)
128
+
129
+ def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
130
+ r"""
131
+ Calculate the exergy balance of the heat exchanger.
132
+
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.
136
+
137
+ Parameters
138
+ ----------
139
+ T0 : float
140
+ Ambient temperature in :math:`\mathrm{K}`.
141
+ p0 : float
142
+ Ambient pressure in :math:`\mathrm{Pa}`.
143
+ split_physical_exergy : bool
144
+ Flag indicating whether physical exergy is split into thermal and mechanical components.
145
+
146
+ Raises
147
+ ------
148
+ 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.
162
+ """
163
+ # Ensure that the component has both inlet and outlet streams
164
+ if len(self.inl) < 2 or len(self.outl) < 2:
165
+ raise ValueError("Heat exchanger requires two inlets and two outlets.")
166
+
167
+ # Access the streams via .values() to iterate over the actual stream data
168
+ all_streams = list(self.inl.values()) + list(self.outl.values())
169
+
170
+ if not self.dissipative:
171
+ # Case 1: All streams are above the ambient temperature
172
+ if all([stream['T'] >= T0 for stream in all_streams]):
173
+ 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'])
177
+ 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']
180
+
181
+ # Case 2: All streams are below or equal to the ambient temperature
182
+ elif all([stream['T'] <= T0 for stream in all_streams]):
183
+ 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'])
187
+ 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):
196
+ 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'])
200
+ 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):
209
+ 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'])
213
+ 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:
229
+ 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'])
233
+ 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'])
239
+
240
+ else:
241
+ 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']
246
+ )
247
+ self.E_P = np.nan
248
+ # Calculate exergy destruction and efficiency
249
+ if np.isnan(self.E_P):
250
+ self.E_D = self.E_F
251
+ else:
252
+ self.E_D = self.E_F - self.E_P
253
+ self.epsilon = self.calc_epsilon()
254
+
255
+ # Log the results
256
+ logging.info(
257
+ f"HeatExchanger exergy balance calculated: "
258
+ f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
259
+ f"Efficiency={self.epsilon:.2%}"
260
+ )
261
+
262
+
263
+ 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.
268
+ Parameters
269
+ ----------
270
+ A : numpy.ndarray
271
+ Coefficient matrix for the linear equation system.
272
+ b : numpy.ndarray
273
+ Right-hand side vector of the linear equation system.
274
+ counter : int
275
+ Current row counter in the linear equation system.
276
+ T0 : float
277
+ Reference temperature for exergy calculations.
278
+ equations : dict
279
+ Dictionary to store equation descriptions.
280
+ chemical_exergy_enabled : bool
281
+ Flag indicating whether chemical exergy analysis is enabled.
282
+ Returns
283
+ -------
284
+ A : numpy.ndarray
285
+ Updated coefficient matrix.
286
+ b : numpy.ndarray
287
+ Updated right-hand side vector.
288
+ counter : int
289
+ Updated row counter.
290
+ equations : dict
291
+ Updated dictionary of equation descriptions.
292
+ """
293
+ # Equality equation for mechanical and chemical exergy costs.
294
+ def set_equal(A, row, in_item, out_item, var):
295
+ if in_item["e_" + var] != 0 and out_item["e_" + var] != 0:
296
+ A[row, in_item["CostVar_index"][var]] = 1 / in_item["e_" + var]
297
+ A[row, out_item["CostVar_index"][var]] = -1 / out_item["e_" + var]
298
+ elif in_item["e_" + var] == 0 and out_item["e_" + var] != 0:
299
+ A[row, in_item["CostVar_index"][var]] = 1
300
+ elif in_item["e_" + var] != 0 and out_item["e_" + var] == 0:
301
+ A[row, out_item["CostVar_index"][var]] = 1
302
+ else:
303
+ A[row, in_item["CostVar_index"][var]] = 1
304
+ A[row, out_item["CostVar_index"][var]] = -1
305
+
306
+ # Thermal fuel rule on hot stream: c_T_in0 = c_T_out0.
307
+ def set_thermal_f_hot(A, row):
308
+ if self.inl[0]["e_T"] != 0 and self.outl[0]["e_T"] != 0:
309
+ A[row, self.inl[0]["CostVar_index"]["T"]] = 1 / self.inl[0]["E_T"]
310
+ A[row, self.outl[0]["CostVar_index"]["T"]] = -1 / self.outl[0]["E_T"]
311
+ elif self.inl[0]["e_T"] == 0 and self.outl[0]["e_T"] != 0:
312
+ A[row, self.inl[0]["CostVar_index"]["T"]] = 1
313
+ elif self.inl[0]["e_T"] != 0 and self.outl[0]["e_T"] == 0:
314
+ A[row, self.outl[0]["CostVar_index"]["T"]] = 1
315
+ else:
316
+ A[row, self.inl[0]["CostVar_index"]["T"]] = 1
317
+ A[row, self.outl[0]["CostVar_index"]["T"]] = -1
318
+
319
+ # Thermal fuel rule on cold stream: c_T_in1 = c_T_out1.
320
+ def set_thermal_f_cold(A, row):
321
+ if self.inl[1]["e_T"] != 0 and self.outl[1]["e_T"] != 0:
322
+ A[row, self.inl[1]["CostVar_index"]["T"]] = 1 / self.inl[1]["E_T"]
323
+ A[row, self.outl[1]["CostVar_index"]["T"]] = -1 / self.outl[1]["E_T"]
324
+ elif self.inl[1]["e_T"] == 0 and self.outl[1]["e_T"] != 0:
325
+ A[row, self.inl[1]["CostVar_index"]["T"]] = 1
326
+ elif self.inl[1]["e_T"] != 0 and self.outl[1]["e_T"] == 0:
327
+ A[row, self.outl[1]["CostVar_index"]["T"]] = 1
328
+ else:
329
+ A[row, self.inl[1]["CostVar_index"]["T"]] = 1
330
+ A[row, self.outl[1]["CostVar_index"]["T"]] = -1
331
+
332
+ # Thermal product rule: Equate the two outlet thermal costs (c_T_out0 = c_T_out1).
333
+ def set_thermal_p_rule(A, row):
334
+ if self.outl[0]["e_T"] != 0 and self.outl[1]["e_T"] != 0:
335
+ A[row, self.outl[0]["CostVar_index"]["T"]] = 1 / self.outl[0]["E_T"]
336
+ A[row, self.outl[1]["CostVar_index"]["T"]] = -1 / self.outl[1]["E_T"]
337
+ elif self.outl[0]["e_T"] == 0 and self.outl[1]["e_T"] != 0:
338
+ A[row, self.outl[0]["CostVar_index"]["T"]] = 1
339
+ elif self.outl[0]["e_T"] != 0 and self.outl[1]["e_T"] == 0:
340
+ A[row, self.outl[1]["CostVar_index"]["T"]] = 1
341
+ else:
342
+ A[row, self.outl[0]["CostVar_index"]["T"]] = 1
343
+ A[row, self.outl[1]["CostVar_index"]["T"]] = -1
344
+
345
+ # Determine the thermal case based on temperatures.
346
+ # Case 1: All temperatures > T0.
347
+ if all([c["T"] > T0 for c in list(self.inl.values()) + list(self.outl.values())]):
348
+ set_thermal_f_hot(A, counter + 0)
349
+ equations[counter] = f"aux_f_rule_hot_{self.name}"
350
+ # Case 2: All temperatures <= T0.
351
+ elif all([c["T"] <= T0 for c in list(self.inl.values()) + list(self.outl.values())]):
352
+ 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):
357
+ 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):
362
+ 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):
367
+ 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")
373
+ 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}"
378
+
379
+ # Mechanical equations (always added)
380
+ set_equal(A, counter + 1, self.inl[0], self.outl[0], "M")
381
+ 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
+
385
+ # Only add chemical auxiliary equations if chemical exergy is enabled.
386
+ if chemical_exergy_enabled:
387
+ set_equal(A, counter + 3, self.inl[0], self.outl[0], "CH")
388
+ 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']}"
391
+ num_aux_eqs = 5
392
+ else:
393
+ # Skip chemical auxiliary equations.
394
+ num_aux_eqs = 3
395
+
396
+ for i in range(num_aux_eqs):
397
+ b[counter + i] = 0
398
+
399
+ return A, b, counter + num_aux_eqs, equations
400
+
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
+
412
+ Parameters
413
+ ----------
414
+ 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.
421
+ """
422
+ if all([c["T"] > T0 for c in list(self.inl.values()) + list(self.outl.values())]):
423
+ 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"])
426
+ elif all([c["T"] <= T0 for c in list(self.inl.values()) + list(self.outl.values())]):
427
+ 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):
432
+ 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):
437
+ 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:
441
+ 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"])
444
+
445
+ self.c_F = self.C_F / self.E_F
446
+ self.c_P = self.C_P / self.E_P
447
+ self.C_D = self.c_F * self.E_D
448
+ self.r = (self.c_P - self.c_F) / self.c_F
449
+ self.f = self.Z_costs / (self.Z_costs + self.C_D)