exerpy 0.0.1__py3-none-any.whl → 0.0.3__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 +2 -4
- exerpy/analyses.py +597 -297
- exerpy/components/__init__.py +3 -0
- exerpy/components/combustion/base.py +157 -114
- exerpy/components/component.py +8 -8
- exerpy/components/heat_exchanger/base.py +593 -256
- exerpy/components/heat_exchanger/condenser.py +353 -166
- exerpy/components/heat_exchanger/simple.py +575 -225
- exerpy/components/heat_exchanger/steam_generator.py +153 -123
- exerpy/components/helpers/cycle_closer.py +61 -34
- exerpy/components/helpers/power_bus.py +117 -0
- exerpy/components/nodes/deaerator.py +221 -102
- exerpy/components/nodes/drum.py +50 -39
- exerpy/components/nodes/flash_tank.py +218 -43
- exerpy/components/nodes/mixer.py +296 -115
- exerpy/components/nodes/splitter.py +173 -0
- exerpy/components/nodes/storage.py +130 -0
- exerpy/components/piping/valve.py +351 -139
- exerpy/components/power_machines/generator.py +105 -38
- exerpy/components/power_machines/motor.py +111 -39
- exerpy/components/turbomachinery/compressor.py +181 -63
- exerpy/components/turbomachinery/pump.py +182 -63
- exerpy/components/turbomachinery/turbine.py +182 -74
- exerpy/functions.py +388 -263
- exerpy/parser/from_aspen/aspen_config.py +57 -48
- exerpy/parser/from_aspen/aspen_parser.py +373 -280
- exerpy/parser/from_ebsilon/__init__.py +2 -2
- exerpy/parser/from_ebsilon/check_ebs_path.py +15 -19
- exerpy/parser/from_ebsilon/ebsilon_config.py +329 -227
- exerpy/parser/from_ebsilon/ebsilon_functions.py +205 -38
- exerpy/parser/from_ebsilon/ebsilon_parser.py +392 -255
- exerpy/parser/from_ebsilon/utils.py +16 -11
- exerpy/parser/from_tespy/tespy_config.py +32 -1
- exerpy/parser/from_tespy/tespy_parser.py +151 -0
- {exerpy-0.0.1.dist-info → exerpy-0.0.3.dist-info}/METADATA +45 -4
- exerpy-0.0.3.dist-info/RECORD +48 -0
- exerpy-0.0.1.dist-info/RECORD +0 -44
- {exerpy-0.0.1.dist-info → exerpy-0.0.3.dist-info}/WHEEL +0 -0
- {exerpy-0.0.1.dist-info → exerpy-0.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,123 +1,134 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from exerpy.components.component import Component
|
|
4
|
-
from exerpy.components.component import component_registry
|
|
3
|
+
from exerpy.components.component import Component, component_registry
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
@component_registry
|
|
8
7
|
class SteamGenerator(Component):
|
|
9
8
|
r"""
|
|
10
|
-
Class for exergy analysis of
|
|
9
|
+
Class for exergy and exergoeconomic analysis of heat exchangers.
|
|
11
10
|
|
|
12
|
-
This class performs exergy analysis calculations for
|
|
13
|
-
|
|
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.
|
|
11
|
+
This class performs exergy and exergoeconomic analysis calculations for heat exchanger components,
|
|
12
|
+
accounting for two inlet and two outlet streams across various temperature regimes, including
|
|
13
|
+
above and below ambient temperature, and optional dissipative behavior.
|
|
65
14
|
|
|
66
15
|
Attributes
|
|
67
16
|
----------
|
|
68
17
|
E_F : float
|
|
69
|
-
Exergy fuel of the component :math:`\dot{E}
|
|
18
|
+
Exergy fuel of the component :math:`\dot{E}_\mathrm{F}` in :math:`\mathrm{W}`.
|
|
70
19
|
E_P : float
|
|
71
|
-
Exergy product of the component :math:`\dot{E}
|
|
20
|
+
Exergy product of the component :math:`\dot{E}_\mathrm{P}` in :math:`\mathrm{W}`.
|
|
72
21
|
E_D : float
|
|
73
|
-
Exergy destruction of the component :math:`\dot{E}
|
|
22
|
+
Exergy destruction of the component :math:`\dot{E}_\mathrm{D}` in :math:`\mathrm{W}`.
|
|
74
23
|
epsilon : float
|
|
75
|
-
Exergetic efficiency :math:`\varepsilon
|
|
24
|
+
Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
|
|
76
25
|
inl : dict
|
|
77
|
-
Dictionary containing inlet
|
|
26
|
+
Dictionary containing inlet stream data with mass flows and specific exergies.
|
|
78
27
|
outl : dict
|
|
79
|
-
Dictionary containing outlet
|
|
28
|
+
Dictionary containing outlet stream data with mass flows and specific exergies.
|
|
29
|
+
Z_costs : float
|
|
30
|
+
Investment cost rate of the component in currency/h.
|
|
31
|
+
C_P : float
|
|
32
|
+
Cost of product stream :math:`\dot{C}_P` in currency/h.
|
|
33
|
+
C_F : float
|
|
34
|
+
Cost of fuel stream :math:`\dot{C}_F` in currency/h.
|
|
35
|
+
C_D : float
|
|
36
|
+
Cost of exergy destruction :math:`\dot{C}_D` in currency/h.
|
|
37
|
+
c_P : float
|
|
38
|
+
Specific cost of product stream (currency per unit exergy).
|
|
39
|
+
c_F : float
|
|
40
|
+
Specific cost of fuel stream (currency per unit exergy).
|
|
41
|
+
r : float
|
|
42
|
+
Relative cost difference, :math:`(c_P - c_F)/c_F`.
|
|
43
|
+
f : float
|
|
44
|
+
Exergoeconomic factor, :math:`\dot{Z}/(\dot{Z} + \dot{C}_D)`.
|
|
45
|
+
Ex_C_col : dict
|
|
46
|
+
Custom cost coefficients collection passed via `kwargs`.
|
|
47
|
+
|
|
48
|
+
Notes
|
|
49
|
+
-----
|
|
50
|
+
The component has several input and output streams as follows.
|
|
51
|
+
|
|
52
|
+
Inlet streams:
|
|
53
|
+
|
|
54
|
+
- inl[0]: Feed water inlet (high pressure)
|
|
55
|
+
- inl[1]: Steam inlet (intermediate pressure)
|
|
56
|
+
- inl[2]: Heat inlet (providing the heat input Q)
|
|
57
|
+
- inl[3]: Water injection (high pressure)
|
|
58
|
+
- inl[4]: Water injection (intermediate pressure)
|
|
59
|
+
|
|
60
|
+
Outlet streams:
|
|
61
|
+
|
|
62
|
+
- outl[0]: Superheated steam outlet (high pressure)
|
|
63
|
+
- outl[1]: Superheated steam outlet (intermediate pressure)
|
|
64
|
+
- outl[2]: Drain / Blow down outlet
|
|
65
|
+
|
|
80
66
|
"""
|
|
81
67
|
|
|
82
68
|
def __init__(self, **kwargs):
|
|
83
|
-
r"""
|
|
84
|
-
|
|
69
|
+
r"""
|
|
70
|
+
Initialize the steam generator component.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
**kwargs : dict
|
|
75
|
+
Arbitrary keyword arguments. Recognized keys:
|
|
76
|
+
- Ex_C_col (dict): custom cost coefficients, default {}
|
|
77
|
+
- Z_costs (float): investment cost rate in currency/h, default 0.0
|
|
78
|
+
"""
|
|
85
79
|
super().__init__(**kwargs)
|
|
86
80
|
|
|
87
81
|
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
88
82
|
r"""
|
|
89
|
-
|
|
83
|
+
Compute the exergy balance of the steam generator.
|
|
90
84
|
|
|
91
|
-
|
|
85
|
+
The exergy fuel is defined as follows.
|
|
86
|
+
|
|
87
|
+
If `split_physical_exergy` is `True`:
|
|
92
88
|
|
|
93
89
|
.. math::
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
\dot{E}_{\mathrm{F}}
|
|
91
|
+
= \bigl[\dot{E}^{\mathrm{T}}_{\mathrm{out,HP}} - \dot{E}^{\mathrm{T}}_{\mathrm{in,HP}}\bigr]
|
|
92
|
+
+ \bigl[\dot{E}^{\mathrm{T}}_{\mathrm{out,IP}} - \dot{E}^{\mathrm{T}}_{\mathrm{in,IP}}\bigr]
|
|
93
|
+
- \dot{E}^{\mathrm{T}}_{\mathrm{w,HP}}
|
|
94
|
+
- \dot{E}^{\mathrm{T}}_{\mathrm{w,IP}}
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
If `split_physical_exergy` is `False`:
|
|
98
97
|
|
|
99
98
|
.. math::
|
|
100
|
-
|
|
101
|
-
\dot{
|
|
102
|
-
+ \
|
|
103
|
-
\dot{
|
|
104
|
-
- \dot{
|
|
105
|
-
- \dot{m}_{\mathrm{w,IP}}\,e_{\mathrm{w,IP}}
|
|
99
|
+
\dot{E}_{\mathrm{F}}
|
|
100
|
+
= \bigl[\dot{E}^{\mathrm{PH}}_{\mathrm{out,HP}} - \dot{E}^{\mathrm{PH}}_{\mathrm{in,HP}}\bigr]
|
|
101
|
+
+ \bigl[\dot{E}^{\mathrm{PH}}_{\mathrm{out,IP}} - \dot{E}^{\mathrm{PH}}_{\mathrm{in,IP}}\bigr]
|
|
102
|
+
- \dot{E}^{\mathrm{PH}}_{\mathrm{w,HP}}
|
|
103
|
+
- \dot{E}^{\mathrm{PH}}_{\mathrm{w,IP}}
|
|
106
104
|
|
|
107
|
-
The exergy
|
|
105
|
+
The exergy product is defined as:
|
|
108
106
|
|
|
109
107
|
.. math::
|
|
110
|
-
E_D = E_F - E_P
|
|
111
108
|
|
|
112
|
-
|
|
109
|
+
\dot{E}_\mathrm{P} = \Bigl[ \dot E^{\mathrm{PH}}_{\mathrm{out,HP}}
|
|
110
|
+
- \dot E^{\mathrm{PH}}_{\mathrm{in,HP}} \Bigr]
|
|
111
|
+
+ \Bigl[ \dot E^{\mathrm{PH}}_{\mathrm{out,IP}}
|
|
112
|
+
- \dot E^{\mathrm{PH}}_{\mathrm{in,IP}} \Bigr]
|
|
113
|
+
- \dot E^{\mathrm{PH}}_{\mathrm{w,HP}}
|
|
114
|
+
- \dot E^{\mathrm{PH}}_{\mathrm{w,IP}}
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
where the subscripts HP and IP denote high and intermediate pressure streams,
|
|
117
|
+
respectively, and w stands for water injection.
|
|
116
118
|
|
|
117
119
|
Parameters
|
|
118
120
|
----------
|
|
119
121
|
T0 : float
|
|
120
|
-
Ambient temperature
|
|
122
|
+
Ambient temperature (K).
|
|
123
|
+
p0 : float
|
|
124
|
+
Ambient pressure (Pa).
|
|
125
|
+
split_physical_exergy : bool
|
|
126
|
+
Whether to split thermal and mechanical exergy.
|
|
127
|
+
|
|
128
|
+
Raises
|
|
129
|
+
------
|
|
130
|
+
ValueError
|
|
131
|
+
If required inlets or outlets are missing.
|
|
121
132
|
"""
|
|
122
133
|
# Ensure that all necessary streams exist
|
|
123
134
|
required_inlets = [0]
|
|
@@ -129,34 +140,35 @@ class SteamGenerator(Component):
|
|
|
129
140
|
if idx not in self.outl:
|
|
130
141
|
raise ValueError(f"Missing outlet stream with index {idx}.")
|
|
131
142
|
|
|
132
|
-
|
|
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'
|
|
143
|
+
exergy_type = "e_T" if split_physical_exergy else "e_PH"
|
|
142
144
|
|
|
143
145
|
# Calculate exergy fuel
|
|
144
146
|
# High pressure part: Superheated steam outlet (HP) minus Feed water inlet (HP)
|
|
145
|
-
E_F_HP = self.outl[0][
|
|
147
|
+
E_F_HP = self.outl[0]["m"] * self.outl[0][exergy_type] - self.inl[0]["m"] * self.inl[0][exergy_type]
|
|
146
148
|
# Intermediate pressure part: Superheated steam outlet (IP) minus Steam inlet (IP)
|
|
147
|
-
E_F_IP = self.outl.get(1, {}).get(
|
|
149
|
+
E_F_IP = self.outl.get(1, {}).get("m", 0) * self.outl.get(1, {}).get(exergy_type, 0) - self.inl.get(1, {}).get(
|
|
150
|
+
"m", 0
|
|
151
|
+
) * self.inl.get(1, {}).get(exergy_type, 0)
|
|
148
152
|
# Water injection contributions (assumed to be negative)
|
|
149
|
-
E_F_w_inj = self.inl.get(2, {}).get(
|
|
153
|
+
E_F_w_inj = self.inl.get(2, {}).get("m", 0) * self.inl.get(2, {}).get(exergy_type, 0) + self.inl.get(3, {}).get(
|
|
154
|
+
"m", 0
|
|
155
|
+
) * self.inl.get(3, {}).get(exergy_type, 0)
|
|
150
156
|
self.E_F = E_F_HP + E_F_IP - E_F_w_inj
|
|
151
|
-
logging.warning(
|
|
152
|
-
|
|
157
|
+
logging.warning(
|
|
158
|
+
"Since the temperature level of the heat source of the steam generator is unknown, "
|
|
159
|
+
"the exergy fuel of this component is calculated based on the thermal exergy value of the water streams."
|
|
160
|
+
)
|
|
153
161
|
# Calculate exergy product
|
|
154
162
|
# High pressure part: Superheated steam outlet (HP) minus Feed water inlet (HP)
|
|
155
|
-
E_P_HP = self.outl[0][
|
|
163
|
+
E_P_HP = self.outl[0]["m"] * self.outl[0]["e_PH"] - self.inl[0]["m"] * self.inl[0]["e_PH"]
|
|
156
164
|
# Intermediate pressure part: Superheated steam outlet (IP) minus Steam inlet (IP)
|
|
157
|
-
E_P_IP = self.outl.get(1, {}).get(
|
|
165
|
+
E_P_IP = self.outl.get(1, {}).get("m", 0) * self.outl.get(1, {}).get("e_PH", 0) - self.inl.get(1, {}).get(
|
|
166
|
+
"m", 0
|
|
167
|
+
) * self.inl.get(1, {}).get("e_PH", 0)
|
|
158
168
|
# Water injection contributions (assumed to be negative)
|
|
159
|
-
E_P_w_inj = self.inl.get(2, {}).get(
|
|
169
|
+
E_P_w_inj = self.inl.get(2, {}).get("m", 0) * self.inl.get(2, {}).get("e_PH", 0) + self.inl.get(3, {}).get(
|
|
170
|
+
"m", 0
|
|
171
|
+
) * self.inl.get(3, {}).get("e_PH", 0)
|
|
160
172
|
self.E_P = E_P_HP + E_P_IP - E_P_w_inj
|
|
161
173
|
|
|
162
174
|
# Calculate exergy destruction and efficiency
|
|
@@ -165,31 +177,39 @@ class SteamGenerator(Component):
|
|
|
165
177
|
|
|
166
178
|
# Log the results
|
|
167
179
|
logging.info(
|
|
168
|
-
f"SteamGenerator
|
|
180
|
+
f"Exergy balance of SteamGenerator {self.name} calculated: "
|
|
169
181
|
f"E_P = {self.E_P:.2f} W, E_F = {self.E_F:.2f} W, "
|
|
170
182
|
f"E_D = {self.E_D:.2f} W, Efficiency = {self.epsilon:.2%}"
|
|
171
183
|
)
|
|
172
184
|
|
|
173
|
-
|
|
174
185
|
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
186
|
+
r"""
|
|
187
|
+
This function must be implemented in the future.
|
|
188
|
+
|
|
189
|
+
The exergoeconomic analysis of SteamGenerator is not implemented yet.
|
|
190
|
+
"""
|
|
191
|
+
logging.error(
|
|
192
|
+
"The exergoeconomic analysis of SteamGenerator is not implemented yet. "
|
|
193
|
+
"This method will be implemented in a future release."
|
|
194
|
+
)
|
|
175
195
|
"""
|
|
176
196
|
Auxiliary equations for the steam generator.
|
|
177
|
-
|
|
197
|
+
|
|
178
198
|
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
179
199
|
the following auxiliary cost relations:
|
|
180
|
-
|
|
200
|
+
|
|
181
201
|
(1) c_T(heat_source)/E_F = c_T(HP_outlet)/E_T(HP) + c_T(IP_outlet)/E_T(IP)
|
|
182
202
|
- P-principle: thermal exergy costs from heat source are distributed to steam outlets
|
|
183
|
-
|
|
203
|
+
|
|
184
204
|
(2) 1/E_M_in(HP) * C_M_in(HP) - 1/E_M_out(HP) * C_M_out(HP) = 0
|
|
185
205
|
- F-principle: specific mechanical exergy costs equalized between HP inlet/outlet
|
|
186
|
-
|
|
206
|
+
|
|
187
207
|
(3) 1/E_M_in(IP) * C_M_in(IP) - 1/E_M_out(IP) * C_M_out(IP) = 0
|
|
188
208
|
- F-principle: specific mechanical exergy costs equalized between IP inlet/outlet
|
|
189
|
-
|
|
209
|
+
|
|
190
210
|
(4-5) Chemical exergy cost equations (if enabled) for HP and IP streams
|
|
191
211
|
- F-principle: specific chemical exergy costs equalized between inlets/outlets
|
|
192
|
-
|
|
212
|
+
|
|
193
213
|
Parameters
|
|
194
214
|
----------
|
|
195
215
|
A : numpy.ndarray
|
|
@@ -204,7 +224,7 @@ class SteamGenerator(Component):
|
|
|
204
224
|
Dictionary for storing equation labels.
|
|
205
225
|
chemical_exergy_enabled : bool
|
|
206
226
|
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
207
|
-
|
|
227
|
+
|
|
208
228
|
Returns
|
|
209
229
|
-------
|
|
210
230
|
A : numpy.ndarray
|
|
@@ -217,48 +237,58 @@ class SteamGenerator(Component):
|
|
|
217
237
|
Updated dictionary with equation labels.
|
|
218
238
|
"""
|
|
219
239
|
|
|
220
|
-
def exergoeconomic_balance(self, T0):
|
|
240
|
+
def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False):
|
|
241
|
+
r"""
|
|
242
|
+
This function must be implemented in the future.
|
|
243
|
+
|
|
244
|
+
The exergoeconomic analysis of SteamGenerator is not implemented yet.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
logging.error(
|
|
248
|
+
"The exergoeconomic analysis of SteamGenerator is not implemented yet. "
|
|
249
|
+
"This method will be implemented in a future release."
|
|
250
|
+
)
|
|
221
251
|
"""
|
|
222
252
|
Perform exergoeconomic balance calculations for the steam generator.
|
|
223
|
-
|
|
253
|
+
|
|
224
254
|
This method calculates various exergoeconomic parameters including:
|
|
225
255
|
- Cost rates of product (C_P) and fuel (C_F)
|
|
226
256
|
- Specific cost of product (c_P) and fuel (c_F)
|
|
227
257
|
- Cost rate of exergy destruction (C_D)
|
|
228
258
|
- Relative cost difference (r)
|
|
229
259
|
- Exergoeconomic factor (f)
|
|
230
|
-
|
|
260
|
+
|
|
231
261
|
Parameters
|
|
232
262
|
----------
|
|
233
263
|
T0 : float
|
|
234
264
|
Ambient temperature
|
|
235
|
-
|
|
265
|
+
chemical_exergy_enabled : bool, optional
|
|
266
|
+
If True, chemical exergy is considered in the calculations.
|
|
267
|
+
|
|
236
268
|
Notes
|
|
237
269
|
-----
|
|
238
270
|
The exergoeconomic balance considers thermal (T), chemical (CH),
|
|
239
271
|
and mechanical (M) exergy components for the inlet and outlet streams.
|
|
240
272
|
"""
|
|
241
273
|
# 1) Product cost rate: HP and IP steam net physical exergy costs, minus injection
|
|
242
|
-
C_P_hp =
|
|
243
|
-
- self.inl[0]['m'] * self.inl[0]['C_PH'])
|
|
274
|
+
C_P_hp = self.outl[0]["m"] * self.outl[0]["C_PH"] - self.inl[0]["m"] * self.inl[0]["C_PH"]
|
|
244
275
|
C_P_ip = 0.0
|
|
245
276
|
if 1 in self.outl and 1 in self.inl:
|
|
246
|
-
C_P_ip =
|
|
247
|
-
- self.inl[1]['m'] * self.inl[1]['C_PH'])
|
|
277
|
+
C_P_ip = self.outl[1]["m"] * self.outl[1]["C_PH"] - self.inl[1]["m"] * self.inl[1]["C_PH"]
|
|
248
278
|
# Subtract water injection costs
|
|
249
279
|
C_P_w = 0.0
|
|
250
280
|
if 3 in self.inl:
|
|
251
|
-
C_P_w += self.inl[3][
|
|
281
|
+
C_P_w += self.inl[3]["m"] * self.inl[3]["C_PH"]
|
|
252
282
|
if 4 in self.inl:
|
|
253
|
-
C_P_w += self.inl[4][
|
|
283
|
+
C_P_w += self.inl[4]["m"] * self.inl[4]["C_PH"]
|
|
254
284
|
self.C_P = C_P_hp + C_P_ip - C_P_w
|
|
255
285
|
|
|
256
286
|
# 2) Fuel cost rate: cost of heat exergy stream
|
|
257
|
-
self.C_F = self.inl[2][
|
|
287
|
+
self.C_F = self.inl[2]["C_T"]
|
|
258
288
|
|
|
259
289
|
# 3) Specific costs and destruction cost
|
|
260
|
-
self.c_F = self.C_F / self.E_F if self.E_F != 0 else float(
|
|
261
|
-
self.c_P = self.C_P / self.E_P if self.E_P != 0 else float(
|
|
290
|
+
self.c_F = self.C_F / self.E_F if self.E_F != 0 else float("nan")
|
|
291
|
+
self.c_P = self.C_P / self.E_P if self.E_P != 0 else float("nan")
|
|
262
292
|
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(
|
|
264
|
-
self.f = self.Z_costs / (self.Z_costs + self.C_D) if (self.Z_costs + self.C_D) != 0 else float(
|
|
293
|
+
self.r = (self.c_P - self.c_F) / self.c_F if self.c_F != 0 else float("nan")
|
|
294
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D) if (self.Z_costs + self.C_D) != 0 else float("nan")
|
|
@@ -2,15 +2,15 @@ import logging
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
|
-
from exerpy.components.component import Component
|
|
6
|
-
from exerpy.components.component import component_registry
|
|
5
|
+
from exerpy.components.component import Component, component_registry
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@component_registry
|
|
10
9
|
class CycleCloser(Component):
|
|
11
10
|
r"""
|
|
12
|
-
Component for closing cycles. This component is not
|
|
11
|
+
Component for closing cycles. This component is not considered in exergy analysis, but it is used in exergoeconomic analysis.
|
|
13
12
|
"""
|
|
13
|
+
|
|
14
14
|
def __init__(self, **kwargs):
|
|
15
15
|
r"""Initialize CycleCloser component with given parameters."""
|
|
16
16
|
super().__init__(**kwargs)
|
|
@@ -18,7 +18,7 @@ class CycleCloser(Component):
|
|
|
18
18
|
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
19
19
|
r"""
|
|
20
20
|
The CycleCloser component does not have an exergy balance calculation.
|
|
21
|
-
"""
|
|
21
|
+
"""
|
|
22
22
|
self.E_D = np.nan
|
|
23
23
|
self.E_F = np.nan
|
|
24
24
|
self.E_P = np.nan
|
|
@@ -26,24 +26,21 @@ class CycleCloser(Component):
|
|
|
26
26
|
self.epsilon = np.nan
|
|
27
27
|
|
|
28
28
|
# Log the results
|
|
29
|
-
logging.info(
|
|
30
|
-
f"The exergy balance of a CycleCloser component is skipped."
|
|
31
|
-
)
|
|
29
|
+
logging.info(f"The exergy balance of a CycleCloser {self.name} is skipped.")
|
|
32
30
|
|
|
33
|
-
|
|
34
31
|
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
35
32
|
"""
|
|
36
33
|
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
|
|
34
|
+
|
|
35
|
+
This function adds two rows to the cost matrix A and the right-hand side vector b to enforce
|
|
39
36
|
the following auxiliary cost relations:
|
|
40
|
-
|
|
37
|
+
|
|
41
38
|
(1) 1/E_M_in * C_M_in - 1/E_M_out * C_M_out = 0
|
|
42
39
|
(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
|
|
40
|
+
|
|
41
|
+
These equations ensure that the specific mechanical and thermal costs are equalized between
|
|
45
42
|
the inlet and outlet of the cycle closer. Chemical exergy is not considered for the cycle closer.
|
|
46
|
-
|
|
43
|
+
|
|
47
44
|
Parameters
|
|
48
45
|
----------
|
|
49
46
|
A : numpy.ndarray
|
|
@@ -59,7 +56,7 @@ class CycleCloser(Component):
|
|
|
59
56
|
chemical_exergy_enabled : bool
|
|
60
57
|
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
61
58
|
This flag is ignored for CycleCloser.
|
|
62
|
-
|
|
59
|
+
|
|
63
60
|
Returns
|
|
64
61
|
-------
|
|
65
62
|
A : numpy.ndarray
|
|
@@ -74,31 +71,61 @@ class CycleCloser(Component):
|
|
|
74
71
|
# Mechanical cost equality equation:
|
|
75
72
|
A[counter, self.inl[0]["CostVar_index"]["M"]] = (1 / self.inl[0]["e_M"]) if self.inl[0]["e_M"] != 0 else 1
|
|
76
73
|
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] =
|
|
74
|
+
equations[counter] = {
|
|
75
|
+
"kind": "aux_equality",
|
|
76
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
77
|
+
"property": "c_M",
|
|
78
|
+
}
|
|
78
79
|
b[counter] = 0
|
|
79
80
|
|
|
80
81
|
# 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"]] = (
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
A[counter + 1, self.inl[0]["CostVar_index"]["T"]] = (1 / self.inl[0]["e_T"]) if self.inl[0]["e_T"] != 0 else 1
|
|
83
|
+
A[counter + 1, self.outl[0]["CostVar_index"]["T"]] = (
|
|
84
|
+
(-1 / self.outl[0]["e_T"]) if self.outl[0]["e_T"] != 0 else -1
|
|
85
|
+
)
|
|
86
|
+
equations[counter + 1] = {
|
|
87
|
+
"kind": "aux_equality",
|
|
88
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
89
|
+
"property": "c_T",
|
|
90
|
+
}
|
|
91
|
+
b[counter + 1] = 0
|
|
85
92
|
|
|
86
93
|
counter += 2
|
|
94
|
+
|
|
95
|
+
if chemical_exergy_enabled:
|
|
96
|
+
# Chemical cost equality equation:
|
|
97
|
+
A[counter, self.inl[0]["CostVar_index"]["CH"]] = (1 / self.inl[0]["e_C"]) if self.inl[0]["e_CH"] != 0 else 1
|
|
98
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = (
|
|
99
|
+
(-1 / self.outl[0]["e_C"]) if self.outl[0]["e_CH"] != 0 else -1
|
|
100
|
+
)
|
|
101
|
+
equations[counter] = {
|
|
102
|
+
"kind": "aux_equality",
|
|
103
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
104
|
+
"property": "c_CH",
|
|
105
|
+
}
|
|
106
|
+
b[counter] = 0
|
|
107
|
+
|
|
108
|
+
counter += 1
|
|
109
|
+
|
|
87
110
|
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.
|
|
111
|
+
|
|
112
|
+
def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False) -> None:
|
|
96
113
|
"""
|
|
114
|
+
Exergoeconomic balance for the CycleCloser is not defined.
|
|
115
|
+
|
|
116
|
+
This component does not generate or consume exergy, so all cost terms are undefined.
|
|
97
117
|
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
T0 : float
|
|
121
|
+
Ambient temperature (unused).
|
|
122
|
+
chemical_exergy_enabled : bool, optional
|
|
123
|
+
If True, chemical exergy is considered in the calculations.
|
|
124
|
+
"""
|
|
125
|
+
self.C_F = np.nan
|
|
98
126
|
self.C_P = np.nan
|
|
99
|
-
self.
|
|
100
|
-
self.
|
|
101
|
-
self.
|
|
102
|
-
self.
|
|
103
|
-
self.
|
|
104
|
-
self.f = np.nan
|
|
127
|
+
self.C_D = np.nan
|
|
128
|
+
self.c_TOT = np.nan
|
|
129
|
+
self.C_TOT = np.nan
|
|
130
|
+
self.r = np.nan
|
|
131
|
+
self.f = np.nan
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from exerpy.components.component import Component, component_registry
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@component_registry
|
|
9
|
+
class PowerBus(Component):
|
|
10
|
+
r"""
|
|
11
|
+
Component for power busses. This component is not considered in exergy analysis, but it is used in exergoeconomic analysis.
|
|
12
|
+
"""
|
|
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 PowerBus 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(f"The exergy balance of a PowerBus {self.name} is skipped.")
|
|
30
|
+
|
|
31
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
32
|
+
"""
|
|
33
|
+
Auxiliary equations for the cycle closer.
|
|
34
|
+
|
|
35
|
+
This function adds two rows to the cost matrix A and the right-hand side vector b to enforce
|
|
36
|
+
the following auxiliary cost relations:
|
|
37
|
+
|
|
38
|
+
(1) 1/E_M_in * C_M_in - 1/E_M_out * C_M_out = 0
|
|
39
|
+
(2) 1/E_T_in * C_T_in - 1/E_T_out * C_T_out = 0
|
|
40
|
+
|
|
41
|
+
These equations ensure that the specific mechanical and thermal costs are equalized between
|
|
42
|
+
the inlet and outlet of the cycle closer. Chemical exergy is not considered for the cycle closer.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
A : numpy.ndarray
|
|
47
|
+
The current cost matrix.
|
|
48
|
+
b : numpy.ndarray
|
|
49
|
+
The current right-hand-side vector.
|
|
50
|
+
counter : int
|
|
51
|
+
The current row index in the matrix.
|
|
52
|
+
T0 : float
|
|
53
|
+
Ambient temperature (not used in this component).
|
|
54
|
+
equations : list or dict
|
|
55
|
+
Data structure for storing equation labels.
|
|
56
|
+
chemical_exergy_enabled : bool
|
|
57
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
58
|
+
This flag is ignored for CycleCloser.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
A : numpy.ndarray
|
|
63
|
+
The updated cost matrix.
|
|
64
|
+
b : numpy.ndarray
|
|
65
|
+
The updated right-hand-side vector.
|
|
66
|
+
counter : int
|
|
67
|
+
The updated row index (increased by 2).
|
|
68
|
+
equations : list or dict
|
|
69
|
+
Updated structure with equation labels.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# Splitter case
|
|
73
|
+
if len(self.inl) >= 1 and len(self.outl) <= 1:
|
|
74
|
+
logging.info(f"PowerBus {self.name} has only one output, no auxiliary equations added.")
|
|
75
|
+
|
|
76
|
+
# Mixer case
|
|
77
|
+
elif len(self.inl) == 1 and len(self.outl) > 1:
|
|
78
|
+
logging.info(f"PowerBus {self.name} has multiple outputs, auxiliary equations will be added.")
|
|
79
|
+
for out in list(self.outl.values())[:]:
|
|
80
|
+
A[counter, self.inl[0]["CostVar_index"]["exergy"]] = (
|
|
81
|
+
(1 / self.inl[0]["E"]) if self.inl[0]["E"] != 0 else 1
|
|
82
|
+
)
|
|
83
|
+
A[counter, out["CostVar_index"]["exergy"]] = (-1 / out["E"]) if out["E"] != 0 else -1
|
|
84
|
+
equations[counter] = {
|
|
85
|
+
"kind": "aux_power_eq",
|
|
86
|
+
"objects": [self.name, self.inl[0]["name"], out["name"]],
|
|
87
|
+
"property": "c_TOT",
|
|
88
|
+
}
|
|
89
|
+
b[counter] = 0
|
|
90
|
+
counter += 1
|
|
91
|
+
|
|
92
|
+
# Mixer case with multiple inputs and outputs
|
|
93
|
+
else:
|
|
94
|
+
logging.error(f"PowerBus {self.name} has multiple inputs and outputs, which has not been implemented yet.")
|
|
95
|
+
|
|
96
|
+
return A, b, counter, equations
|
|
97
|
+
|
|
98
|
+
def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Exergoeconomic balance for the PowerBus is not defined.
|
|
101
|
+
|
|
102
|
+
This component does not convert or destroy exergy, so all cost terms are undefined.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
T0 : float
|
|
107
|
+
Ambient temperature (unused).
|
|
108
|
+
chemical_exergy_enabled : bool, optional
|
|
109
|
+
If True, chemical exergy is considered in the calculations.
|
|
110
|
+
"""
|
|
111
|
+
self.C_F = np.nan
|
|
112
|
+
self.C_P = np.nan
|
|
113
|
+
self.C_D = np.nan
|
|
114
|
+
self.c_TOT = np.nan
|
|
115
|
+
self.C_TOT = np.nan
|
|
116
|
+
self.r = np.nan
|
|
117
|
+
self.f = np.nan
|