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.
- exerpy/__init__.py +12 -0
- exerpy/analyses.py +1711 -0
- exerpy/components/__init__.py +16 -0
- exerpy/components/combustion/__init__.py +0 -0
- exerpy/components/combustion/base.py +248 -0
- exerpy/components/component.py +126 -0
- exerpy/components/heat_exchanger/__init__.py +0 -0
- exerpy/components/heat_exchanger/base.py +449 -0
- exerpy/components/heat_exchanger/condenser.py +323 -0
- exerpy/components/heat_exchanger/simple.py +358 -0
- exerpy/components/heat_exchanger/steam_generator.py +264 -0
- exerpy/components/helpers/__init__.py +0 -0
- exerpy/components/helpers/cycle_closer.py +104 -0
- exerpy/components/nodes/__init__.py +0 -0
- exerpy/components/nodes/deaerator.py +318 -0
- exerpy/components/nodes/drum.py +164 -0
- exerpy/components/nodes/flash_tank.py +89 -0
- exerpy/components/nodes/mixer.py +332 -0
- exerpy/components/piping/__init__.py +0 -0
- exerpy/components/piping/valve.py +394 -0
- exerpy/components/power_machines/__init__.py +0 -0
- exerpy/components/power_machines/generator.py +168 -0
- exerpy/components/power_machines/motor.py +173 -0
- exerpy/components/turbomachinery/__init__.py +0 -0
- exerpy/components/turbomachinery/compressor.py +318 -0
- exerpy/components/turbomachinery/pump.py +310 -0
- exerpy/components/turbomachinery/turbine.py +351 -0
- exerpy/data/Ahrendts.json +90 -0
- exerpy/functions.py +637 -0
- exerpy/parser/__init__.py +0 -0
- exerpy/parser/from_aspen/__init__.py +0 -0
- exerpy/parser/from_aspen/aspen_config.py +61 -0
- exerpy/parser/from_aspen/aspen_parser.py +721 -0
- exerpy/parser/from_ebsilon/__init__.py +38 -0
- exerpy/parser/from_ebsilon/check_ebs_path.py +74 -0
- exerpy/parser/from_ebsilon/ebsilon_config.py +1055 -0
- exerpy/parser/from_ebsilon/ebsilon_functions.py +181 -0
- exerpy/parser/from_ebsilon/ebsilon_parser.py +660 -0
- exerpy/parser/from_ebsilon/utils.py +79 -0
- exerpy/parser/from_tespy/tespy_config.py +23 -0
- exerpy-0.0.1.dist-info/METADATA +158 -0
- exerpy-0.0.1.dist-info/RECORD +44 -0
- exerpy-0.0.1.dist-info/WHEEL +4 -0
- 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)
|