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,310 @@
|
|
|
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 Pump(Component):
|
|
11
|
+
r"""
|
|
12
|
+
Class for exergy analysis of pumps.
|
|
13
|
+
|
|
14
|
+
This class performs exergy analysis calculations for pumps, with definitions
|
|
15
|
+
of exergy product and fuel varying based on the temperature relationships between
|
|
16
|
+
inlet stream, outlet stream, and ambient conditions.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
**kwargs : dict
|
|
21
|
+
Arbitrary keyword arguments passed to parent class.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
E_F : float
|
|
26
|
+
Exergy fuel of the component :math:`\dot{E}_\mathrm{F}` in :math:`\mathrm{W}`.
|
|
27
|
+
E_P : float
|
|
28
|
+
Exergy product of the component :math:`\dot{E}_\mathrm{P}` in :math:`\mathrm{W}`.
|
|
29
|
+
E_D : float
|
|
30
|
+
Exergy destruction of the component :math:`\dot{E}_\mathrm{D}` in :math:`\mathrm{W}`.
|
|
31
|
+
epsilon : float
|
|
32
|
+
Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
|
|
33
|
+
P : float
|
|
34
|
+
Power input to the pump in :math:`\mathrm{W}`.
|
|
35
|
+
inl : dict
|
|
36
|
+
Dictionary containing inlet stream data with temperature, mass flows,
|
|
37
|
+
enthalpies, and specific exergies.
|
|
38
|
+
outl : dict
|
|
39
|
+
Dictionary containing outlet stream data with temperature, mass flows,
|
|
40
|
+
enthalpies, and specific exergies.
|
|
41
|
+
|
|
42
|
+
Notes
|
|
43
|
+
-----
|
|
44
|
+
The exergy analysis considers three cases based on temperature relationships:
|
|
45
|
+
|
|
46
|
+
Case 1 - **Both temperatures above ambient** (:math:`T_\mathrm{in}, T_\mathrm{out} > T_0`):
|
|
47
|
+
|
|
48
|
+
.. math::
|
|
49
|
+
|
|
50
|
+
\dot{E}_\mathrm{P} &= \dot{m} \cdot (e_\mathrm{out}^\mathrm{PH} -
|
|
51
|
+
e_\mathrm{in}^\mathrm{PH})\\
|
|
52
|
+
\dot{E}_\mathrm{F} &= |\dot{W}|
|
|
53
|
+
|
|
54
|
+
Case 2 - **Inlet below, outlet above ambient** (:math:`T_\mathrm{in} < T_0 < T_\mathrm{out}`):
|
|
55
|
+
|
|
56
|
+
.. math::
|
|
57
|
+
|
|
58
|
+
\dot{E}_\mathrm{P} &= |\dot{W}| + (e_\mathrm{out}^\mathrm{PH} -
|
|
59
|
+
e_\mathrm{in}^\mathrm{M})\\
|
|
60
|
+
\dot{E}_\mathrm{F} &= e_\mathrm{in}^\mathrm{M} + e_\mathrm{in}^\mathrm{PH}
|
|
61
|
+
|
|
62
|
+
Case 3 - **Both temperatures below ambient** (:math:`T_\mathrm{in}, T_\mathrm{out} \leq T_0`):
|
|
63
|
+
|
|
64
|
+
.. math::
|
|
65
|
+
|
|
66
|
+
\dot{E}_\mathrm{P} &= e_\mathrm{out}^\mathrm{M} - e_\mathrm{in}^\mathrm{M}\\
|
|
67
|
+
\dot{E}_\mathrm{F} &= e_\mathrm{in}^\mathrm{PH} - e_\mathrm{out}^\mathrm{PH}
|
|
68
|
+
|
|
69
|
+
For all valid cases, the exergy destruction is:
|
|
70
|
+
|
|
71
|
+
.. math::
|
|
72
|
+
|
|
73
|
+
\dot{E}_\mathrm{D} = \dot{E}_\mathrm{F} - \dot{E}_\mathrm{P}
|
|
74
|
+
|
|
75
|
+
where:
|
|
76
|
+
- :math:`\dot{W}`: Power input
|
|
77
|
+
- :math:`e^\mathrm{PH}`: Physical exergy
|
|
78
|
+
- :math:`e^\mathrm{M}`: Mechanical exergy
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, **kwargs):
|
|
82
|
+
r"""Initialize pump component with given parameters."""
|
|
83
|
+
super().__init__(**kwargs)
|
|
84
|
+
self.P = None
|
|
85
|
+
|
|
86
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
87
|
+
r"""
|
|
88
|
+
Calculate the exergy balance of the pump.
|
|
89
|
+
|
|
90
|
+
Performs exergy balance calculations considering the temperature relationships
|
|
91
|
+
between inlet stream, outlet stream, and ambient conditions.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
T0 : float
|
|
96
|
+
Ambient temperature in :math:`\mathrm{K}`.
|
|
97
|
+
p0 : float
|
|
98
|
+
Ambient pressure in :math:`\mathrm{Pa}`.
|
|
99
|
+
split_physical_exergy : bool
|
|
100
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
# Get power flow if not already available
|
|
104
|
+
if self.P is None:
|
|
105
|
+
self.P = self.outl[0]['m'] * (self.outl[0]['h'] - self.inl[0]['h'])
|
|
106
|
+
|
|
107
|
+
# First, check for the invalid case: outlet temperature smaller than inlet temperature.
|
|
108
|
+
if self.inl[0]['T'] > self.outl[0]['T']:
|
|
109
|
+
logging.warning(
|
|
110
|
+
f"Exergy balance of pump '{self.name}' where outlet temperature ({self.outl[0]['T']}) "
|
|
111
|
+
f"is smaller than inlet temperature ({self.inl[0]['T']}) is not implemented."
|
|
112
|
+
)
|
|
113
|
+
self.E_P = np.nan
|
|
114
|
+
self.E_F = np.nan
|
|
115
|
+
|
|
116
|
+
# Case 1: Both temperatures above ambient
|
|
117
|
+
elif round(self.inl[0]['T'], 5) >= T0 and round(self.outl[0]['T'], 5) > T0:
|
|
118
|
+
self.E_P = self.outl[0]['m'] * (self.outl[0]['e_PH'] - self.inl[0]['e_PH'])
|
|
119
|
+
self.E_F = abs(self.P)
|
|
120
|
+
|
|
121
|
+
# Case 2: Inlet below, outlet above ambient
|
|
122
|
+
elif round(self.inl[0]['T'], 5) < T0 and round(self.outl[0]['T'], 5) > T0:
|
|
123
|
+
if split_physical_exergy:
|
|
124
|
+
self.E_P = (self.outl[0]['m'] * self.outl[0]['e_T'] +
|
|
125
|
+
self.outl[0]['m'] * (self.outl[0]['e_M'] - self.inl[0]['e_M']))
|
|
126
|
+
self.E_F = abs(self.P) + self.inl[0]['m'] * self.inl[0]['e_T']
|
|
127
|
+
else:
|
|
128
|
+
logging.warning("While dealing with pump below ambient, "
|
|
129
|
+
"physical exergy should be split into thermal and mechanical components!")
|
|
130
|
+
self.E_P = self.outl[0]['m'] * (self.outl[0]['e_PH'] - self.inl[0]['e_PH'])
|
|
131
|
+
self.E_F = abs(self.P)
|
|
132
|
+
|
|
133
|
+
# Case 3: Both temperatures below ambient
|
|
134
|
+
elif round(self.inl[0]['T'], 5) < T0 and round(self.outl[0]['T'], 5) <= T0:
|
|
135
|
+
if split_physical_exergy:
|
|
136
|
+
self.E_P = self.outl[0]['m'] * (self.outl[0]['e_M'] - self.inl[0]['e_M'])
|
|
137
|
+
self.E_F = abs(self.P) + self.inl[0]['m'] * (self.inl[0]['e_T'] -
|
|
138
|
+
self.outl[0]['e_T'])
|
|
139
|
+
else:
|
|
140
|
+
logging.warning("While dealing with pump below ambient, "
|
|
141
|
+
"physical exergy should be split into thermal and mechanical components!")
|
|
142
|
+
self.E_P = self.outl[0]['m'] * (self.outl[0]['e_PH'] - self.inl[0]['e_PH'])
|
|
143
|
+
self.E_F = abs(self.P)
|
|
144
|
+
|
|
145
|
+
# Invalid case: outlet temperature smaller than inlet
|
|
146
|
+
else:
|
|
147
|
+
logging.warning(
|
|
148
|
+
'Exergy balance of a pump where outlet temperature is smaller '
|
|
149
|
+
'than inlet temperature is not implemented.'
|
|
150
|
+
)
|
|
151
|
+
self.E_P = np.nan
|
|
152
|
+
self.E_F = np.nan
|
|
153
|
+
|
|
154
|
+
# Calculate exergy destruction and efficiency
|
|
155
|
+
self.E_D = self.E_F - self.E_P
|
|
156
|
+
self.epsilon = self.calc_epsilon()
|
|
157
|
+
|
|
158
|
+
# Log the results
|
|
159
|
+
logging.info(
|
|
160
|
+
f"Pump exergy balance calculated: "
|
|
161
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
162
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
167
|
+
"""
|
|
168
|
+
Auxiliary equations for the pump.
|
|
169
|
+
|
|
170
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
171
|
+
the following auxiliary cost relations:
|
|
172
|
+
|
|
173
|
+
(1) Chemical exergy cost equation (if enabled):
|
|
174
|
+
1/E_CH_in * C_CH_in - 1/E_CH_out * C_CH_out = 0
|
|
175
|
+
- F-principle: specific chemical exergy costs equalized between inlet/outlet
|
|
176
|
+
|
|
177
|
+
(2) Thermal/Mechanical exergy cost equations (based on temperature conditions):
|
|
178
|
+
|
|
179
|
+
Case 1 (T_in > T0, T_out > T0):
|
|
180
|
+
1/dET * C_T_out - 1/dET * C_T_in - 1/dEM * C_M_out + 1/dEM * C_M_in = 0
|
|
181
|
+
- P-principle: relates inlet/outlet thermal and mechanical exergy costs
|
|
182
|
+
|
|
183
|
+
Case 2 (T_in ≤ T0, T_out > T0):
|
|
184
|
+
1/E_T_out * C_T_out - 1/dEM * C_M_out + 1/dEM * C_M_in = 0
|
|
185
|
+
- P-principle: relates outlet thermal and inlet/outlet mechanical exergy costs
|
|
186
|
+
|
|
187
|
+
Case 3 (T_in ≤ T0, T_out ≤ T0):
|
|
188
|
+
1/E_T_out * C_T_out - 1/E_T_in * C_T_in = 0
|
|
189
|
+
- F-principle: specific thermal exergy costs equalized between inlet/outlet
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
A : numpy.ndarray
|
|
194
|
+
The current cost matrix.
|
|
195
|
+
b : numpy.ndarray
|
|
196
|
+
The current right-hand-side vector.
|
|
197
|
+
counter : int
|
|
198
|
+
The current row index in the matrix.
|
|
199
|
+
T0 : float
|
|
200
|
+
Ambient temperature.
|
|
201
|
+
equations : list or dict
|
|
202
|
+
Data structure for storing equation labels.
|
|
203
|
+
chemical_exergy_enabled : bool
|
|
204
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
A : numpy.ndarray
|
|
209
|
+
The updated cost matrix.
|
|
210
|
+
b : numpy.ndarray
|
|
211
|
+
The updated right-hand-side vector.
|
|
212
|
+
counter : int
|
|
213
|
+
The updated row index (increased by 2 if chemical exergy is enabled, or by 1 otherwise).
|
|
214
|
+
equations : list or dict
|
|
215
|
+
Updated structure with equation labels.
|
|
216
|
+
"""
|
|
217
|
+
# --- Chemical equality equation (row added only if enabled) ---
|
|
218
|
+
if chemical_exergy_enabled:
|
|
219
|
+
# Set the chemical cost equality:
|
|
220
|
+
A[counter, self.inl[0]["CostVar_index"]["CH"]] = (1 / self.inl[0]["E_CH"]) if self.inl[0]["e_CH"] != 0 else 1
|
|
221
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = (-1 / self.outl[0]["E_CH"]) if self.outl[0]["e_CH"] != 0 else 1
|
|
222
|
+
equations[counter] = f"aux_equality_chem_{self.outl[0]['name']}"
|
|
223
|
+
chem_row = 1
|
|
224
|
+
else:
|
|
225
|
+
chem_row = 0
|
|
226
|
+
|
|
227
|
+
# --- Thermal/Mechanical cost equation ---
|
|
228
|
+
# Compute differences in thermal and mechanical exergy:
|
|
229
|
+
dET = self.outl[0]["E_T"] - self.inl[0]["E_T"]
|
|
230
|
+
dEM = self.outl[0]["E_M"] - self.inl[0]["E_M"]
|
|
231
|
+
|
|
232
|
+
# The row for the thermal/mechanical equation:
|
|
233
|
+
row_index = counter + chem_row
|
|
234
|
+
if self.inl[0]["T"] > T0 and self.outl[0]["T"] > T0:
|
|
235
|
+
if dET != 0 and dEM != 0:
|
|
236
|
+
A[row_index, self.inl[0]["CostVar_index"]["T"]] = -1 / dET
|
|
237
|
+
A[row_index, self.outl[0]["CostVar_index"]["T"]] = 1 / dET
|
|
238
|
+
A[row_index, self.inl[0]["CostVar_index"]["M"]] = 1 / dEM
|
|
239
|
+
A[row_index, self.outl[0]["CostVar_index"]["M"]] = -1 / dEM
|
|
240
|
+
equations[row_index] = f"aux_p_rule_{self.name}"
|
|
241
|
+
else:
|
|
242
|
+
logging.warning("Case where thermal or mechanical exergy difference is zero is not implemented.")
|
|
243
|
+
elif self.inl[0]["T"] <= T0 and self.outl[0]["T"] > T0:
|
|
244
|
+
A[row_index, self.outl[0]["CostVar_index"]["T"]] = 1 / self.outl[0]["E_T"]
|
|
245
|
+
A[row_index, self.inl[0]["CostVar_index"]["M"]] = 1 / dEM
|
|
246
|
+
A[row_index, self.outl[0]["CostVar_index"]["M"]] = -1 / dEM
|
|
247
|
+
equations[row_index] = f"aux_p_rule_{self.name}"
|
|
248
|
+
else:
|
|
249
|
+
A[row_index, self.inl[0]["CostVar_index"]["T"]] = -1 / self.inl[0]["E_T"]
|
|
250
|
+
A[row_index, self.outl[0]["CostVar_index"]["T"]] = 1 / self.outl[0]["E_T"]
|
|
251
|
+
equations[row_index] = f"aux_f_rule_{self.name}"
|
|
252
|
+
|
|
253
|
+
# Set the right-hand side entry for the thermal/mechanical row to zero.
|
|
254
|
+
b[row_index] = 0
|
|
255
|
+
|
|
256
|
+
# Update the counter accordingly.
|
|
257
|
+
if chemical_exergy_enabled:
|
|
258
|
+
new_counter = counter + 2
|
|
259
|
+
else:
|
|
260
|
+
new_counter = counter + 1
|
|
261
|
+
|
|
262
|
+
return A, b, new_counter, equations
|
|
263
|
+
|
|
264
|
+
def exergoeconomic_balance(self, T0):
|
|
265
|
+
"""
|
|
266
|
+
Perform exergoeconomic balance calculations for the pump.
|
|
267
|
+
|
|
268
|
+
This method calculates various exergoeconomic parameters including:
|
|
269
|
+
- Cost rates of product (C_P) and fuel (C_F)
|
|
270
|
+
- Specific cost of product (c_P) and fuel (c_F)
|
|
271
|
+
- Cost rate of exergy destruction (C_D)
|
|
272
|
+
- Relative cost difference (r)
|
|
273
|
+
- Exergoeconomic factor (f)
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
T0 : float
|
|
278
|
+
Ambient temperature
|
|
279
|
+
|
|
280
|
+
Notes
|
|
281
|
+
-----
|
|
282
|
+
The exergoeconomic balance considers thermal (T), chemical (CH),
|
|
283
|
+
and mechanical (M) exergy components for the inlet and outlet streams.
|
|
284
|
+
"""
|
|
285
|
+
# Retrieve the cost of power from the inlet stream of kind "power"
|
|
286
|
+
power_cost = None
|
|
287
|
+
for stream in self.inl.values():
|
|
288
|
+
if stream.get("kind") == "power":
|
|
289
|
+
power_cost = stream.get("C_TOT")
|
|
290
|
+
break
|
|
291
|
+
if power_cost is None:
|
|
292
|
+
logging.error("No inlet power stream found to determine power cost (C_TOT).")
|
|
293
|
+
raise ValueError("No inlet power stream found for exergoeconomic_balance.")
|
|
294
|
+
|
|
295
|
+
# Compute product and fuel costs depending on inlet/outlet temperatures relative to T0.
|
|
296
|
+
if self.inl[0]["T"] >= T0 and self.outl[0]["T"] >= T0:
|
|
297
|
+
self.C_P = self.outl[0]["C_PH"] - self.inl[0]["C_PH"]
|
|
298
|
+
self.C_F = power_cost
|
|
299
|
+
elif self.inl[0]["T"] <= T0 and self.outl[0]["T"] > T0:
|
|
300
|
+
self.C_P = self.outl[0]["C_T"] + (self.outl[0]["C_M"] - self.inl[0]["C_M"])
|
|
301
|
+
self.C_F = power_cost + self.inl[0]["C_T"]
|
|
302
|
+
elif self.inl[0]["T"] <= T0 and self.outl[0]["T"] <= T0:
|
|
303
|
+
self.C_P = self.outl[0]["C_M"] - self.inl[0]["C_M"]
|
|
304
|
+
self.C_F = power_cost + (self.inl[0]["C_T"] - self.outl[0]["C_T"])
|
|
305
|
+
|
|
306
|
+
self.c_F = self.C_F / self.E_F
|
|
307
|
+
self.c_P = self.C_P / self.E_P
|
|
308
|
+
self.C_D = self.c_F * self.E_D
|
|
309
|
+
self.r = (self.C_P - self.C_F) / self.C_F
|
|
310
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D)
|
|
@@ -0,0 +1,351 @@
|
|
|
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 Turbine(Component):
|
|
11
|
+
r"""
|
|
12
|
+
Class for exergy analysis of turbines.
|
|
13
|
+
|
|
14
|
+
This class performs exergy analysis calculations for turbines, with definitions
|
|
15
|
+
of exergy product and fuel varying based on the temperature relationships between
|
|
16
|
+
inlet stream, outlet stream, and ambient conditions.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
**kwargs : dict
|
|
21
|
+
Arbitrary keyword arguments passed to parent class.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
E_F : float
|
|
26
|
+
Exergy fuel of the component :math:`\dot{E}_\mathrm{F}` in :math:`\mathrm{W}`.
|
|
27
|
+
E_P : float
|
|
28
|
+
Exergy product of the component :math:`\dot{E}_\mathrm{P}` in :math:`\mathrm{W}`.
|
|
29
|
+
E_D : float
|
|
30
|
+
Exergy destruction of the component :math:`\dot{E}_\mathrm{D}` in :math:`\mathrm{W}`.
|
|
31
|
+
epsilon : float
|
|
32
|
+
Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
|
|
33
|
+
P : float
|
|
34
|
+
Power output of the turbine in :math:`\mathrm{W}`.
|
|
35
|
+
inl : dict
|
|
36
|
+
Dictionary containing inlet stream data with temperature, mass flows,
|
|
37
|
+
enthalpies, and specific exergies. Must have at least one inlet.
|
|
38
|
+
outl : dict
|
|
39
|
+
Dictionary containing outlet streams data with temperature, mass flows,
|
|
40
|
+
enthalpies, and specific exergies. Can have multiple outlets, their
|
|
41
|
+
properties will be summed up in the calculations.
|
|
42
|
+
|
|
43
|
+
Notes
|
|
44
|
+
-----
|
|
45
|
+
The exergy analysis considers three cases based on temperature relationships:
|
|
46
|
+
|
|
47
|
+
.. math::
|
|
48
|
+
|
|
49
|
+
\dot{E}_\mathrm{P} =
|
|
50
|
+
\begin{cases}
|
|
51
|
+
-P & T_\mathrm{in}, T_\mathrm{out} \geq T_0\\
|
|
52
|
+
-P + \dot{E}_\mathrm{out}^\mathrm{T}
|
|
53
|
+
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
|
|
54
|
+
-P + \dot{E}_\mathrm{out}^\mathrm{T} - \dot{E}_\mathrm{in}^\mathrm{T}
|
|
55
|
+
& T_0 \geq T_\mathrm{in}, T_\mathrm{out}
|
|
56
|
+
\end{cases}
|
|
57
|
+
|
|
58
|
+
\dot{E}_\mathrm{F} =
|
|
59
|
+
\begin{cases}
|
|
60
|
+
\dot{E}_\mathrm{in}^\mathrm{PH} - \dot{E}_\mathrm{out}^\mathrm{PH}
|
|
61
|
+
& T_\mathrm{in}, T_\mathrm{out} \geq T_0\\
|
|
62
|
+
\dot{E}_\mathrm{in}^\mathrm{T} + \dot{E}_\mathrm{in}^\mathrm{M} -
|
|
63
|
+
\dot{E}_\mathrm{out}^\mathrm{M}
|
|
64
|
+
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
|
|
65
|
+
\dot{E}_\mathrm{in}^\mathrm{M} - \dot{E}_\mathrm{out}^\mathrm{M}
|
|
66
|
+
& T_0 \geq T_\mathrm{in}, T_\mathrm{out}
|
|
67
|
+
\end{cases}
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, **kwargs):
|
|
71
|
+
r"""Initialize turbine component with given parameters."""
|
|
72
|
+
super().__init__(**kwargs)
|
|
73
|
+
self.P = None
|
|
74
|
+
|
|
75
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
76
|
+
r"""
|
|
77
|
+
Calculate the exergy balance of the turbine.
|
|
78
|
+
|
|
79
|
+
Performs exergy balance calculations considering the temperature relationships
|
|
80
|
+
between inlet stream, outlet stream, and ambient conditions.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
T0 : float
|
|
85
|
+
Ambient temperature in :math:`\mathrm{K}`.
|
|
86
|
+
p0 : float
|
|
87
|
+
Ambient pressure in :math:`\mathrm{Pa}`.
|
|
88
|
+
split_physical_exergy : bool
|
|
89
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
90
|
+
"""
|
|
91
|
+
# Get power flow if not already available
|
|
92
|
+
if self.P is None:
|
|
93
|
+
self.P = self._total_outlet('m', 'h') - self.inl[0]['m'] * self.inl[0]['h']
|
|
94
|
+
|
|
95
|
+
# Case 1: Both temperatures above ambient
|
|
96
|
+
if self.inl[0]['T'] >= T0 and self.outl[0]['T'] >= T0 and self.inl[0]['T'] >= self.outl[0]['T']:
|
|
97
|
+
self.E_P = abs(self.P)
|
|
98
|
+
self.E_F = (self.inl[0]['m'] * self.inl[0]['e_PH'] -
|
|
99
|
+
self._total_outlet('m', 'e_PH'))
|
|
100
|
+
|
|
101
|
+
# Case 2: Inlet above, outlet at/below ambient
|
|
102
|
+
elif self.inl[0]['T'] > T0 and self.outl[0]['T'] <= T0:
|
|
103
|
+
if split_physical_exergy:
|
|
104
|
+
self.E_P = abs(self.P) + self._total_outlet('m', 'e_T')
|
|
105
|
+
self.E_F = (self.inl[0]['m'] * self.inl[0]['e_T'] +
|
|
106
|
+
self.inl[0]['m'] * self.inl[0]['e_M'] -
|
|
107
|
+
self._total_outlet('m', 'e_M'))
|
|
108
|
+
else:
|
|
109
|
+
logging.warning("While dealing with expander below ambient, "
|
|
110
|
+
"physical exergy should be split into thermal and mechanical components!")
|
|
111
|
+
self.E_P = np.nan
|
|
112
|
+
self.E_F = np.nan
|
|
113
|
+
|
|
114
|
+
# Case 3: Both temperatures at/below ambient
|
|
115
|
+
elif self.inl[0]['T'] <= T0 and self.outl[0]['T'] <= T0:
|
|
116
|
+
if split_physical_exergy:
|
|
117
|
+
self.E_P = abs(self.P) + (
|
|
118
|
+
self._total_outlet('m', 'e_T') - self.inl[0]['m'] * self.inl[0]['e_T'])
|
|
119
|
+
self.E_F = (self.inl[0]['m'] * self.inl[0]['e_M'] -
|
|
120
|
+
self._total_outlet('m', 'e_M'))
|
|
121
|
+
else:
|
|
122
|
+
logging.warning("While dealing with expander below ambient, "
|
|
123
|
+
"physical exergy should be split into thermal and mechanical components!")
|
|
124
|
+
self.E_P = np.nan
|
|
125
|
+
self.E_F = np.nan
|
|
126
|
+
# Invalid case: outlet temperature larger than inlet
|
|
127
|
+
else:
|
|
128
|
+
logging.warning(
|
|
129
|
+
'Exergy balance of a turbine where outlet temperature is larger '
|
|
130
|
+
'than inlet temperature is not implemented.'
|
|
131
|
+
)
|
|
132
|
+
self.E_P = np.nan
|
|
133
|
+
self.E_F = np.nan
|
|
134
|
+
|
|
135
|
+
# Calculate exergy destruction and efficiency
|
|
136
|
+
self.E_D = self.E_F - self.E_P
|
|
137
|
+
if self.E_F == np.nan:
|
|
138
|
+
self.E_D = self.inl[0]['m'] * self.inl[0]['e_PH'] - self._total_outlet('m', 'e_PH') - abs(self.P)
|
|
139
|
+
self.epsilon = self.calc_epsilon()
|
|
140
|
+
|
|
141
|
+
# Log the results
|
|
142
|
+
logging.info(
|
|
143
|
+
f"Turbine exergy balance calculated: "
|
|
144
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
145
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def _total_outlet(self, mass_flow: str, property_name: str) -> float:
|
|
149
|
+
r"""
|
|
150
|
+
Calculate the sum of mass flow times property across all outlets.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
mass_flow : str
|
|
155
|
+
Key for the mass flow value.
|
|
156
|
+
property_name : str
|
|
157
|
+
Key for the property to be summed.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
float
|
|
162
|
+
Sum of mass flow times property across all outlets.
|
|
163
|
+
"""
|
|
164
|
+
total = 0.0
|
|
165
|
+
for outlet in self.outl.values():
|
|
166
|
+
if outlet and mass_flow in outlet and property_name in outlet:
|
|
167
|
+
total += outlet[mass_flow] * outlet[property_name]
|
|
168
|
+
return total
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
172
|
+
"""
|
|
173
|
+
Auxiliary equations for the turbine.
|
|
174
|
+
|
|
175
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
176
|
+
the following auxiliary cost relations:
|
|
177
|
+
|
|
178
|
+
For each material outlet (when inlet and first outlet are above ambient temperature T0):
|
|
179
|
+
|
|
180
|
+
(1) 1/E_T_in * C_T_in - 1/E_T_out * C_T_out = 0
|
|
181
|
+
- F-principle: specific thermal exergy costs equalized between inlet and each outlet
|
|
182
|
+
|
|
183
|
+
(2) 1/E_M_in * C_M_in - 1/E_M_out * C_M_out = 0
|
|
184
|
+
- F-principle: specific mechanical exergy costs equalized between inlet and each outlet
|
|
185
|
+
|
|
186
|
+
(3) 1/E_CH_in * C_CH_in - 1/E_CH_out * C_CH_out = 0 (if chemical_exergy_enabled)
|
|
187
|
+
- F-principle: specific chemical exergy costs equalized between inlet and each outlet
|
|
188
|
+
|
|
189
|
+
For power outlets (with both source and target components):
|
|
190
|
+
|
|
191
|
+
(4) 1/E_ref * C_ref - 1/E_out * C_out = 0
|
|
192
|
+
- P-principle: specific power exergy costs equalized across all power outlets
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
A : numpy.ndarray
|
|
197
|
+
The current cost matrix.
|
|
198
|
+
b : numpy.ndarray
|
|
199
|
+
The current right-hand-side vector.
|
|
200
|
+
counter : int
|
|
201
|
+
The current row index in the matrix.
|
|
202
|
+
T0 : float
|
|
203
|
+
Ambient temperature.
|
|
204
|
+
equations : list or dict
|
|
205
|
+
Data structure for storing equation labels.
|
|
206
|
+
chemical_exergy_enabled : bool
|
|
207
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
A : numpy.ndarray
|
|
212
|
+
The updated cost matrix.
|
|
213
|
+
b : numpy.ndarray
|
|
214
|
+
The updated right-hand-side vector.
|
|
215
|
+
counter : int
|
|
216
|
+
The updated row index after adding all auxiliary equations.
|
|
217
|
+
equations : list or dict
|
|
218
|
+
Updated structure with equation labels.
|
|
219
|
+
"""
|
|
220
|
+
# Process only if the inlet and the first outlet are above T0.
|
|
221
|
+
if self.inl[0]["T"] > T0 and self.outl[0]["T"] > T0:
|
|
222
|
+
# Filter material outlets
|
|
223
|
+
material_outlets = [outlet for outlet in self.outl.values() if outlet.get("kind") == "material"]
|
|
224
|
+
# Determine number of rows per outlet.
|
|
225
|
+
num_rows_per_outlet = 3 if chemical_exergy_enabled else 2
|
|
226
|
+
|
|
227
|
+
for i, outlet in enumerate(material_outlets):
|
|
228
|
+
row_offset = num_rows_per_outlet * i
|
|
229
|
+
|
|
230
|
+
# --- Thermal exergy equation ---
|
|
231
|
+
A[counter + row_offset, self.inl[0]["CostVar_index"]["T"]] = (
|
|
232
|
+
1 / self.inl[0]["E_T"] if self.inl[0]["e_T"] != 0 else 1
|
|
233
|
+
)
|
|
234
|
+
A[counter + row_offset, outlet["CostVar_index"]["T"]] = (
|
|
235
|
+
-1 / outlet["E_T"] if outlet["e_T"] != 0 else -1
|
|
236
|
+
)
|
|
237
|
+
equations[counter + row_offset] = f"aux_f_rule_{outlet['name']}"
|
|
238
|
+
|
|
239
|
+
# --- Mechanical exergy equation ---
|
|
240
|
+
A[counter + row_offset + 1, self.inl[0]["CostVar_index"]["M"]] = (
|
|
241
|
+
1 / self.inl[0]["E_M"] if self.inl[0]["e_M"] != 0 else 1
|
|
242
|
+
)
|
|
243
|
+
A[counter + row_offset + 1, outlet["CostVar_index"]["M"]] = (
|
|
244
|
+
-1 / outlet["E_M"] if outlet["e_M"] != 0 else -1
|
|
245
|
+
)
|
|
246
|
+
equations[counter + row_offset + 1] = f"aux_f_rule_{outlet['name']}"
|
|
247
|
+
|
|
248
|
+
# --- Chemical exergy equation (conditionally added) ---
|
|
249
|
+
if chemical_exergy_enabled:
|
|
250
|
+
A[counter + row_offset + 2, self.inl[0]["CostVar_index"]["CH"]] = (
|
|
251
|
+
1 / self.inl[0]["E_CH"] if self.inl[0]["e_CH"] != 0 else 1
|
|
252
|
+
)
|
|
253
|
+
A[counter + row_offset + 2, outlet["CostVar_index"]["CH"]] = (
|
|
254
|
+
-1 / outlet["E_CH"] if outlet["e_CH"] != 0 else -1
|
|
255
|
+
)
|
|
256
|
+
equations[counter + row_offset + 2] = f"aux_f_rule_{outlet['name']}"
|
|
257
|
+
|
|
258
|
+
# Update counter based on number of rows added for all material outlets.
|
|
259
|
+
num_material_rows = num_rows_per_outlet * len(material_outlets)
|
|
260
|
+
for j in range(num_material_rows):
|
|
261
|
+
b[counter + j] = 0
|
|
262
|
+
counter += num_material_rows
|
|
263
|
+
else:
|
|
264
|
+
logging.warning("Turbine with outlet below T0 not implemented in exergoeconomics yet!")
|
|
265
|
+
|
|
266
|
+
# --- Auxiliary equation for shaft power equality ---
|
|
267
|
+
power_outlets = [outlet for outlet in self.outl.values()
|
|
268
|
+
if outlet.get("kind") == "power" and outlet.get("source_component") and outlet.get("target_component")]
|
|
269
|
+
if len(power_outlets) > 1:
|
|
270
|
+
ref = power_outlets[0]
|
|
271
|
+
ref_idx = ref["CostVar_index"]["exergy"]
|
|
272
|
+
for outlet in power_outlets[1:]:
|
|
273
|
+
cur_idx = outlet["CostVar_index"]["exergy"]
|
|
274
|
+
A[counter, ref_idx] = 1 / ref["E"] if ref["E"] != 0 else 1
|
|
275
|
+
A[counter, cur_idx] = -1 / outlet["E"] if outlet["E"] != 0 else -1
|
|
276
|
+
b[counter] = 0
|
|
277
|
+
equations[counter] = f"aux_p_rule_power_{self.name}_{outlet['name']}"
|
|
278
|
+
counter += 1
|
|
279
|
+
|
|
280
|
+
return A, b, counter, equations
|
|
281
|
+
|
|
282
|
+
def exergoeconomic_balance(self, T0):
|
|
283
|
+
"""
|
|
284
|
+
Perform exergoeconomic balance calculations for the turbine.
|
|
285
|
+
|
|
286
|
+
The turbine may have multiple power outputs and multiple material outputs. In this
|
|
287
|
+
function the cost of power is computed as the sum of C_TOT from all inlet streams of kind "power".
|
|
288
|
+
Material outlet costs are summed over all outlets of kind "material". The cost balance is then
|
|
289
|
+
computed according to the following cases:
|
|
290
|
+
|
|
291
|
+
Case 1 (both inlet and first outlet above T0):
|
|
292
|
+
C_P = (total power cost)
|
|
293
|
+
C_F = C_PH_inlet - (sum of C_PH from material outlets)
|
|
294
|
+
|
|
295
|
+
Case 2 (inlet above T0, outlet at or below T0):
|
|
296
|
+
C_P = (total power cost) + (sum of C_T from material outlets)
|
|
297
|
+
C_F = C_T_inlet + (C_M_inlet - (sum of C_M from material outlets))
|
|
298
|
+
|
|
299
|
+
Case 3 (both inlet and outlet at or below T0):
|
|
300
|
+
C_P = (total power cost) + ((sum of C_T from material outlets) - C_T_inlet)
|
|
301
|
+
C_F = C_M_inlet - (sum of C_M from material outlets)
|
|
302
|
+
|
|
303
|
+
Finally, the specific fuel cost (c_F), specific product cost (c_P), total cost destruction (C_D),
|
|
304
|
+
relative difference (r), and exergoeconomic factor (f) are calculated.
|
|
305
|
+
|
|
306
|
+
Parameters
|
|
307
|
+
----------
|
|
308
|
+
T0 : float
|
|
309
|
+
Ambient temperature.
|
|
310
|
+
|
|
311
|
+
Raises
|
|
312
|
+
------
|
|
313
|
+
ValueError
|
|
314
|
+
If required cost values are missing.
|
|
315
|
+
"""
|
|
316
|
+
# Sum the cost of all outlet power streams.
|
|
317
|
+
C_power_out = sum(stream.get("C_TOT", 0) for stream in self.outl.values() if stream.get("kind") == "power")
|
|
318
|
+
# Assume a single primary inlet for material cost properties.
|
|
319
|
+
inlet = self.inl[0]
|
|
320
|
+
# Filter material outlets and sum their cost components.
|
|
321
|
+
material_outlets = [out for out in self.outl.values() if out.get("kind") == "material"]
|
|
322
|
+
sum_C_PH_out = sum(out.get("C_PH", 0) for out in material_outlets)
|
|
323
|
+
sum_C_T_out = sum(out.get("C_T", 0) for out in material_outlets)
|
|
324
|
+
sum_C_M_out = sum(out.get("C_M", 0) for out in material_outlets)
|
|
325
|
+
|
|
326
|
+
# Case 1: Both inlet and first outlet above ambient.
|
|
327
|
+
if inlet["T"] >= T0 and self.outl[0]["T"] >= T0:
|
|
328
|
+
self.C_P = C_power_out
|
|
329
|
+
self.C_F = inlet.get("C_PH", 0) - sum_C_PH_out
|
|
330
|
+
|
|
331
|
+
# Case 2: Inlet above ambient and outlet at or below ambient.
|
|
332
|
+
elif inlet["T"] > T0 and self.outl[0]["T"] <= T0:
|
|
333
|
+
self.C_P = C_power_out + sum_C_T_out
|
|
334
|
+
self.C_F = inlet.get("C_T", 0) + (inlet.get("C_M", 0) - sum_C_M_out)
|
|
335
|
+
|
|
336
|
+
# Case 3: Both inlet and outlet at or below ambient.
|
|
337
|
+
elif inlet["T"] <= T0 and self.outl[0]["T"] <= T0:
|
|
338
|
+
self.C_P = C_power_out + (sum_C_T_out - inlet.get("C_T", 0))
|
|
339
|
+
self.C_F = inlet.get("C_M", 0) - sum_C_M_out
|
|
340
|
+
|
|
341
|
+
else:
|
|
342
|
+
logging.warning("Exergoeconomic balance of a turbine with outlet temperature larger than inlet is not implemented.")
|
|
343
|
+
self.C_P = np.nan
|
|
344
|
+
self.C_F = np.nan
|
|
345
|
+
|
|
346
|
+
# Calculate the specific cost terms and exergoeconomic parameters.
|
|
347
|
+
self.c_F = self.C_F / self.E_F
|
|
348
|
+
self.c_P = self.C_P / self.E_P
|
|
349
|
+
self.C_D = self.c_F * self.E_D
|
|
350
|
+
self.r = (self.C_P - self.C_F) / self.C_F
|
|
351
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D)
|