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,264 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from exerpy.components.component import Component
|
|
4
|
+
from exerpy.components.component import component_registry
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@component_registry
|
|
8
|
+
class SteamGenerator(Component):
|
|
9
|
+
r"""
|
|
10
|
+
Class for exergy analysis of steam generators.
|
|
11
|
+
|
|
12
|
+
This class performs exergy analysis calculations for steam generators.
|
|
13
|
+
The component has several input and output streams as follows:
|
|
14
|
+
|
|
15
|
+
Inlet streams:
|
|
16
|
+
- inl[0]: Feed water inlet (high pressure)
|
|
17
|
+
- inl[1]: Steam inlet (intermediate pressure)
|
|
18
|
+
- inl[2]: Heat inlet (providing the heat input Q)
|
|
19
|
+
- inl[3]: Water injection (high pressure)
|
|
20
|
+
- inl[4]: Water injection (intermediate pressure)
|
|
21
|
+
|
|
22
|
+
Outlet streams:
|
|
23
|
+
- outl[0]: Superheated steam outlet (high pressure)
|
|
24
|
+
- outl[1]: Superheated steam outlet (intermediate pressure)
|
|
25
|
+
- outl[2]: Drain / Blow down outlet
|
|
26
|
+
|
|
27
|
+
The exergy product is defined as:
|
|
28
|
+
|
|
29
|
+
.. math::
|
|
30
|
+
\dot{E}_P = \Bigl[ \dot{m}_{\mathrm{out,HP}}\,e_{\mathrm{out,HP}}
|
|
31
|
+
- \dot{m}_{\mathrm{in,HP}}\,e_{\mathrm{in,HP}} \Bigr]
|
|
32
|
+
+ \Bigl[ \dot{m}_{\mathrm{out,IP}}\,e_{\mathrm{out,IP}}
|
|
33
|
+
- \dot{m}_{\mathrm{in,IP}}\,e_{\mathrm{in,IP}} \Bigr]
|
|
34
|
+
- \dot{m}_{\mathrm{w,HP}}\,e_{\mathrm{w,HP}}
|
|
35
|
+
- \dot{m}_{\mathrm{w,IP}}\,e_{\mathrm{w,IP}}
|
|
36
|
+
|
|
37
|
+
where:
|
|
38
|
+
- \(\dot{m}\) is the mass flow rate and \(e\) is the specific exergy of the stream.
|
|
39
|
+
- The subscripts HP and IP denote high and intermediate pressure streams,
|
|
40
|
+
respectively, and 'w' stands for water injection.
|
|
41
|
+
|
|
42
|
+
The exergy fuel is computed from the heat input as:
|
|
43
|
+
|
|
44
|
+
.. math::
|
|
45
|
+
\dot{E}_F = E_q = Q \left( 1 - \frac{T_b}{T_0} \right)
|
|
46
|
+
|
|
47
|
+
with the thermodynamic temperature difference defined by
|
|
48
|
+
|
|
49
|
+
.. math::
|
|
50
|
+
T_b = \frac{h_{\mathrm{out,HP}} - h_{\mathrm{in,HP}}}{s_{\mathrm{out,HP}} - s_{\mathrm{in,HP}}}
|
|
51
|
+
|
|
52
|
+
where:
|
|
53
|
+
- \(h\) and \(s\) are the specific enthalpy and entropy,
|
|
54
|
+
- \(T_0\) is the ambient temperature.
|
|
55
|
+
|
|
56
|
+
The exergy destruction and efficiency are then given by:
|
|
57
|
+
|
|
58
|
+
.. math::
|
|
59
|
+
\dot{E}_D = \dot{E}_F - \dot{E}_P \quad\mathrm{and}\quad \varepsilon = \frac{\dot{E}_P}{\dot{E}_F}
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
**kwargs : dict
|
|
64
|
+
Arbitrary keyword arguments passed to the parent class.
|
|
65
|
+
|
|
66
|
+
Attributes
|
|
67
|
+
----------
|
|
68
|
+
E_F : float
|
|
69
|
+
Exergy fuel of the component :math:`\dot{E}_F` in :math:`\mathrm{W}`.
|
|
70
|
+
E_P : float
|
|
71
|
+
Exergy product of the component :math:`\dot{E}_P` in :math:`\mathrm{W}`.
|
|
72
|
+
E_D : float
|
|
73
|
+
Exergy destruction of the component :math:`\dot{E}_D` in :math:`\mathrm{W}`.
|
|
74
|
+
epsilon : float
|
|
75
|
+
Exergetic efficiency :math:`\varepsilon`.
|
|
76
|
+
inl : dict
|
|
77
|
+
Dictionary containing inlet streams.
|
|
78
|
+
outl : dict
|
|
79
|
+
Dictionary containing outlet streams.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, **kwargs):
|
|
83
|
+
r"""Initialize steam generator component with given parameters."""
|
|
84
|
+
self.dissipative = False
|
|
85
|
+
super().__init__(**kwargs)
|
|
86
|
+
|
|
87
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
88
|
+
r"""
|
|
89
|
+
Calculate the exergy balance of the steam generator.
|
|
90
|
+
|
|
91
|
+
This method computes the exergy fuel from the heat inlet using the relation
|
|
92
|
+
|
|
93
|
+
.. math::
|
|
94
|
+
E_F = Q \left(1 - \frac{T_b}{T_0}\right),
|
|
95
|
+
\quad T_b = \frac{h_{\mathrm{out,HP}} - h_{\mathrm{in,HP}}}{s_{\mathrm{out,HP}} - s_{\mathrm{in,HP}}}
|
|
96
|
+
|
|
97
|
+
and the exergy product as
|
|
98
|
+
|
|
99
|
+
.. math::
|
|
100
|
+
E_P = \Bigl[ \dot{m}_{\mathrm{out,HP}}\,e_{\mathrm{out,HP}} -
|
|
101
|
+
\dot{m}_{\mathrm{in,HP}}\,e_{\mathrm{in,HP}} \Bigr]
|
|
102
|
+
+ \Bigl[ \dot{m}_{\mathrm{out,IP}}\,e_{\mathrm{out,IP}} -
|
|
103
|
+
\dot{m}_{\mathrm{in,IP}}\,e_{\mathrm{in,IP}} \Bigr]
|
|
104
|
+
- \dot{m}_{\mathrm{w,HP}}\,e_{\mathrm{w,HP}}
|
|
105
|
+
- \dot{m}_{\mathrm{w,IP}}\,e_{\mathrm{w,IP}}
|
|
106
|
+
|
|
107
|
+
The exergy destruction is given by
|
|
108
|
+
|
|
109
|
+
.. math::
|
|
110
|
+
E_D = E_F - E_P
|
|
111
|
+
|
|
112
|
+
and the exergetic efficiency is
|
|
113
|
+
|
|
114
|
+
.. math::
|
|
115
|
+
\varepsilon = \frac{E_P}{E_F}
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
T0 : float
|
|
120
|
+
Ambient temperature in Kelvin.
|
|
121
|
+
"""
|
|
122
|
+
# Ensure that all necessary streams exist
|
|
123
|
+
required_inlets = [0]
|
|
124
|
+
required_outlets = [0, 2]
|
|
125
|
+
for idx in required_inlets:
|
|
126
|
+
if idx not in self.inl:
|
|
127
|
+
raise ValueError(f"Missing inlet stream with index {idx}.")
|
|
128
|
+
for idx in required_outlets:
|
|
129
|
+
if idx not in self.outl:
|
|
130
|
+
raise ValueError(f"Missing outlet stream with index {idx}.")
|
|
131
|
+
|
|
132
|
+
# Calculate T_b for high pressure streams: from superheated steam outlet (HP) and feed water inlet (HP)
|
|
133
|
+
try:
|
|
134
|
+
T_b = (self.outl[0]['h'] - self.inl[0]['h']) / (self.outl[0]['s'] - self.inl[0]['s'])
|
|
135
|
+
except ZeroDivisionError:
|
|
136
|
+
raise ZeroDivisionError("Division by zero encountered in calculating T_b. Check entropy differences.")
|
|
137
|
+
|
|
138
|
+
if split_physical_exergy:
|
|
139
|
+
exergy_type = 'e_T'
|
|
140
|
+
else:
|
|
141
|
+
exergy_type = 'e_PH'
|
|
142
|
+
|
|
143
|
+
# Calculate exergy fuel
|
|
144
|
+
# High pressure part: Superheated steam outlet (HP) minus Feed water inlet (HP)
|
|
145
|
+
E_F_HP = self.outl[0]['m'] * self.outl[0][exergy_type] - self.inl[0]['m'] * self.inl[0][exergy_type]
|
|
146
|
+
# Intermediate pressure part: Superheated steam outlet (IP) minus Steam inlet (IP)
|
|
147
|
+
E_F_IP = self.outl.get(1, {}).get('m', 0) * self.outl.get(1, {}).get(exergy_type, 0) - self.inl.get(1, {}).get('m', 0) * self.inl.get(1, {}).get(exergy_type, 0)
|
|
148
|
+
# Water injection contributions (assumed to be negative)
|
|
149
|
+
E_F_w_inj = self.inl.get(2, {}).get('m', 0) * self.inl.get(2, {}).get(exergy_type, 0) + self.inl.get(3, {}).get('m', 0) * self.inl.get(3, {}).get(exergy_type, 0)
|
|
150
|
+
self.E_F = E_F_HP + E_F_IP - E_F_w_inj
|
|
151
|
+
logging.warning(f"Since the temperature level of the heat source of the steam generator is unknown, "
|
|
152
|
+
"the exergy fuel of this component is calculated based on the thermal exergy value of the water streams.")
|
|
153
|
+
# Calculate exergy product
|
|
154
|
+
# High pressure part: Superheated steam outlet (HP) minus Feed water inlet (HP)
|
|
155
|
+
E_P_HP = self.outl[0]['m'] * self.outl[0]['e_PH'] - self.inl[0]['m'] * self.inl[0]['e_PH']
|
|
156
|
+
# Intermediate pressure part: Superheated steam outlet (IP) minus Steam inlet (IP)
|
|
157
|
+
E_P_IP = self.outl.get(1, {}).get('m', 0) * self.outl.get(1, {}).get('e_PH', 0) - self.inl.get(1, {}).get('m', 0) * self.inl.get(1, {}).get('e_PH', 0)
|
|
158
|
+
# Water injection contributions (assumed to be negative)
|
|
159
|
+
E_P_w_inj = self.inl.get(2, {}).get('m', 0) * self.inl.get(2, {}).get('e_PH', 0) + self.inl.get(3, {}).get('m', 0) * self.inl.get(3, {}).get('e_PH', 0)
|
|
160
|
+
self.E_P = E_P_HP + E_P_IP - E_P_w_inj
|
|
161
|
+
|
|
162
|
+
# Calculate exergy destruction and efficiency
|
|
163
|
+
self.E_D = self.E_F - self.E_P
|
|
164
|
+
self.epsilon = self.calc_epsilon()
|
|
165
|
+
|
|
166
|
+
# Log the results
|
|
167
|
+
logging.info(
|
|
168
|
+
f"SteamGenerator exergy balance calculated: "
|
|
169
|
+
f"E_P = {self.E_P:.2f} W, E_F = {self.E_F:.2f} W, "
|
|
170
|
+
f"E_D = {self.E_D:.2f} W, Efficiency = {self.epsilon:.2%}"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
175
|
+
"""
|
|
176
|
+
Auxiliary equations for the steam generator.
|
|
177
|
+
|
|
178
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
179
|
+
the following auxiliary cost relations:
|
|
180
|
+
|
|
181
|
+
(1) c_T(heat_source)/E_F = c_T(HP_outlet)/E_T(HP) + c_T(IP_outlet)/E_T(IP)
|
|
182
|
+
- P-principle: thermal exergy costs from heat source are distributed to steam outlets
|
|
183
|
+
|
|
184
|
+
(2) 1/E_M_in(HP) * C_M_in(HP) - 1/E_M_out(HP) * C_M_out(HP) = 0
|
|
185
|
+
- F-principle: specific mechanical exergy costs equalized between HP inlet/outlet
|
|
186
|
+
|
|
187
|
+
(3) 1/E_M_in(IP) * C_M_in(IP) - 1/E_M_out(IP) * C_M_out(IP) = 0
|
|
188
|
+
- F-principle: specific mechanical exergy costs equalized between IP inlet/outlet
|
|
189
|
+
|
|
190
|
+
(4-5) Chemical exergy cost equations (if enabled) for HP and IP streams
|
|
191
|
+
- F-principle: specific chemical exergy costs equalized between inlets/outlets
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
A : numpy.ndarray
|
|
196
|
+
The current cost matrix.
|
|
197
|
+
b : numpy.ndarray
|
|
198
|
+
The current right-hand-side vector.
|
|
199
|
+
counter : int
|
|
200
|
+
The current row index in the matrix.
|
|
201
|
+
T0 : float
|
|
202
|
+
Ambient temperature.
|
|
203
|
+
equations : dict
|
|
204
|
+
Dictionary for storing equation labels.
|
|
205
|
+
chemical_exergy_enabled : bool
|
|
206
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
A : numpy.ndarray
|
|
211
|
+
The updated cost matrix.
|
|
212
|
+
b : numpy.ndarray
|
|
213
|
+
The updated right-hand-side vector.
|
|
214
|
+
counter : int
|
|
215
|
+
The updated row index.
|
|
216
|
+
equations : dict
|
|
217
|
+
Updated dictionary with equation labels.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def exergoeconomic_balance(self, T0):
|
|
221
|
+
"""
|
|
222
|
+
Perform exergoeconomic balance calculations for the steam generator.
|
|
223
|
+
|
|
224
|
+
This method calculates various exergoeconomic parameters including:
|
|
225
|
+
- Cost rates of product (C_P) and fuel (C_F)
|
|
226
|
+
- Specific cost of product (c_P) and fuel (c_F)
|
|
227
|
+
- Cost rate of exergy destruction (C_D)
|
|
228
|
+
- Relative cost difference (r)
|
|
229
|
+
- Exergoeconomic factor (f)
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
T0 : float
|
|
234
|
+
Ambient temperature
|
|
235
|
+
|
|
236
|
+
Notes
|
|
237
|
+
-----
|
|
238
|
+
The exergoeconomic balance considers thermal (T), chemical (CH),
|
|
239
|
+
and mechanical (M) exergy components for the inlet and outlet streams.
|
|
240
|
+
"""
|
|
241
|
+
# 1) Product cost rate: HP and IP steam net physical exergy costs, minus injection
|
|
242
|
+
C_P_hp = (self.outl[0]['m'] * self.outl[0]['C_PH']
|
|
243
|
+
- self.inl[0]['m'] * self.inl[0]['C_PH'])
|
|
244
|
+
C_P_ip = 0.0
|
|
245
|
+
if 1 in self.outl and 1 in self.inl:
|
|
246
|
+
C_P_ip = (self.outl[1]['m'] * self.outl[1]['C_PH']
|
|
247
|
+
- self.inl[1]['m'] * self.inl[1]['C_PH'])
|
|
248
|
+
# Subtract water injection costs
|
|
249
|
+
C_P_w = 0.0
|
|
250
|
+
if 3 in self.inl:
|
|
251
|
+
C_P_w += self.inl[3]['m'] * self.inl[3]['C_PH']
|
|
252
|
+
if 4 in self.inl:
|
|
253
|
+
C_P_w += self.inl[4]['m'] * self.inl[4]['C_PH']
|
|
254
|
+
self.C_P = C_P_hp + C_P_ip - C_P_w
|
|
255
|
+
|
|
256
|
+
# 2) Fuel cost rate: cost of heat exergy stream
|
|
257
|
+
self.C_F = self.inl[2]['C_T']
|
|
258
|
+
|
|
259
|
+
# 3) Specific costs and destruction cost
|
|
260
|
+
self.c_F = self.C_F / self.E_F if self.E_F != 0 else float('nan')
|
|
261
|
+
self.c_P = self.C_P / self.E_P if self.E_P != 0 else float('nan')
|
|
262
|
+
self.C_D = self.C_F - self.C_P
|
|
263
|
+
self.r = (self.c_P - self.c_F) / self.c_F if self.c_F != 0 else float('nan')
|
|
264
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D) if (self.Z_costs + self.C_D) != 0 else float('nan')
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
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 CycleCloser(Component):
|
|
11
|
+
r"""
|
|
12
|
+
Component for closing cycles. This component is not analyzed in exergy analysis.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, **kwargs):
|
|
15
|
+
r"""Initialize CycleCloser component with given parameters."""
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
|
|
18
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
19
|
+
r"""
|
|
20
|
+
The CycleCloser component does not have an exergy balance calculation.
|
|
21
|
+
"""
|
|
22
|
+
self.E_D = np.nan
|
|
23
|
+
self.E_F = np.nan
|
|
24
|
+
self.E_P = np.nan
|
|
25
|
+
self.E_L = np.nan
|
|
26
|
+
self.epsilon = np.nan
|
|
27
|
+
|
|
28
|
+
# Log the results
|
|
29
|
+
logging.info(
|
|
30
|
+
f"The exergy balance of a CycleCloser component is skipped."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
35
|
+
"""
|
|
36
|
+
Auxiliary equations for the cycle closer.
|
|
37
|
+
|
|
38
|
+
This function adds two rows to the cost matrix A and the right-hand side vector b to enforce
|
|
39
|
+
the following auxiliary cost relations:
|
|
40
|
+
|
|
41
|
+
(1) 1/E_M_in * C_M_in - 1/E_M_out * C_M_out = 0
|
|
42
|
+
(2) 1/E_T_in * C_T_in - 1/E_T_out * C_T_out = 0
|
|
43
|
+
|
|
44
|
+
These equations ensure that the specific mechanical and thermal costs are equalized between
|
|
45
|
+
the inlet and outlet of the cycle closer. Chemical exergy is not considered for the cycle closer.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
A : numpy.ndarray
|
|
50
|
+
The current cost matrix.
|
|
51
|
+
b : numpy.ndarray
|
|
52
|
+
The current right-hand-side vector.
|
|
53
|
+
counter : int
|
|
54
|
+
The current row index in the matrix.
|
|
55
|
+
T0 : float
|
|
56
|
+
Ambient temperature (not used in this component).
|
|
57
|
+
equations : list or dict
|
|
58
|
+
Data structure for storing equation labels.
|
|
59
|
+
chemical_exergy_enabled : bool
|
|
60
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
61
|
+
This flag is ignored for CycleCloser.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
A : numpy.ndarray
|
|
66
|
+
The updated cost matrix.
|
|
67
|
+
b : numpy.ndarray
|
|
68
|
+
The updated right-hand-side vector.
|
|
69
|
+
counter : int
|
|
70
|
+
The updated row index (increased by 2).
|
|
71
|
+
equations : list or dict
|
|
72
|
+
Updated structure with equation labels.
|
|
73
|
+
"""
|
|
74
|
+
# Mechanical cost equality equation:
|
|
75
|
+
A[counter, self.inl[0]["CostVar_index"]["M"]] = (1 / self.inl[0]["e_M"]) if self.inl[0]["e_M"] != 0 else 1
|
|
76
|
+
A[counter, self.outl[0]["CostVar_index"]["M"]] = (-1 / self.outl[0]["e_M"]) if self.outl[0]["e_M"] != 0 else -1
|
|
77
|
+
equations[counter] = f"aux_cyclecloser_mech"
|
|
78
|
+
b[counter] = 0
|
|
79
|
+
|
|
80
|
+
# Thermal cost equality equation:
|
|
81
|
+
A[counter+1, self.inl[0]["CostVar_index"]["T"]] = (1 / self.inl[0]["e_T"]) if self.inl[0]["e_T"] != 0 else 1
|
|
82
|
+
A[counter+1, self.outl[0]["CostVar_index"]["T"]] = (-1 / self.outl[0]["e_T"]) if self.outl[0]["e_T"] != 0 else -1
|
|
83
|
+
equations[counter+1] = f"aux_cyclecloser_thermal"
|
|
84
|
+
b[counter+1] = 0
|
|
85
|
+
|
|
86
|
+
counter += 2
|
|
87
|
+
return A, b, counter, equations
|
|
88
|
+
|
|
89
|
+
def exergoeconomic_balance(self, T0):
|
|
90
|
+
"""
|
|
91
|
+
Placeholder for exergoeconomic balance calculations.
|
|
92
|
+
|
|
93
|
+
The CycleCloser component is not considered in exergoeconomic analysis
|
|
94
|
+
and all calculations are skipped. NaN values are assigned to all
|
|
95
|
+
exergoeconomic parameters.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
self.C_P = np.nan
|
|
99
|
+
self.C_F = np.nan
|
|
100
|
+
self.c_F = np.nan
|
|
101
|
+
self.c_P = np.nan
|
|
102
|
+
self.C_D = np.nan
|
|
103
|
+
self.r = np.nan
|
|
104
|
+
self.f = np.nan
|
|
File without changes
|
|
@@ -0,0 +1,318 @@
|
|
|
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 Deaerator(Component):
|
|
11
|
+
r"""
|
|
12
|
+
Class for exergy analysis of deaerators.
|
|
13
|
+
|
|
14
|
+
This class performs exergy analysis calculations for deaerators with multiple
|
|
15
|
+
inlet streams and one outlet stream. The exergy product and fuel definitions
|
|
16
|
+
vary based on the temperature relationships between inlet streams, outlet
|
|
17
|
+
stream, and ambient conditions.
|
|
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 with temperature, mass flows,
|
|
36
|
+
and specific exergies.
|
|
37
|
+
outl : dict
|
|
38
|
+
Dictionary containing outlet stream data with temperature, mass flows,
|
|
39
|
+
and specific exergies.
|
|
40
|
+
|
|
41
|
+
Notes
|
|
42
|
+
-----
|
|
43
|
+
The exergy analysis accounts for physical exergy only. The equations for exergy
|
|
44
|
+
product and fuel are defined based on temperature relationships:
|
|
45
|
+
|
|
46
|
+
.. math::
|
|
47
|
+
|
|
48
|
+
\dot{E}_\mathrm{P} =
|
|
49
|
+
\begin{cases}
|
|
50
|
+
\begin{cases}
|
|
51
|
+
\sum_i \dot{m}_i \cdot (e_\mathrm{out}^\mathrm{PH} -
|
|
52
|
+
e_{\mathrm{in,}i}^\mathrm{PH})
|
|
53
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \mathrm{ & }
|
|
54
|
+
T_{\mathrm{in,}i} \geq T_0 \\
|
|
55
|
+
\sum_i \dot{m}_i \cdot e_\mathrm{out}^\mathrm{PH}
|
|
56
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \mathrm{ & }
|
|
57
|
+
T_{\mathrm{in,}i} < T_0 \\
|
|
58
|
+
\end{cases} & T_\mathrm{out} > T_0\\
|
|
59
|
+
\mathrm{not defined (nan)} & T_\mathrm{out} = T_0\\
|
|
60
|
+
\begin{cases}
|
|
61
|
+
\sum_i \dot{m}_i \cdot e_\mathrm{out}^\mathrm{PH}
|
|
62
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \mathrm{ & }
|
|
63
|
+
T_{\mathrm{in,}i} \geq T_0 \\
|
|
64
|
+
\sum_i \dot{m}_i \cdot (e_\mathrm{out}^\mathrm{PH} -
|
|
65
|
+
e_{\mathrm{in,}i}^\mathrm{PH})
|
|
66
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \mathrm{ & }
|
|
67
|
+
T_{\mathrm{in,}i} < T_0 \\
|
|
68
|
+
\end{cases} & T_\mathrm{out} < T_0\\
|
|
69
|
+
\end{cases}
|
|
70
|
+
|
|
71
|
+
\dot{E}_\mathrm{F} =
|
|
72
|
+
\begin{cases}
|
|
73
|
+
\begin{cases}
|
|
74
|
+
\sum_i \dot{m}_i \cdot (e_{\mathrm{in,}i}^\mathrm{PH} -
|
|
75
|
+
e_\mathrm{out}^\mathrm{PH})
|
|
76
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \\
|
|
77
|
+
\sum_i \dot{m}_i \cdot e_{\mathrm{in,}i}^\mathrm{PH}
|
|
78
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \mathrm{ & }
|
|
79
|
+
T_{\mathrm{in,}i} < T_0 \\
|
|
80
|
+
\end{cases} & T_\mathrm{out} > T_0\\
|
|
81
|
+
\sum_i \dot{m}_i \cdot e_{\mathrm{in,}i}^\mathrm{PH}
|
|
82
|
+
& T_\mathrm{out} = T_0\\
|
|
83
|
+
\begin{cases}
|
|
84
|
+
\sum_i \dot{m}_i \cdot e_{\mathrm{in,}i}^\mathrm{PH}
|
|
85
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \mathrm{ & }
|
|
86
|
+
T_{\mathrm{in,}i} \geq T_0 \\
|
|
87
|
+
\sum_i \dot{m}_i \cdot (e_{\mathrm{in,}i}^\mathrm{PH} -
|
|
88
|
+
e_\mathrm{out}^\mathrm{PH})
|
|
89
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \\
|
|
90
|
+
\end{cases} & T_\mathrm{out} < T_0\\
|
|
91
|
+
\end{cases}
|
|
92
|
+
|
|
93
|
+
\forall i \in \mathrm{deaerator inlets}
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, **kwargs):
|
|
97
|
+
r"""Initialize deaerator component with given parameters."""
|
|
98
|
+
super().__init__(**kwargs)
|
|
99
|
+
|
|
100
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
101
|
+
r"""
|
|
102
|
+
Calculate the exergy balance of the deaerator.
|
|
103
|
+
|
|
104
|
+
Performs exergy balance calculations considering the temperature relationships
|
|
105
|
+
between inlet streams, outlet stream, and ambient conditions.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
T0 : float
|
|
110
|
+
Ambient temperature in :math:`\mathrm{K}`.
|
|
111
|
+
p0 : float
|
|
112
|
+
Ambient pressure in :math:`\mathrm{Pa}`.
|
|
113
|
+
split_physical_exergy : bool
|
|
114
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
115
|
+
|
|
116
|
+
Raises
|
|
117
|
+
------
|
|
118
|
+
ValueError
|
|
119
|
+
If the required inlet and outlet streams are not properly defined.
|
|
120
|
+
"""
|
|
121
|
+
# Ensure that the component has both inlet and outlet streams
|
|
122
|
+
if len(self.inl) < 2 or len(self.outl) < 1:
|
|
123
|
+
raise ValueError("Deaerator requires at least two inlets and one outlet.")
|
|
124
|
+
|
|
125
|
+
self.E_P = 0
|
|
126
|
+
self.E_F = 0
|
|
127
|
+
|
|
128
|
+
# Case 1: Outlet temperature is greater than T0
|
|
129
|
+
if self.outl[0]['T'] > T0:
|
|
130
|
+
for _, inlet in self.inl.items():
|
|
131
|
+
if inlet['T'] < self.outl[0]['T']: # Tin < Tout
|
|
132
|
+
if inlet['T'] >= T0: # and Tin >= T0
|
|
133
|
+
self.E_P += inlet['m'] * (self.outl[0]['e_PH'] - inlet['e_PH'])
|
|
134
|
+
else: # and Tin < T0
|
|
135
|
+
self.E_P += inlet['m'] * self.outl[0]['e_PH']
|
|
136
|
+
self.E_F += inlet['m'] * inlet['e_PH']
|
|
137
|
+
else: # Tin > Tout
|
|
138
|
+
self.E_F += inlet['m'] * (inlet['e_PH'] - self.outl[0]['e_PH'])
|
|
139
|
+
|
|
140
|
+
# Case 2: Outlet temperature is equal to T0
|
|
141
|
+
elif self.outl[0]['T'] == T0:
|
|
142
|
+
self.E_P = np.nan
|
|
143
|
+
for _, inlet in self.inl.items():
|
|
144
|
+
self.E_F += inlet['m'] * inlet['e_PH']
|
|
145
|
+
|
|
146
|
+
# Case 3: Outlet temperature is less than T0
|
|
147
|
+
else:
|
|
148
|
+
for _, inlet in self.inl.items():
|
|
149
|
+
if inlet['T'] > self.outl[0]['T']: # Tin > Tout
|
|
150
|
+
if inlet['T'] >= T0: # and Tin >= T0
|
|
151
|
+
self.E_P += inlet['m'] * self.outl[0]['e_PH']
|
|
152
|
+
self.E_F += inlet['m'] * inlet['e_PH']
|
|
153
|
+
else: # and Tin < T0
|
|
154
|
+
self.E_P += inlet['m'] * (self.outl[0]['e_PH'] - inlet['e_PH'])
|
|
155
|
+
else: # Tin < Tout
|
|
156
|
+
self.E_F += inlet['m'] * (inlet['e_PH'] - self.outl[0]['e_PH'])
|
|
157
|
+
|
|
158
|
+
# Calculate exergy destruction and efficiency
|
|
159
|
+
if np.isnan(self.E_P):
|
|
160
|
+
self.E_D = self.E_F
|
|
161
|
+
else:
|
|
162
|
+
self.E_D = self.E_F - self.E_P
|
|
163
|
+
self.epsilon = self.calc_epsilon()
|
|
164
|
+
|
|
165
|
+
# Log the results
|
|
166
|
+
logging.info(
|
|
167
|
+
f"Deaerator exergy balance calculated: "
|
|
168
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
169
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
173
|
+
"""
|
|
174
|
+
Auxiliary equations for the deaerator.
|
|
175
|
+
|
|
176
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
177
|
+
the following auxiliary cost relations:
|
|
178
|
+
|
|
179
|
+
(1) Mixing equation for chemical exergy costs (if enabled):
|
|
180
|
+
- The outlet's specific chemical exergy cost is calculated as a mass-weighted
|
|
181
|
+
average of the inlet streams' specific chemical exergy costs
|
|
182
|
+
- This enforces proper chemical exergy cost distribution through the deaerator
|
|
183
|
+
|
|
184
|
+
(2) Mixing equation for mechanical exergy costs:
|
|
185
|
+
- The outlet's specific mechanical exergy cost is calculated as a mass-weighted
|
|
186
|
+
average of the inlet streams' specific mechanical exergy costs
|
|
187
|
+
- This ensures mechanical exergy costs are properly conserved in the mixing process
|
|
188
|
+
|
|
189
|
+
Both equations implement the proportionality rule for mixing processes where
|
|
190
|
+
the outlet's specific costs should reflect the contribution of each inlet stream.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
A : numpy.ndarray
|
|
195
|
+
The current cost matrix.
|
|
196
|
+
b : numpy.ndarray
|
|
197
|
+
The current right-hand-side vector.
|
|
198
|
+
counter : int
|
|
199
|
+
The current row index in the matrix.
|
|
200
|
+
T0 : float
|
|
201
|
+
Ambient temperature (provided for consistency; not used in this function).
|
|
202
|
+
equations : dict
|
|
203
|
+
Dictionary for storing equation labels.
|
|
204
|
+
chemical_exergy_enabled : bool
|
|
205
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
A : numpy.ndarray
|
|
210
|
+
The updated cost matrix.
|
|
211
|
+
b : numpy.ndarray
|
|
212
|
+
The updated right-hand-side vector.
|
|
213
|
+
counter : int
|
|
214
|
+
The updated row index (increased by 2 if chemical exergy is enabled, or by 1 otherwise).
|
|
215
|
+
equations : dict
|
|
216
|
+
Updated dictionary with equation labels.
|
|
217
|
+
"""
|
|
218
|
+
# --- Chemical cost auxiliary equation (conditionally added) ---
|
|
219
|
+
if chemical_exergy_enabled:
|
|
220
|
+
if self.outl[0]["e_CH"] != 0:
|
|
221
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = -1 / self.outl[0]["E_CH"]
|
|
222
|
+
# Iterate over inlet streams for chemical mixing.
|
|
223
|
+
for inlet in self.inl.values():
|
|
224
|
+
if inlet["e_CH"] != 0:
|
|
225
|
+
A[counter, inlet["CostVar_index"]["CH"]] = inlet["m"] / (self.outl[0]["m"] * inlet["E_CH"])
|
|
226
|
+
else:
|
|
227
|
+
A[counter, inlet["CostVar_index"]["CH"]] = 1
|
|
228
|
+
else:
|
|
229
|
+
# Outlet chemical exergy is zero: assign fallback for all inlets.
|
|
230
|
+
for inlet in self.inl.values():
|
|
231
|
+
A[counter, inlet["CostVar_index"]["CH"]] = 1
|
|
232
|
+
equations[counter] = f"aux_mixing_chem_{self.outl[0]['name']}"
|
|
233
|
+
chem_row = 1 # One row added for chemical equation.
|
|
234
|
+
else:
|
|
235
|
+
chem_row = 0 # No row added.
|
|
236
|
+
|
|
237
|
+
# --- Mechanical cost auxiliary equation ---
|
|
238
|
+
mech_row = 0 # This row will always be added.
|
|
239
|
+
if self.outl[0]["e_M"] != 0:
|
|
240
|
+
A[counter + chem_row, self.outl[0]["CostVar_index"]["M"]] = -1 / self.outl[0]["E_M"]
|
|
241
|
+
# Iterate over inlet streams for mechanical mixing.
|
|
242
|
+
for inlet in self.inl.values():
|
|
243
|
+
if inlet["e_M"] != 0:
|
|
244
|
+
A[counter + chem_row, inlet["CostVar_index"]["M"]] = inlet["m"] / (self.outl[0]["m"] * inlet["E_M"])
|
|
245
|
+
else:
|
|
246
|
+
A[counter + chem_row, inlet["CostVar_index"]["M"]] = 1
|
|
247
|
+
else:
|
|
248
|
+
for inlet in self.inl.values():
|
|
249
|
+
A[counter + chem_row, inlet["CostVar_index"]["M"]] = 1
|
|
250
|
+
equations[counter + chem_row] = f"aux_mixing_mech_{self.outl[0]['name']}"
|
|
251
|
+
|
|
252
|
+
# Set the right-hand side entries to zero for the added rows.
|
|
253
|
+
if chemical_exergy_enabled:
|
|
254
|
+
b[counter] = 0
|
|
255
|
+
b[counter + 1] = 0
|
|
256
|
+
counter += 2 # Two rows were added.
|
|
257
|
+
else:
|
|
258
|
+
b[counter] = 0
|
|
259
|
+
counter += 1 # Only one row was added.
|
|
260
|
+
|
|
261
|
+
return A, b, counter, equations
|
|
262
|
+
|
|
263
|
+
def exergoeconomic_balance(self, T0):
|
|
264
|
+
"""
|
|
265
|
+
Perform exergoeconomic balance calculations for the deaerator.
|
|
266
|
+
|
|
267
|
+
This method calculates various exergoeconomic parameters including:
|
|
268
|
+
- Cost rates of product (C_P) and fuel (C_F)
|
|
269
|
+
- Specific cost of product (c_P) and fuel (c_F)
|
|
270
|
+
- Cost rate of exergy destruction (C_D)
|
|
271
|
+
- Relative cost difference (r)
|
|
272
|
+
- Exergoeconomic factor (f)
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
T0 : float
|
|
277
|
+
Ambient temperature
|
|
278
|
+
|
|
279
|
+
Notes
|
|
280
|
+
-----
|
|
281
|
+
The exergoeconomic balance considers thermal (T), chemical (CH),
|
|
282
|
+
and mechanical (M) exergy components for the inlet and outlet streams.
|
|
283
|
+
"""
|
|
284
|
+
self.C_P = 0
|
|
285
|
+
self.C_F = 0
|
|
286
|
+
if self.outl[0]["T"] > T0:
|
|
287
|
+
for i in self.inl:
|
|
288
|
+
if i["T"] < self.outl[0]["T"]:
|
|
289
|
+
# cold inlets
|
|
290
|
+
self.C_F += i["C_M"] + i["C_CH"]
|
|
291
|
+
else:
|
|
292
|
+
# hot inlets
|
|
293
|
+
self.C_F += - i["M"] * i["C_T"] * i["e_T"] + (
|
|
294
|
+
i["C_T"] + i["C_M"] + i["C_CH"])
|
|
295
|
+
self.C_F += (-self.outl[0]["C_M"] - self.outl[0]["C_CH"])
|
|
296
|
+
elif self.outl[0]["T"] - 1e-6 < T0 and self.outl[0]["T"] + 1e-6 > T0:
|
|
297
|
+
# dissipative
|
|
298
|
+
for i in self.inl:
|
|
299
|
+
self.C_F += i["C_TOT"]
|
|
300
|
+
else:
|
|
301
|
+
for i in self.inl:
|
|
302
|
+
if i["T"] > self.outl[0]["T"]:
|
|
303
|
+
# hot inlets
|
|
304
|
+
self.C_F += i["C_M"] + i["C_CH"]
|
|
305
|
+
else:
|
|
306
|
+
# cold inlets
|
|
307
|
+
self.C_F += - i["M"] * i["C_T"] * i["e_T"] + (
|
|
308
|
+
i["C_T"] + i["C_M"] + i["C_CH"])
|
|
309
|
+
self.C_F += (-self.outl[0]["C_M"] - self.outl[0]["C_CH"])
|
|
310
|
+
self.C_P = self.C_F + self.Z_costs # +1/num_serving_comps * C_diff
|
|
311
|
+
# ToDo: add case that merge profits from dissipative component(s)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
self.c_F = self.C_F / self.E_F
|
|
315
|
+
self.c_P = self.C_P / self.E_P
|
|
316
|
+
self.C_D = self.c_F * self.E_D
|
|
317
|
+
self.r = (self.c_P - self.c_F) / self.c_F
|
|
318
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D)
|