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
|
@@ -2,24 +2,17 @@ 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 Valve(Component):
|
|
11
10
|
r"""
|
|
12
|
-
Class for exergy analysis of valves.
|
|
11
|
+
Class for exergy and exergoeconomic analysis of valves.
|
|
13
12
|
|
|
14
|
-
This class performs exergy analysis calculations for
|
|
15
|
-
one inlet and one outlet
|
|
16
|
-
|
|
17
|
-
and ambient conditions.
|
|
18
|
-
|
|
19
|
-
Parameters
|
|
20
|
-
----------
|
|
21
|
-
**kwargs : dict
|
|
22
|
-
Arbitrary keyword arguments passed to parent class.
|
|
13
|
+
This class performs exergy and exergoeconomic analysis calculations for valve components,
|
|
14
|
+
accounting for one inlet and one outlet streams across various temperature regimes, including
|
|
15
|
+
above and below ambient temperature.
|
|
23
16
|
|
|
24
17
|
Attributes
|
|
25
18
|
----------
|
|
@@ -32,11 +25,27 @@ class Valve(Component):
|
|
|
32
25
|
epsilon : float
|
|
33
26
|
Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
|
|
34
27
|
inl : dict
|
|
35
|
-
Dictionary containing inlet stream data with
|
|
36
|
-
and specific exergies.
|
|
28
|
+
Dictionary containing inlet stream data with mass flows and specific exergies.
|
|
37
29
|
outl : dict
|
|
38
|
-
Dictionary containing outlet stream data with
|
|
39
|
-
|
|
30
|
+
Dictionary containing outlet stream data with mass flows and specific exergies.
|
|
31
|
+
Z_costs : float
|
|
32
|
+
Investment cost rate of the component in currency/h.
|
|
33
|
+
C_P : float
|
|
34
|
+
Cost of product stream :math:`\dot{C}_P` in currency/h.
|
|
35
|
+
C_F : float
|
|
36
|
+
Cost of fuel stream :math:`\dot{C}_F` in currency/h.
|
|
37
|
+
C_D : float
|
|
38
|
+
Cost of exergy destruction :math:`\dot{C}_D` in currency/h.
|
|
39
|
+
c_P : float
|
|
40
|
+
Specific cost of product stream (currency per unit exergy).
|
|
41
|
+
c_F : float
|
|
42
|
+
Specific cost of fuel stream (currency per unit exergy).
|
|
43
|
+
r : float
|
|
44
|
+
Relative cost difference, :math:`(c_P - c_F)/c_F`.
|
|
45
|
+
f : float
|
|
46
|
+
Exergoeconomic factor, :math:`\dot{Z}/(\dot{Z} + \dot{C}_D)`.
|
|
47
|
+
Ex_C_col : dict
|
|
48
|
+
Custom cost coefficients collection passed via `kwargs`.
|
|
40
49
|
|
|
41
50
|
Notes
|
|
42
51
|
-----
|
|
@@ -47,7 +56,7 @@ class Valve(Component):
|
|
|
47
56
|
|
|
48
57
|
\dot{E}_\mathrm{P} =
|
|
49
58
|
\begin{cases}
|
|
50
|
-
\
|
|
59
|
+
\text{not defined (nan)}
|
|
51
60
|
& T_\mathrm{in}, T_\mathrm{out} > T_0\\
|
|
52
61
|
\dot{m} \cdot e_\mathrm{out}^\mathrm{T}
|
|
53
62
|
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
|
|
@@ -59,19 +68,19 @@ class Valve(Component):
|
|
|
59
68
|
\begin{cases}
|
|
60
69
|
\dot{m} \cdot (e_\mathrm{in}^\mathrm{PH} - e_\mathrm{out}^\mathrm{PH})
|
|
61
70
|
& T_\mathrm{in}, T_\mathrm{out} > T_0\\
|
|
62
|
-
\dot{m} \cdot (e_\mathrm{in}^\mathrm{T} + e_\mathrm{in}^\mathrm{M}
|
|
71
|
+
\dot{m} \cdot (e_\mathrm{in}^\mathrm{T} + e_\mathrm{in}^\mathrm{M}
|
|
63
72
|
- e_\mathrm{out}^\mathrm{M})
|
|
64
73
|
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
|
|
65
74
|
\dot{m} \cdot (e_\mathrm{in}^\mathrm{M} - e_\mathrm{out}^\mathrm{M})
|
|
66
75
|
& T_0 \geq T_\mathrm{in}, T_\mathrm{out}
|
|
67
76
|
\end{cases}
|
|
68
77
|
|
|
69
|
-
For all cases, except when :math:`T_\mathrm{out} > T_\mathrm{in}`, the exergy
|
|
78
|
+
For all cases, except when :math:`T_\mathrm{out} > T_\mathrm{in}`, the exergy
|
|
70
79
|
destruction is calculated as:
|
|
71
80
|
|
|
72
81
|
.. math::
|
|
73
82
|
\dot{E}_\mathrm{D} = \begin{cases}
|
|
74
|
-
\dot{E}_\mathrm{F} & \
|
|
83
|
+
\dot{E}_\mathrm{F} & \text{if } \dot{E}_\mathrm{P} = \text{nan}\\
|
|
75
84
|
\dot{E}_\mathrm{F} - \dot{E}_\mathrm{P} & \mathrm{otherwise}
|
|
76
85
|
\end{cases}
|
|
77
86
|
|
|
@@ -105,36 +114,64 @@ class Valve(Component):
|
|
|
105
114
|
------
|
|
106
115
|
ValueError
|
|
107
116
|
If the required inlet and outlet streams are not properly defined.
|
|
108
|
-
"""
|
|
117
|
+
"""
|
|
109
118
|
# Ensure that the component has both inlet and outlet streams
|
|
110
119
|
if len(self.inl) < 1 or len(self.outl) < 1:
|
|
111
120
|
raise ValueError("Valve requires at least one inlet and one outlet.")
|
|
112
121
|
|
|
113
|
-
T_in = self.inl[0][
|
|
114
|
-
T_out = self.outl[0][
|
|
122
|
+
T_in = self.inl[0]["T"]
|
|
123
|
+
T_out = self.outl[0]["T"]
|
|
124
|
+
|
|
125
|
+
p_in = self.inl[0]["p"]
|
|
126
|
+
p_out = self.outl[0]["p"]
|
|
115
127
|
|
|
116
|
-
#
|
|
117
|
-
if
|
|
128
|
+
# Check for zero mass flow
|
|
129
|
+
if abs(self.inl[0]["m"]) < 1e-10:
|
|
130
|
+
logging.info(f"Valve {self.name} has zero mass flow: exergy balance not considered.")
|
|
118
131
|
self.E_P = np.nan
|
|
119
|
-
self.E_F =
|
|
120
|
-
|
|
132
|
+
self.E_F = np.nan
|
|
133
|
+
self.E_D = np.nan
|
|
134
|
+
self.epsilon = np.nan
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# Check if inlet and outlet are physically identical
|
|
138
|
+
if abs(T_in - T_out) < 1e-2 and abs(p_in - p_out) <= 1e-4 * max(p_in, 1e-9):
|
|
139
|
+
logging.info(f"Valve {self.name} inlet and outlet are physically identical.")
|
|
140
|
+
self.E_P = 0.0
|
|
141
|
+
self.E_F = 0.0
|
|
142
|
+
self.E_D = 0.0
|
|
143
|
+
self.epsilon = 1.0
|
|
144
|
+
logging.info(
|
|
145
|
+
f"Valve exergy balance calculated: "
|
|
146
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
147
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
148
|
+
)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Case 1: Both temperatures above ambient
|
|
152
|
+
if T_in > T0 and T_out > T0:
|
|
153
|
+
self.E_P = np.nan
|
|
154
|
+
self.E_F = self.inl[0]["m"] * (self.inl[0]["e_PH"] - self.outl[0]["e_PH"])
|
|
155
|
+
|
|
156
|
+
# Case 2: Inlet above ambient, outlet below or equal to ambient
|
|
157
|
+
elif T_in > T0 and T_out <= T0:
|
|
121
158
|
if split_physical_exergy:
|
|
122
|
-
self.E_P = self.inl[0][
|
|
123
|
-
self.E_F = self.inl[0][
|
|
124
|
-
self.outl[0]['e_M'])
|
|
159
|
+
self.E_P = self.inl[0]["m"] * self.outl[0]["e_T"]
|
|
160
|
+
self.E_F = self.inl[0]["m"] * (self.inl[0]["e_T"] + self.inl[0]["e_M"] - self.outl[0]["e_M"])
|
|
125
161
|
else:
|
|
126
162
|
logging.warning(
|
|
127
163
|
"Exergy balance of a valve, where outlet temperature is smaller than "
|
|
128
|
-
"ambient temperature, is not implemented for non-split physical exergy."
|
|
164
|
+
"ambient temperature, is not implemented for non-split physical exergy. "
|
|
129
165
|
"Valve is treated as dissipative."
|
|
130
166
|
)
|
|
131
167
|
self.E_P = np.nan
|
|
132
|
-
self.E_F = self.inl[0][
|
|
168
|
+
self.E_F = self.inl[0]["m"] * (self.inl[0]["e_PH"] - self.outl[0]["e_PH"])
|
|
133
169
|
|
|
170
|
+
# Case 3: Both temperatures below ambient
|
|
134
171
|
elif T_in <= T0 and T_out <= T0:
|
|
135
172
|
if split_physical_exergy:
|
|
136
|
-
self.E_P = self.inl[0][
|
|
137
|
-
self.E_F = self.inl[0][
|
|
173
|
+
self.E_P = self.inl[0]["m"] * (self.outl[0]["e_T"] - self.inl[0]["e_T"])
|
|
174
|
+
self.E_F = self.inl[0]["m"] * (self.inl[0]["e_M"] - self.outl[0]["e_M"])
|
|
138
175
|
else:
|
|
139
176
|
logging.warning(
|
|
140
177
|
"Exergy balance of a valve, where both temperatures are smaller than "
|
|
@@ -142,14 +179,28 @@ class Valve(Component):
|
|
|
142
179
|
"Valve is treated as dissipative."
|
|
143
180
|
)
|
|
144
181
|
self.E_P = np.nan
|
|
145
|
-
self.E_F = self.inl[0][
|
|
146
|
-
|
|
182
|
+
self.E_F = self.inl[0]["m"] * (self.inl[0]["e_PH"] - self.outl[0]["e_PH"])
|
|
183
|
+
|
|
184
|
+
# Case 4: Inlet below or at ambient, outlet above ambient
|
|
185
|
+
elif T_in <= T0 and T_out > T0:
|
|
147
186
|
logging.warning(
|
|
148
|
-
"
|
|
149
|
-
"
|
|
187
|
+
f"Valve {self.name} with temperature increase from below ambient to above ambient - "
|
|
188
|
+
"non-physical behavior. Treated as dissipative."
|
|
189
|
+
)
|
|
190
|
+
self.E_P = np.nan
|
|
191
|
+
self.E_F = self.inl[0]["m"] * (self.inl[0]["e_PH"] - self.outl[0]["e_PH"])
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
logging.error(
|
|
195
|
+
f"Valve {self.name} encountered an unexpected condition: "
|
|
196
|
+
f"T_in={T_in:.2f} K, T_out={T_out:.2f} K, T0={T0:.2f} K. "
|
|
197
|
+
"This should not occur - exergy balance cannot be calculated."
|
|
150
198
|
)
|
|
151
199
|
self.E_P = np.nan
|
|
152
200
|
self.E_F = np.nan
|
|
201
|
+
self.E_D = np.nan
|
|
202
|
+
self.epsilon = np.nan
|
|
203
|
+
return
|
|
153
204
|
|
|
154
205
|
# Calculate exergy destruction
|
|
155
206
|
if np.isnan(self.E_P):
|
|
@@ -162,32 +213,34 @@ class Valve(Component):
|
|
|
162
213
|
|
|
163
214
|
# Log the results
|
|
164
215
|
logging.info(
|
|
165
|
-
f"Valve
|
|
216
|
+
f"Exergy balance of Valve {self.name} calculated: "
|
|
166
217
|
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
167
218
|
f"Efficiency={self.epsilon:.2%}"
|
|
168
219
|
)
|
|
169
220
|
|
|
170
|
-
|
|
171
221
|
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
172
222
|
"""
|
|
173
223
|
Auxiliary equations for the valve.
|
|
174
|
-
|
|
224
|
+
|
|
175
225
|
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
176
226
|
the following auxiliary cost relations:
|
|
177
|
-
|
|
178
|
-
For T_in > T0 and T_out > T0:
|
|
227
|
+
|
|
228
|
+
For (T_in > T0 and T_out > T0) or (T_in <= T0 and T_out > T0):
|
|
179
229
|
- Valve is treated as dissipative (warning issued)
|
|
180
|
-
|
|
230
|
+
|
|
181
231
|
For T_out <= T0:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
232
|
+
(1) 1/E_M_in * C_M_in - 1/E_M_out * C_M_out = 0
|
|
233
|
+
|
|
234
|
+
- F-principle: specific mechanical exergy costs equalized between inlet/outlet
|
|
235
|
+
|
|
236
|
+
- If E_M is zero for either stream, appropriate fallback coefficients are used
|
|
237
|
+
|
|
186
238
|
When chemical_exergy_enabled is True:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
239
|
+
(2) 1/E_CH_in * C_CH_in - 1/E_CH_out * C_CH_out = 0
|
|
240
|
+
|
|
241
|
+
- F-principle: specific chemical exergy costs equalized between inlet/outlet
|
|
242
|
+
- If E_CH is zero for either stream, appropriate fallback coefficients are used
|
|
243
|
+
|
|
191
244
|
Parameters
|
|
192
245
|
----------
|
|
193
246
|
A : numpy.ndarray
|
|
@@ -202,7 +255,7 @@ class Valve(Component):
|
|
|
202
255
|
Dictionary for storing equation labels.
|
|
203
256
|
chemical_exergy_enabled : bool
|
|
204
257
|
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
205
|
-
|
|
258
|
+
|
|
206
259
|
Returns
|
|
207
260
|
-------
|
|
208
261
|
A : numpy.ndarray
|
|
@@ -214,51 +267,65 @@ class Valve(Component):
|
|
|
214
267
|
equations : dict
|
|
215
268
|
Updated dictionary with equation labels.
|
|
216
269
|
"""
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
equations[counter] = f"aux_{self.name}_mech_{self.outl[0]['name']}"
|
|
233
|
-
b[counter] = 0
|
|
234
|
-
counter += 1
|
|
270
|
+
|
|
271
|
+
# Check if valve is dissipative
|
|
272
|
+
if np.isnan(self.E_P):
|
|
273
|
+
logging.warning(f"Valve {self.name} is dissipative - no auxiliary equations added.")
|
|
274
|
+
return A, b, counter, equations
|
|
275
|
+
|
|
276
|
+
# Productive valve - T_out must be ≤ T0 (Cases 2 or 3)
|
|
277
|
+
# Mechanical cost equation (always added for productive valves)
|
|
278
|
+
if self.inl[0]["e_M"] != 0 and self.outl[0]["e_M"] != 0:
|
|
279
|
+
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1 / self.inl[0]["E_M"]
|
|
280
|
+
A[counter, self.outl[0]["CostVar_index"]["M"]] = -1 / self.outl[0]["E_M"]
|
|
281
|
+
elif self.inl[0]["e_M"] == 0 and self.outl[0]["e_M"] != 0:
|
|
282
|
+
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1
|
|
283
|
+
elif self.inl[0]["e_M"] != 0 and self.outl[0]["e_M"] == 0:
|
|
284
|
+
A[counter, self.outl[0]["CostVar_index"]["M"]] = 1
|
|
235
285
|
else:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
286
|
+
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1
|
|
287
|
+
A[counter, self.outl[0]["CostVar_index"]["M"]] = -1
|
|
288
|
+
equations[counter] = {
|
|
289
|
+
"kind": "aux_equality",
|
|
290
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
291
|
+
"property": "c_M",
|
|
292
|
+
}
|
|
293
|
+
b[counter] = 0
|
|
294
|
+
counter += 1
|
|
295
|
+
|
|
239
296
|
if chemical_exergy_enabled:
|
|
240
297
|
# --- Chemical cost equation (conditionally added) ---
|
|
241
|
-
A[counter, self.inl[0]["CostVar_index"]["CH"]] =
|
|
242
|
-
A[counter, self.outl[0]["CostVar_index"]["CH"]] = (
|
|
243
|
-
|
|
298
|
+
A[counter, self.inl[0]["CostVar_index"]["CH"]] = 1 / self.inl[0]["E_CH"] if self.inl[0]["e_CH"] != 0 else 1
|
|
299
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = (
|
|
300
|
+
-1 / self.outl[0]["E_CH"] if self.outl[0]["e_CH"] != 0 else -1
|
|
301
|
+
)
|
|
302
|
+
equations[counter] = {
|
|
303
|
+
"kind": "aux_equality",
|
|
304
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
305
|
+
"property": "c_CH",
|
|
306
|
+
}
|
|
244
307
|
# Set right-hand side for both rows.
|
|
245
308
|
b[counter] = 0
|
|
246
309
|
counter += 1
|
|
247
|
-
|
|
310
|
+
|
|
248
311
|
return A, b, counter, equations
|
|
249
|
-
|
|
312
|
+
|
|
250
313
|
def dis_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled=False, all_components=None):
|
|
251
|
-
"""
|
|
314
|
+
r"""
|
|
252
315
|
Constructs the cost equations for a dissipative Valve in ExerPy,
|
|
253
|
-
distributing the valve
|
|
316
|
+
distributing the valve's extra cost difference (C_diff) to all other productive
|
|
254
317
|
components (non-dissipative and non-CycleCloser) in proportion to their exergy destruction (E_D)
|
|
255
318
|
and adding an extra overall cost balance row that enforces:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
319
|
+
|
|
320
|
+
.. math::
|
|
321
|
+
(\dot C_{\mathrm{in},T} - \dot C_{\mathrm{out},T})
|
|
322
|
+
+ (\dot C_{\mathrm{in},M} - \dot C_{\mathrm{out},M})
|
|
323
|
+
- \dot C_{\mathrm{diff}}
|
|
324
|
+
= -\,\dot Z_{\mathrm{costs}}
|
|
325
|
+
|
|
259
326
|
In this formulation, the unknown cost variable in the "dissipative" column (i.e. C_diff)
|
|
260
|
-
is solved for, ensuring the valve
|
|
261
|
-
|
|
327
|
+
is solved for, ensuring the valve's cost balance.
|
|
328
|
+
|
|
262
329
|
Parameters
|
|
263
330
|
----------
|
|
264
331
|
A : numpy.ndarray
|
|
@@ -275,16 +342,15 @@ class Valve(Component):
|
|
|
275
342
|
Flag indicating whether chemical exergy is considered. (Ignored here.)
|
|
276
343
|
all_components : list, optional
|
|
277
344
|
Global list of all component objects; if not provided, defaults to [].
|
|
278
|
-
|
|
345
|
+
|
|
279
346
|
Returns
|
|
280
347
|
-------
|
|
281
348
|
tuple
|
|
282
349
|
Updated (A, b, counter, equations).
|
|
283
|
-
|
|
350
|
+
|
|
284
351
|
Notes
|
|
285
352
|
-----
|
|
286
|
-
- It is assumed that each inlet/outlet stream
|
|
287
|
-
"T" (thermal), "M" (mechanical), and "dissipative" (the extra unknown).
|
|
353
|
+
- It is assumed that each inlet/outlet stream's CostVar_index dictionary has keys: "T" (thermal), "M" (mechanical), and "dissipative" (the extra unknown).
|
|
288
354
|
- self.Z_costs is the known cost rate (in currency/s) for the valve.
|
|
289
355
|
"""
|
|
290
356
|
# --- Thermal difference row ---
|
|
@@ -295,7 +361,11 @@ class Valve(Component):
|
|
|
295
361
|
A[counter, self.inl[0]["CostVar_index"]["T"]] = 1
|
|
296
362
|
A[counter, self.outl[0]["CostVar_index"]["T"]] = -1
|
|
297
363
|
b[counter] = 0
|
|
298
|
-
equations[counter] =
|
|
364
|
+
equations[counter] = {
|
|
365
|
+
"kind": "dis_equality",
|
|
366
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
367
|
+
"property": "c_T",
|
|
368
|
+
}
|
|
299
369
|
counter += 1
|
|
300
370
|
|
|
301
371
|
# --- Mechanical difference row ---
|
|
@@ -306,30 +376,56 @@ class Valve(Component):
|
|
|
306
376
|
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1
|
|
307
377
|
A[counter, self.outl[0]["CostVar_index"]["M"]] = -1
|
|
308
378
|
b[counter] = 0
|
|
309
|
-
equations[counter] =
|
|
379
|
+
equations[counter] = {
|
|
380
|
+
"kind": "dis_equality",
|
|
381
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
382
|
+
"property": "c_M",
|
|
383
|
+
}
|
|
310
384
|
counter += 1
|
|
311
385
|
|
|
386
|
+
# --- Chemical difference row (if chemical exergy is enabled) ---
|
|
387
|
+
if chemical_exergy_enabled:
|
|
388
|
+
A[counter, self.inl[0]["CostVar_index"]["CH"]] = 1 / self.inl[0]["E_CH"] if self.inl[0]["E_CH"] != 0 else 1
|
|
389
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = (
|
|
390
|
+
-1 / self.outl[0]["E_CH"] if self.outl[0]["E_CH"] != 0 else -1
|
|
391
|
+
)
|
|
392
|
+
b[counter] = 0
|
|
393
|
+
equations[counter] = {
|
|
394
|
+
"kind": "dis_equality",
|
|
395
|
+
"objects": [self.name, self.inl[0]["name"], self.outl[0]["name"]],
|
|
396
|
+
"property": "c_CH",
|
|
397
|
+
}
|
|
398
|
+
counter += 1
|
|
399
|
+
|
|
312
400
|
# --- Distribution of dissipative cost difference to other components based on E_D ---
|
|
313
401
|
if all_components is None:
|
|
314
402
|
all_components = []
|
|
315
403
|
# Serving components: all productive components (excluding self, any dissipative, and CycleCloser)
|
|
316
|
-
serving = [
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
404
|
+
serving = [
|
|
405
|
+
comp
|
|
406
|
+
for comp in all_components
|
|
407
|
+
if comp is not self
|
|
408
|
+
and hasattr(comp, "exergy_cost_line")
|
|
409
|
+
and not comp.__class__.__name__.endswith("PowerBus")
|
|
410
|
+
and hasattr(comp, "E_D")
|
|
411
|
+
and not np.isnan(comp.E_D)
|
|
412
|
+
]
|
|
413
|
+
total_E_D = sum(comp.E_D for comp in serving)
|
|
321
414
|
diss_col = self.inl[0]["CostVar_index"].get("dissipative")
|
|
322
415
|
if diss_col is None:
|
|
323
|
-
logging.
|
|
416
|
+
logging.error(f"No 'dissipative' column allocated for {self.name}.")
|
|
324
417
|
else:
|
|
325
418
|
if total_E_D == 0:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
419
|
+
if len(serving) > 0:
|
|
420
|
+
for comp in serving:
|
|
421
|
+
A[comp.exergy_cost_line, diss_col] = 1 / len(serving)
|
|
422
|
+
else:
|
|
423
|
+
logging.warning(f"No serving components found for dissipative component {self.name}")
|
|
329
424
|
else:
|
|
330
425
|
for comp in serving:
|
|
331
426
|
weight = getattr(comp, "E_D", 0) / total_E_D
|
|
332
|
-
|
|
427
|
+
comp.serving_weight = weight
|
|
428
|
+
A[comp.exergy_cost_line, diss_col] = weight
|
|
333
429
|
|
|
334
430
|
# --- Extra overall cost balance row ---
|
|
335
431
|
# This row enforces:
|
|
@@ -338,57 +434,173 @@ class Valve(Component):
|
|
|
338
434
|
A[counter, self.outl[0]["CostVar_index"]["T"]] = -1
|
|
339
435
|
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1
|
|
340
436
|
A[counter, self.outl[0]["CostVar_index"]["M"]] = -1
|
|
437
|
+
if chemical_exergy_enabled:
|
|
438
|
+
A[counter, self.inl[0]["CostVar_index"]["CH"]] = 1
|
|
439
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = -1
|
|
341
440
|
# Subtract the unknown dissipative cost difference:
|
|
342
441
|
A[counter, self.inl[0]["CostVar_index"]["dissipative"]] = -1
|
|
343
442
|
b[counter] = -self.Z_costs
|
|
344
|
-
equations[counter] =
|
|
443
|
+
equations[counter] = {"kind": "dis_balance", "objects": [self.name], "property": "dissipative_cost_balance"}
|
|
345
444
|
counter += 1
|
|
346
445
|
|
|
347
446
|
return A, b, counter, equations
|
|
348
447
|
|
|
448
|
+
def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False):
|
|
449
|
+
r"""
|
|
450
|
+
Perform exergoeconomic cost balance for the valve (throttling component).
|
|
349
451
|
|
|
452
|
+
The valve is a throttling device that reduces pressure without doing work.
|
|
453
|
+
Unlike other components, a valve can be either **dissipative** (purely destructive)
|
|
454
|
+
or **productive** depending on temperature conditions and whether physical
|
|
455
|
+
exergy is split into thermal and mechanical components.
|
|
456
|
+
|
|
457
|
+
**Dissipative Valve (E_P is NaN):**
|
|
458
|
+
|
|
459
|
+
When the valve has no identifiable product (Cases 1, 4, or Cases 2-3 without split),
|
|
460
|
+
the entire exergy loss is considered destruction. The cost of fuel is the decrease
|
|
461
|
+
in physical exergy cost:
|
|
462
|
+
|
|
463
|
+
.. math::
|
|
464
|
+
\dot{C}_{\mathrm{F}} = \dot{C}_{\mathrm{in}}^{\mathrm{PH}} - \dot{C}_{\mathrm{out}}^{\mathrm{PH}}
|
|
465
|
+
|
|
466
|
+
.. math::
|
|
467
|
+
\dot{C}_{\mathrm{P}} = \text{NaN (no product)}
|
|
468
|
+
|
|
469
|
+
**Productive Valve (E_P has a value):**
|
|
470
|
+
|
|
471
|
+
When physical exergy is split (split_physical_exergy=True) and specific temperature
|
|
472
|
+
conditions are met, the valve can have a product:
|
|
473
|
+
|
|
474
|
+
*Case 2 (Inlet above T0, outlet below T0):*
|
|
475
|
+
|
|
476
|
+
The product is the thermal exergy at the outlet (cooling capacity), and the fuel
|
|
477
|
+
is the thermal exergy decrease plus the mechanical exergy loss:
|
|
478
|
+
|
|
479
|
+
.. math::
|
|
480
|
+
\dot{C}_{\mathrm{P}} = \dot{C}_{\mathrm{out}}^{\mathrm{T}}
|
|
481
|
+
|
|
482
|
+
.. math::
|
|
483
|
+
\dot{C}_{\mathrm{F}} = \dot{C}_{\mathrm{in}}^{\mathrm{T}} + (\dot{C}_{\mathrm{in}}^{\mathrm{M}} - \dot{C}_{\mathrm{out}}^{\mathrm{M}})
|
|
484
|
+
|
|
485
|
+
*Case 3 (Both inlet and outlet below T0):*
|
|
486
|
+
|
|
487
|
+
The product is the increase in thermal exergy (cooling capacity increase), and
|
|
488
|
+
the fuel is the mechanical exergy loss:
|
|
489
|
+
|
|
490
|
+
.. math::
|
|
491
|
+
\dot{C}_{\mathrm{P}} = \dot{C}_{\mathrm{out}}^{\mathrm{T}} - \dot{C}_{\mathrm{in}}^{\mathrm{T}}
|
|
492
|
+
|
|
493
|
+
.. math::
|
|
494
|
+
\dot{C}_{\mathrm{F}} = \dot{C}_{\mathrm{in}}^{\mathrm{M}} - \dot{C}_{\mathrm{out}}^{\mathrm{M}}
|
|
495
|
+
|
|
496
|
+
**Calculated exergoeconomic indicators:**
|
|
497
|
+
|
|
498
|
+
Specific cost of fuel:
|
|
499
|
+
|
|
500
|
+
.. math::
|
|
501
|
+
c_{\mathrm{F}} = \frac{\dot{C}_{\mathrm{F}}}{\dot{E}_{\mathrm{F}}}
|
|
502
|
+
|
|
503
|
+
Specific cost of product:
|
|
504
|
+
|
|
505
|
+
.. math::
|
|
506
|
+
c_{\mathrm{P}} = \frac{\dot{C}_{\mathrm{P}}}{\dot{E}_{\mathrm{P}}}
|
|
507
|
+
|
|
508
|
+
Cost rate of exergy destruction:
|
|
509
|
+
|
|
510
|
+
.. math::
|
|
511
|
+
\dot{C}_{\mathrm{D}} = c_{\mathrm{F}} \cdot \dot{E}_{\mathrm{D}}
|
|
512
|
+
|
|
513
|
+
Relative cost difference:
|
|
514
|
+
|
|
515
|
+
.. math::
|
|
516
|
+
r = \frac{c_{\mathrm{P}} - c_{\mathrm{F}}}{c_{\mathrm{F}}}
|
|
517
|
+
|
|
518
|
+
Exergoeconomic factor:
|
|
519
|
+
|
|
520
|
+
.. math::
|
|
521
|
+
f = \frac{\dot{Z}}{\dot{Z} + \dot{C}_{\mathrm{D}}}
|
|
350
522
|
|
|
351
|
-
def exergoeconomic_balance(self, T0):
|
|
352
|
-
"""
|
|
353
|
-
Perform exergoeconomic balance calculations for the valve.
|
|
354
|
-
|
|
355
|
-
This method calculates various exergoeconomic parameters including:
|
|
356
|
-
- Cost rates of product (C_P) and fuel (C_F)
|
|
357
|
-
- Specific cost of product (c_P) and fuel (c_F)
|
|
358
|
-
- Cost rate of exergy destruction (C_D)
|
|
359
|
-
- Relative cost difference (r)
|
|
360
|
-
- Exergoeconomic factor (f)
|
|
361
|
-
|
|
362
523
|
Parameters
|
|
363
524
|
----------
|
|
364
525
|
T0 : float
|
|
365
|
-
Ambient temperature
|
|
366
|
-
|
|
526
|
+
Ambient temperature (K).
|
|
527
|
+
chemical_exergy_enabled : bool, optional
|
|
528
|
+
If True, chemical exergy is considered in the calculations.
|
|
529
|
+
Default is False.
|
|
530
|
+
|
|
531
|
+
Attributes Set
|
|
532
|
+
--------------
|
|
533
|
+
C_P : float or NaN
|
|
534
|
+
Cost rate of product (currency/time). NaN for dissipative valves.
|
|
535
|
+
C_F : float or NaN
|
|
536
|
+
Cost rate of fuel (currency/time).
|
|
537
|
+
c_P : float or NaN
|
|
538
|
+
Specific cost of product (currency/energy). NaN for dissipative valves.
|
|
539
|
+
c_F : float or NaN
|
|
540
|
+
Specific cost of fuel (currency/energy).
|
|
541
|
+
C_D : float or NaN
|
|
542
|
+
Cost rate of exergy destruction (currency/time).
|
|
543
|
+
r : float or NaN
|
|
544
|
+
Relative cost difference (dimensionless). NaN for dissipative valves.
|
|
545
|
+
f : float or NaN
|
|
546
|
+
Exergoeconomic factor (dimensionless).
|
|
547
|
+
|
|
367
548
|
Notes
|
|
368
549
|
-----
|
|
369
|
-
The
|
|
370
|
-
|
|
550
|
+
The valve is unique among components because:
|
|
551
|
+
|
|
552
|
+
1. It typically has no capital cost (Z_costs ≈ 0), making the exergoeconomic
|
|
553
|
+
factor f close to zero.
|
|
554
|
+
|
|
555
|
+
2. It can be dissipative (no product) or productive (identifiable product)
|
|
556
|
+
depending on the analysis approach and temperature conditions.
|
|
557
|
+
|
|
558
|
+
3. For dissipative valves, many exergoeconomic parameters are NaN since there
|
|
559
|
+
is no identifiable product.
|
|
560
|
+
|
|
561
|
+
4. The relative cost difference r is calculated using specific costs (c_P and c_F)
|
|
562
|
+
rather than total cost rates, unlike generator and motor components.
|
|
563
|
+
|
|
564
|
+
5. The method handles NaN values throughout to accommodate both dissipative
|
|
565
|
+
and productive valve configurations.
|
|
566
|
+
|
|
567
|
+
The exergy destruction E_D and product/fuel definitions E_P and E_F must be
|
|
568
|
+
computed prior to calling this method (via exergy_balance).
|
|
569
|
+
|
|
570
|
+
For refrigeration cycles (Case 2) or cryogenic applications (Case 3), the
|
|
571
|
+
productive valve approach may be more appropriate. For typical throttling
|
|
572
|
+
applications, the dissipative approach is standard.
|
|
371
573
|
"""
|
|
372
|
-
|
|
373
|
-
|
|
574
|
+
# Check if valve is dissipative
|
|
575
|
+
if np.isnan(self.E_P):
|
|
576
|
+
# Dissipative valve (Cases 1, 4, or Cases 2-3 without split)
|
|
577
|
+
self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"]
|
|
374
578
|
self.C_P = np.nan
|
|
375
|
-
# dissipative
|
|
376
|
-
elif self.outl[0]["T"] <= T0 and self.inl[0]["T"] > T0:
|
|
377
|
-
self.C_P = self.outl[0]["C_T"]
|
|
378
|
-
self.C_F = self.inl[0]["C_T"] + (
|
|
379
|
-
self.inl[0]["C_M"] - self.outl[0]["C_M"])
|
|
380
|
-
elif self.inl[0]["T"] <= T0 and self.outl[0]["T"] <= T0:
|
|
381
|
-
self.C_P = self.outl[0]["C_T"] - self.inl[0]["C_T"]
|
|
382
|
-
self.C_F = self.inl[0]["C_M"] - self.outl[0]["C_M"]
|
|
383
579
|
else:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
580
|
+
# Productive valve (Cases 2 or 3 with split_physical_exergy=True)
|
|
581
|
+
if self.outl[0]["T"] <= T0 and self.inl[0]["T"] > T0:
|
|
582
|
+
# Case 2 with split
|
|
583
|
+
self.C_P = self.outl[0]["C_T"]
|
|
584
|
+
self.C_F = self.inl[0]["C_T"] + (self.inl[0]["C_M"] - self.outl[0]["C_M"])
|
|
585
|
+
elif self.inl[0]["T"] <= T0 and self.outl[0]["T"] <= T0:
|
|
586
|
+
# Case 3 with split
|
|
587
|
+
self.C_P = self.outl[0]["C_T"] - self.inl[0]["C_T"]
|
|
588
|
+
self.C_F = self.inl[0]["C_M"] - self.outl[0]["C_M"]
|
|
589
|
+
else:
|
|
590
|
+
logging.error(f"Productive valve {self.name} in unexpected temperature regime.")
|
|
591
|
+
self.C_P = np.nan
|
|
592
|
+
self.C_F = np.nan
|
|
593
|
+
|
|
594
|
+
self.c_F = self.C_F / self.E_F if self.E_F != 0 else np.nan
|
|
595
|
+
self.c_P = self.C_P / self.E_P if not np.isnan(self.E_P) and self.E_P != 0 else np.nan
|
|
596
|
+
self.C_D = self.c_F * self.E_D if not np.isnan(self.c_F) else np.nan
|
|
597
|
+
self.r = (
|
|
598
|
+
(self.c_P - self.c_F) / self.c_F
|
|
599
|
+
if not np.isnan(self.c_P) and not np.isnan(self.c_F) and self.c_F != 0
|
|
600
|
+
else np.nan
|
|
601
|
+
)
|
|
602
|
+
self.f = (
|
|
603
|
+
self.Z_costs / (self.Z_costs + self.C_D)
|
|
604
|
+
if not np.isnan(self.C_D) and (self.Z_costs + self.C_D) != 0
|
|
605
|
+
else np.nan
|
|
606
|
+
)
|