exerpy 0.0.2__py3-none-any.whl → 0.0.4__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 +849 -304
- exerpy/components/__init__.py +3 -0
- exerpy/components/combustion/base.py +53 -35
- exerpy/components/component.py +8 -8
- exerpy/components/heat_exchanger/base.py +188 -121
- exerpy/components/heat_exchanger/condenser.py +98 -62
- exerpy/components/heat_exchanger/simple.py +237 -137
- exerpy/components/heat_exchanger/steam_generator.py +46 -41
- exerpy/components/helpers/cycle_closer.py +61 -34
- exerpy/components/helpers/power_bus.py +117 -0
- exerpy/components/nodes/deaerator.py +176 -58
- exerpy/components/nodes/drum.py +50 -39
- exerpy/components/nodes/flash_tank.py +218 -43
- exerpy/components/nodes/mixer.py +249 -69
- exerpy/components/nodes/splitter.py +173 -0
- exerpy/components/nodes/storage.py +130 -0
- exerpy/components/piping/valve.py +311 -115
- exerpy/components/power_machines/generator.py +105 -38
- exerpy/components/power_machines/motor.py +111 -39
- exerpy/components/turbomachinery/compressor.py +214 -68
- exerpy/components/turbomachinery/pump.py +215 -68
- exerpy/components/turbomachinery/turbine.py +182 -74
- exerpy/cost_estimation/__init__.py +65 -0
- exerpy/cost_estimation/turton.py +1260 -0
- exerpy/data/cost_correlations/cepci_index.json +135 -0
- exerpy/data/cost_correlations/component_mapping.json +450 -0
- exerpy/data/cost_correlations/material_factors.json +428 -0
- exerpy/data/cost_correlations/pressure_factors.json +206 -0
- exerpy/data/cost_correlations/turton2008.json +726 -0
- exerpy/data/cost_correlations/turton2008_design_analysis_synthesis_components_tables.pdf +0 -0
- exerpy/data/cost_correlations/turton2008_design_analysis_synthesis_components_theory.pdf +0 -0
- exerpy/functions.py +389 -264
- 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 +328 -226
- 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 +33 -1
- exerpy/parser/from_tespy/tespy_parser.py +151 -0
- {exerpy-0.0.2.dist-info → exerpy-0.0.4.dist-info}/METADATA +43 -2
- exerpy-0.0.4.dist-info/RECORD +57 -0
- exerpy-0.0.2.dist-info/RECORD +0 -44
- {exerpy-0.0.2.dist-info → exerpy-0.0.4.dist-info}/WHEEL +0 -0
- {exerpy-0.0.2.dist-info → exerpy-0.0.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,8 +2,7 @@ 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
|
|
@@ -57,7 +56,7 @@ class Valve(Component):
|
|
|
57
56
|
|
|
58
57
|
\dot{E}_\mathrm{P} =
|
|
59
58
|
\begin{cases}
|
|
60
|
-
\
|
|
59
|
+
\text{not defined (nan)}
|
|
61
60
|
& T_\mathrm{in}, T_\mathrm{out} > T_0\\
|
|
62
61
|
\dot{m} \cdot e_\mathrm{out}^\mathrm{T}
|
|
63
62
|
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
|
|
@@ -69,19 +68,19 @@ class Valve(Component):
|
|
|
69
68
|
\begin{cases}
|
|
70
69
|
\dot{m} \cdot (e_\mathrm{in}^\mathrm{PH} - e_\mathrm{out}^\mathrm{PH})
|
|
71
70
|
& T_\mathrm{in}, T_\mathrm{out} > T_0\\
|
|
72
|
-
\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}
|
|
73
72
|
- e_\mathrm{out}^\mathrm{M})
|
|
74
73
|
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
|
|
75
74
|
\dot{m} \cdot (e_\mathrm{in}^\mathrm{M} - e_\mathrm{out}^\mathrm{M})
|
|
76
75
|
& T_0 \geq T_\mathrm{in}, T_\mathrm{out}
|
|
77
76
|
\end{cases}
|
|
78
77
|
|
|
79
|
-
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
|
|
80
79
|
destruction is calculated as:
|
|
81
80
|
|
|
82
81
|
.. math::
|
|
83
82
|
\dot{E}_\mathrm{D} = \begin{cases}
|
|
84
|
-
\dot{E}_\mathrm{F} & \
|
|
83
|
+
\dot{E}_\mathrm{F} & \text{if } \dot{E}_\mathrm{P} = \text{nan}\\
|
|
85
84
|
\dot{E}_\mathrm{F} - \dot{E}_\mathrm{P} & \mathrm{otherwise}
|
|
86
85
|
\end{cases}
|
|
87
86
|
|
|
@@ -115,36 +114,64 @@ class Valve(Component):
|
|
|
115
114
|
------
|
|
116
115
|
ValueError
|
|
117
116
|
If the required inlet and outlet streams are not properly defined.
|
|
118
|
-
"""
|
|
117
|
+
"""
|
|
119
118
|
# Ensure that the component has both inlet and outlet streams
|
|
120
119
|
if len(self.inl) < 1 or len(self.outl) < 1:
|
|
121
120
|
raise ValueError("Valve requires at least one inlet and one outlet.")
|
|
122
121
|
|
|
123
|
-
T_in = self.inl[0][
|
|
124
|
-
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"]
|
|
125
127
|
|
|
126
|
-
#
|
|
127
|
-
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.")
|
|
128
131
|
self.E_P = np.nan
|
|
129
|
-
self.E_F =
|
|
130
|
-
|
|
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:
|
|
131
158
|
if split_physical_exergy:
|
|
132
|
-
self.E_P = self.inl[0][
|
|
133
|
-
self.E_F = self.inl[0][
|
|
134
|
-
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"])
|
|
135
161
|
else:
|
|
136
162
|
logging.warning(
|
|
137
163
|
"Exergy balance of a valve, where outlet temperature is smaller than "
|
|
138
|
-
"ambient temperature, is not implemented for non-split physical exergy."
|
|
164
|
+
"ambient temperature, is not implemented for non-split physical exergy. "
|
|
139
165
|
"Valve is treated as dissipative."
|
|
140
166
|
)
|
|
141
167
|
self.E_P = np.nan
|
|
142
|
-
self.E_F = self.inl[0][
|
|
168
|
+
self.E_F = self.inl[0]["m"] * (self.inl[0]["e_PH"] - self.outl[0]["e_PH"])
|
|
143
169
|
|
|
170
|
+
# Case 3: Both temperatures below ambient
|
|
144
171
|
elif T_in <= T0 and T_out <= T0:
|
|
145
172
|
if split_physical_exergy:
|
|
146
|
-
self.E_P = self.inl[0][
|
|
147
|
-
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"])
|
|
148
175
|
else:
|
|
149
176
|
logging.warning(
|
|
150
177
|
"Exergy balance of a valve, where both temperatures are smaller than "
|
|
@@ -152,14 +179,28 @@ class Valve(Component):
|
|
|
152
179
|
"Valve is treated as dissipative."
|
|
153
180
|
)
|
|
154
181
|
self.E_P = np.nan
|
|
155
|
-
self.E_F = self.inl[0][
|
|
156
|
-
|
|
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:
|
|
157
186
|
logging.warning(
|
|
158
|
-
"
|
|
159
|
-
"
|
|
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."
|
|
160
198
|
)
|
|
161
199
|
self.E_P = np.nan
|
|
162
200
|
self.E_F = np.nan
|
|
201
|
+
self.E_D = np.nan
|
|
202
|
+
self.epsilon = np.nan
|
|
203
|
+
return
|
|
163
204
|
|
|
164
205
|
# Calculate exergy destruction
|
|
165
206
|
if np.isnan(self.E_P):
|
|
@@ -172,35 +213,34 @@ class Valve(Component):
|
|
|
172
213
|
|
|
173
214
|
# Log the results
|
|
174
215
|
logging.info(
|
|
175
|
-
f"Valve
|
|
216
|
+
f"Exergy balance of Valve {self.name} calculated: "
|
|
176
217
|
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
177
218
|
f"Efficiency={self.epsilon:.2%}"
|
|
178
219
|
)
|
|
179
220
|
|
|
180
|
-
|
|
181
221
|
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
182
222
|
"""
|
|
183
223
|
Auxiliary equations for the valve.
|
|
184
|
-
|
|
224
|
+
|
|
185
225
|
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
186
226
|
the following auxiliary cost relations:
|
|
187
|
-
|
|
188
|
-
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):
|
|
189
229
|
- Valve is treated as dissipative (warning issued)
|
|
190
|
-
|
|
230
|
+
|
|
191
231
|
For T_out <= T0:
|
|
192
232
|
(1) 1/E_M_in * C_M_in - 1/E_M_out * C_M_out = 0
|
|
193
233
|
|
|
194
234
|
- F-principle: specific mechanical exergy costs equalized between inlet/outlet
|
|
195
235
|
|
|
196
236
|
- If E_M is zero for either stream, appropriate fallback coefficients are used
|
|
197
|
-
|
|
237
|
+
|
|
198
238
|
When chemical_exergy_enabled is True:
|
|
199
239
|
(2) 1/E_CH_in * C_CH_in - 1/E_CH_out * C_CH_out = 0
|
|
200
240
|
|
|
201
241
|
- F-principle: specific chemical exergy costs equalized between inlet/outlet
|
|
202
242
|
- If E_CH is zero for either stream, appropriate fallback coefficients are used
|
|
203
|
-
|
|
243
|
+
|
|
204
244
|
Parameters
|
|
205
245
|
----------
|
|
206
246
|
A : numpy.ndarray
|
|
@@ -215,7 +255,7 @@ class Valve(Component):
|
|
|
215
255
|
Dictionary for storing equation labels.
|
|
216
256
|
chemical_exergy_enabled : bool
|
|
217
257
|
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
218
|
-
|
|
258
|
+
|
|
219
259
|
Returns
|
|
220
260
|
-------
|
|
221
261
|
A : numpy.ndarray
|
|
@@ -227,46 +267,56 @@ class Valve(Component):
|
|
|
227
267
|
equations : dict
|
|
228
268
|
Updated dictionary with equation labels.
|
|
229
269
|
"""
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
equations[counter] = f"aux_{self.name}_mech_{self.outl[0]['name']}"
|
|
246
|
-
b[counter] = 0
|
|
247
|
-
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
|
|
248
285
|
else:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
|
|
252
296
|
if chemical_exergy_enabled:
|
|
253
297
|
# --- Chemical cost equation (conditionally added) ---
|
|
254
|
-
A[counter, self.inl[0]["CostVar_index"]["CH"]] =
|
|
255
|
-
A[counter, self.outl[0]["CostVar_index"]["CH"]] = (
|
|
256
|
-
|
|
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
|
+
}
|
|
257
307
|
# Set right-hand side for both rows.
|
|
258
308
|
b[counter] = 0
|
|
259
309
|
counter += 1
|
|
260
|
-
|
|
310
|
+
|
|
261
311
|
return A, b, counter, equations
|
|
262
|
-
|
|
312
|
+
|
|
263
313
|
def dis_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled=False, all_components=None):
|
|
264
|
-
"""
|
|
314
|
+
r"""
|
|
265
315
|
Constructs the cost equations for a dissipative Valve in ExerPy,
|
|
266
|
-
distributing the valve
|
|
316
|
+
distributing the valve's extra cost difference (C_diff) to all other productive
|
|
267
317
|
components (non-dissipative and non-CycleCloser) in proportion to their exergy destruction (E_D)
|
|
268
318
|
and adding an extra overall cost balance row that enforces:
|
|
269
|
-
|
|
319
|
+
|
|
270
320
|
.. math::
|
|
271
321
|
(\dot C_{\mathrm{in},T} - \dot C_{\mathrm{out},T})
|
|
272
322
|
+ (\dot C_{\mathrm{in},M} - \dot C_{\mathrm{out},M})
|
|
@@ -274,8 +324,8 @@ class Valve(Component):
|
|
|
274
324
|
= -\,\dot Z_{\mathrm{costs}}
|
|
275
325
|
|
|
276
326
|
In this formulation, the unknown cost variable in the "dissipative" column (i.e. C_diff)
|
|
277
|
-
is solved for, ensuring the valve
|
|
278
|
-
|
|
327
|
+
is solved for, ensuring the valve's cost balance.
|
|
328
|
+
|
|
279
329
|
Parameters
|
|
280
330
|
----------
|
|
281
331
|
A : numpy.ndarray
|
|
@@ -292,12 +342,12 @@ class Valve(Component):
|
|
|
292
342
|
Flag indicating whether chemical exergy is considered. (Ignored here.)
|
|
293
343
|
all_components : list, optional
|
|
294
344
|
Global list of all component objects; if not provided, defaults to [].
|
|
295
|
-
|
|
345
|
+
|
|
296
346
|
Returns
|
|
297
347
|
-------
|
|
298
348
|
tuple
|
|
299
349
|
Updated (A, b, counter, equations).
|
|
300
|
-
|
|
350
|
+
|
|
301
351
|
Notes
|
|
302
352
|
-----
|
|
303
353
|
- It is assumed that each inlet/outlet stream's CostVar_index dictionary has keys: "T" (thermal), "M" (mechanical), and "dissipative" (the extra unknown).
|
|
@@ -311,7 +361,11 @@ class Valve(Component):
|
|
|
311
361
|
A[counter, self.inl[0]["CostVar_index"]["T"]] = 1
|
|
312
362
|
A[counter, self.outl[0]["CostVar_index"]["T"]] = -1
|
|
313
363
|
b[counter] = 0
|
|
314
|
-
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
|
+
}
|
|
315
369
|
counter += 1
|
|
316
370
|
|
|
317
371
|
# --- Mechanical difference row ---
|
|
@@ -322,30 +376,56 @@ class Valve(Component):
|
|
|
322
376
|
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1
|
|
323
377
|
A[counter, self.outl[0]["CostVar_index"]["M"]] = -1
|
|
324
378
|
b[counter] = 0
|
|
325
|
-
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
|
+
}
|
|
326
384
|
counter += 1
|
|
327
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
|
+
|
|
328
400
|
# --- Distribution of dissipative cost difference to other components based on E_D ---
|
|
329
401
|
if all_components is None:
|
|
330
402
|
all_components = []
|
|
331
403
|
# Serving components: all productive components (excluding self, any dissipative, and CycleCloser)
|
|
332
|
-
serving = [
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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)
|
|
337
414
|
diss_col = self.inl[0]["CostVar_index"].get("dissipative")
|
|
338
415
|
if diss_col is None:
|
|
339
|
-
logging.
|
|
416
|
+
logging.error(f"No 'dissipative' column allocated for {self.name}.")
|
|
340
417
|
else:
|
|
341
418
|
if total_E_D == 0:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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}")
|
|
345
424
|
else:
|
|
346
425
|
for comp in serving:
|
|
347
426
|
weight = getattr(comp, "E_D", 0) / total_E_D
|
|
348
|
-
|
|
427
|
+
comp.serving_weight = weight
|
|
428
|
+
A[comp.exergy_cost_line, diss_col] = weight
|
|
349
429
|
|
|
350
430
|
# --- Extra overall cost balance row ---
|
|
351
431
|
# This row enforces:
|
|
@@ -354,57 +434,173 @@ class Valve(Component):
|
|
|
354
434
|
A[counter, self.outl[0]["CostVar_index"]["T"]] = -1
|
|
355
435
|
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1
|
|
356
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
|
|
357
440
|
# Subtract the unknown dissipative cost difference:
|
|
358
441
|
A[counter, self.inl[0]["CostVar_index"]["dissipative"]] = -1
|
|
359
442
|
b[counter] = -self.Z_costs
|
|
360
|
-
equations[counter] =
|
|
443
|
+
equations[counter] = {"kind": "dis_balance", "objects": [self.name], "property": "dissipative_cost_balance"}
|
|
361
444
|
counter += 1
|
|
362
445
|
|
|
363
446
|
return A, b, counter, equations
|
|
364
447
|
|
|
448
|
+
def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False):
|
|
449
|
+
r"""
|
|
450
|
+
Perform exergoeconomic cost balance for the valve (throttling component).
|
|
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.
|
|
365
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}}}
|
|
366
522
|
|
|
367
|
-
def exergoeconomic_balance(self, T0):
|
|
368
|
-
"""
|
|
369
|
-
Perform exergoeconomic balance calculations for the valve.
|
|
370
|
-
|
|
371
|
-
This method calculates various exergoeconomic parameters including:
|
|
372
|
-
- Cost rates of product (C_P) and fuel (C_F)
|
|
373
|
-
- Specific cost of product (c_P) and fuel (c_F)
|
|
374
|
-
- Cost rate of exergy destruction (C_D)
|
|
375
|
-
- Relative cost difference (r)
|
|
376
|
-
- Exergoeconomic factor (f)
|
|
377
|
-
|
|
378
523
|
Parameters
|
|
379
524
|
----------
|
|
380
525
|
T0 : float
|
|
381
|
-
Ambient temperature
|
|
382
|
-
|
|
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
|
+
|
|
383
548
|
Notes
|
|
384
549
|
-----
|
|
385
|
-
The
|
|
386
|
-
|
|
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.
|
|
387
573
|
"""
|
|
388
|
-
|
|
389
|
-
|
|
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"]
|
|
390
578
|
self.C_P = np.nan
|
|
391
|
-
# dissipative
|
|
392
|
-
elif self.outl[0]["T"] <= T0 and self.inl[0]["T"] > T0:
|
|
393
|
-
self.C_P = self.outl[0]["C_T"]
|
|
394
|
-
self.C_F = self.inl[0]["C_T"] + (
|
|
395
|
-
self.inl[0]["C_M"] - self.outl[0]["C_M"])
|
|
396
|
-
elif self.inl[0]["T"] <= T0 and self.outl[0]["T"] <= T0:
|
|
397
|
-
self.C_P = self.outl[0]["C_T"] - self.inl[0]["C_T"]
|
|
398
|
-
self.C_F = self.inl[0]["C_M"] - self.outl[0]["C_M"]
|
|
399
579
|
else:
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
+
)
|