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
exerpy/components/nodes/mixer.py
CHANGED
|
@@ -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
|
|
@@ -12,8 +11,8 @@ class Mixer(Component):
|
|
|
12
11
|
Class for exergy analysis of mixers.
|
|
13
12
|
|
|
14
13
|
This class performs exergy analysis calculations for mixers with multiple
|
|
15
|
-
inlet streams and generally one outlet stream (multiple outlets are possible).
|
|
16
|
-
The exergy product and fuel definitions vary based on the temperature
|
|
14
|
+
inlet streams and generally one outlet stream (multiple outlets are possible).
|
|
15
|
+
The exergy product and fuel definitions vary based on the temperature
|
|
17
16
|
relationships between inlet streams, outlet streams, and ambient conditions.
|
|
18
17
|
|
|
19
18
|
Parameters
|
|
@@ -120,22 +119,22 @@ class Mixer(Component):
|
|
|
120
119
|
# Ensure that the component has at least two inlets and one outlet.
|
|
121
120
|
if len(self.inl) < 2 or len(self.outl) < 1:
|
|
122
121
|
raise ValueError("Mixer requires at least two inlets and one outlet.")
|
|
123
|
-
|
|
122
|
+
|
|
124
123
|
# Compute effective outlet state by aggregating all outlet streams.
|
|
125
124
|
# Assume that all outlets share the same thermodynamic state.
|
|
126
125
|
outlet_list = list(self.outl.values())
|
|
127
126
|
first_outlet = outlet_list[0]
|
|
128
|
-
T_out = first_outlet[
|
|
129
|
-
e_out_PH = first_outlet[
|
|
127
|
+
T_out = first_outlet["T"]
|
|
128
|
+
e_out_PH = first_outlet["e_PH"]
|
|
130
129
|
# Verify that all outlets have the same thermodynamic state.
|
|
131
130
|
for outlet in outlet_list:
|
|
132
|
-
if outlet[
|
|
131
|
+
if outlet["T"] != T_out or outlet["e_PH"] != e_out_PH:
|
|
133
132
|
msg = "All outlets in Mixer must have the same thermodynamic state."
|
|
134
133
|
logging.error(msg)
|
|
135
134
|
raise ValueError(msg)
|
|
136
135
|
# Sum the mass of all outlet streams (if needed for further analysis)
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
sum(outlet.get("m", 0) for outlet in outlet_list)
|
|
137
|
+
|
|
139
138
|
# Initialize exergy product and fuel.
|
|
140
139
|
self.E_P = 0
|
|
141
140
|
self.E_F = 0
|
|
@@ -144,66 +143,64 @@ class Mixer(Component):
|
|
|
144
143
|
if T_out > T0:
|
|
145
144
|
for _, inlet in self.inl.items():
|
|
146
145
|
# Case when inlet temperature is lower than outlet temperature.
|
|
147
|
-
if inlet[
|
|
148
|
-
if inlet[
|
|
146
|
+
if inlet["T"] < T_out:
|
|
147
|
+
if inlet["T"] >= T0:
|
|
149
148
|
# Contribution to exergy product from inlets above ambient.
|
|
150
|
-
self.E_P += inlet[
|
|
149
|
+
self.E_P += inlet["m"] * (e_out_PH - inlet["e_PH"])
|
|
151
150
|
else: # inlet['T'] < T0
|
|
152
|
-
self.E_P += inlet[
|
|
153
|
-
self.E_F += inlet[
|
|
151
|
+
self.E_P += inlet["m"] * e_out_PH
|
|
152
|
+
self.E_F += inlet["m"] * inlet["e_PH"]
|
|
154
153
|
else: # inlet['T'] > T_out
|
|
155
|
-
self.E_F += inlet[
|
|
156
|
-
|
|
154
|
+
self.E_F += inlet["m"] * (inlet["e_PH"] - e_out_PH)
|
|
155
|
+
|
|
157
156
|
# Case 2: Outlet temperature equals ambient.
|
|
158
157
|
elif T_out == T0:
|
|
159
158
|
self.E_P = np.nan
|
|
160
159
|
for _, inlet in self.inl.items():
|
|
161
|
-
self.E_F += inlet[
|
|
162
|
-
|
|
160
|
+
self.E_F += inlet["m"] * inlet["e_PH"]
|
|
161
|
+
|
|
163
162
|
# Case 3: Outlet temperature is less than ambient.
|
|
164
163
|
else: # T_out < T0
|
|
165
164
|
for _, inlet in self.inl.items():
|
|
166
|
-
if inlet[
|
|
167
|
-
if inlet[
|
|
168
|
-
self.E_P += inlet[
|
|
169
|
-
self.E_F += inlet[
|
|
165
|
+
if inlet["T"] > T_out:
|
|
166
|
+
if inlet["T"] >= T0:
|
|
167
|
+
self.E_P += inlet["m"] * e_out_PH
|
|
168
|
+
self.E_F += inlet["m"] * inlet["e_PH"]
|
|
170
169
|
else: # inlet['T'] < T0
|
|
171
|
-
self.E_P += inlet[
|
|
170
|
+
self.E_P += inlet["m"] * (e_out_PH - inlet["e_PH"])
|
|
172
171
|
else: # inlet['T'] <= T_out
|
|
173
|
-
self.E_F += inlet[
|
|
174
|
-
|
|
172
|
+
self.E_F += inlet["m"] * (inlet["e_PH"] - e_out_PH)
|
|
173
|
+
|
|
175
174
|
# Calculate exergy destruction and efficiency.
|
|
176
175
|
self.E_D = self.E_F - self.E_P
|
|
177
176
|
self.epsilon = self.calc_epsilon()
|
|
178
|
-
|
|
177
|
+
|
|
179
178
|
# Log the results.
|
|
180
179
|
logging.info(
|
|
181
|
-
f"Mixer
|
|
180
|
+
f"Exergy balance of Mixer {self.name} calculated: "
|
|
182
181
|
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
183
182
|
f"Efficiency={self.epsilon:.2%}"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
)
|
|
187
184
|
|
|
188
185
|
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
189
186
|
"""
|
|
190
187
|
Auxiliary equations for the mixer.
|
|
191
|
-
|
|
188
|
+
|
|
192
189
|
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
193
190
|
the following auxiliary cost relations:
|
|
194
|
-
|
|
191
|
+
|
|
195
192
|
(1) Mixing equation for chemical exergy costs (if enabled):
|
|
196
193
|
|
|
197
194
|
- The outlet's specific chemical exergy cost is calculated as a mass-weighted average of the inlet streams' specific chemical exergy costs
|
|
198
|
-
|
|
195
|
+
|
|
199
196
|
- This enforces proper chemical exergy cost distribution through the deaerator
|
|
200
|
-
|
|
197
|
+
|
|
201
198
|
(2) Mixing equation for mechanical exergy costs:
|
|
202
|
-
|
|
199
|
+
|
|
203
200
|
- The outlet's specific mechanical exergy cost is calculated as a mass-weighted average of the inlet streams' specific mechanical exergy costs
|
|
204
|
-
|
|
201
|
+
|
|
205
202
|
- This ensures mechanical exergy costs are properly conserved in the mixing process
|
|
206
|
-
|
|
203
|
+
|
|
207
204
|
Parameters
|
|
208
205
|
----------
|
|
209
206
|
A : numpy.ndarray
|
|
@@ -244,13 +241,16 @@ class Mixer(Component):
|
|
|
244
241
|
# Outlet chemical exergy is zero: assign fallback for all inlets.
|
|
245
242
|
for inlet in self.inl.values():
|
|
246
243
|
A[counter, inlet["CostVar_index"]["CH"]] = 1
|
|
247
|
-
equations[counter] =
|
|
244
|
+
equations[counter] = {
|
|
245
|
+
"kind": "aux_mixing",
|
|
246
|
+
"objects": [self.name, self.inl[0]["name"], self.inl[1]["name"], self.outl[0]["name"]],
|
|
247
|
+
"property": "c_CH",
|
|
248
|
+
}
|
|
248
249
|
chem_row = 1 # One row added for chemical equation.
|
|
249
250
|
else:
|
|
250
251
|
chem_row = 0 # No row added.
|
|
251
252
|
|
|
252
253
|
# --- Mechanical cost auxiliary equation ---
|
|
253
|
-
mech_row = 0 # This row will always be added.
|
|
254
254
|
if self.outl[0]["e_M"] != 0:
|
|
255
255
|
A[counter + chem_row, self.outl[0]["CostVar_index"]["M"]] = -1 / self.outl[0]["E_M"]
|
|
256
256
|
# Iterate over inlet streams for mechanical mixing.
|
|
@@ -262,7 +262,15 @@ class Mixer(Component):
|
|
|
262
262
|
else:
|
|
263
263
|
for inlet in self.inl.values():
|
|
264
264
|
A[counter + chem_row, inlet["CostVar_index"]["M"]] = 1
|
|
265
|
-
|
|
265
|
+
|
|
266
|
+
# Dynamically build the list of inlet names
|
|
267
|
+
inlet_names = [inlet["name"] for inlet in self.inl.values()]
|
|
268
|
+
|
|
269
|
+
equations[counter + chem_row] = {
|
|
270
|
+
"kind": "aux_mixing",
|
|
271
|
+
"objects": [self.name] + inlet_names + [self.outl[0]["name"]],
|
|
272
|
+
"property": "c_M",
|
|
273
|
+
}
|
|
266
274
|
|
|
267
275
|
# Set the right-hand side entries to zero for the added rows.
|
|
268
276
|
if chemical_exergy_enabled:
|
|
@@ -274,40 +282,208 @@ class Mixer(Component):
|
|
|
274
282
|
counter += 1 # Only one row was added.
|
|
275
283
|
|
|
276
284
|
return A, b, counter, equations
|
|
277
|
-
|
|
278
|
-
def exergoeconomic_balance(self, T0):
|
|
279
|
-
"""
|
|
280
|
-
Perform exergoeconomic balance
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
|
|
286
|
+
def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False):
|
|
287
|
+
r"""
|
|
288
|
+
Perform exergoeconomic cost balance for the mixer.
|
|
289
|
+
|
|
290
|
+
The mixer is a component where multiple streams combine. The general
|
|
291
|
+
exergoeconomic balance equation is:
|
|
292
|
+
|
|
293
|
+
.. math::
|
|
294
|
+
\sum_{\mathrm{in}} \left(\dot{C}^{\mathrm{T}}_{\mathrm{in}}
|
|
295
|
+
+ \dot{C}^{\mathrm{M}}_{\mathrm{in}}
|
|
296
|
+
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in}}\right)
|
|
297
|
+
- \sum_{\mathrm{out}} \left(\dot{C}^{\mathrm{T}}_{\mathrm{out}}
|
|
298
|
+
+ \dot{C}^{\mathrm{M}}_{\mathrm{out}}
|
|
299
|
+
+ \dot{C}^{\mathrm{CH}}_{\mathrm{out}}\right)
|
|
300
|
+
+ \dot{Z}
|
|
301
|
+
= 0
|
|
302
|
+
|
|
303
|
+
The product is defined as the outlet stream. The fuel consists of all inlet
|
|
304
|
+
streams, with treatment depending on temperature levels and whether chemical
|
|
305
|
+
exergy is enabled. The cost balance is closed using:
|
|
306
|
+
|
|
307
|
+
.. math::
|
|
308
|
+
\dot{C}_{\mathrm{P}} = \dot{C}_{\mathrm{F}} + \dot{Z}
|
|
309
|
+
|
|
310
|
+
**Case 1: Outlet above ambient temperature**
|
|
311
|
+
|
|
312
|
+
When :math:`T_{\mathrm{out}} > T_0`:
|
|
313
|
+
|
|
314
|
+
For cold inlets (:math:`T_{\mathrm{in}} < T_{\mathrm{out}}`):
|
|
315
|
+
|
|
316
|
+
Without chemical exergy:
|
|
317
|
+
|
|
318
|
+
.. math::
|
|
319
|
+
\dot{C}_{\mathrm{F,cold}} = \dot{C}^{\mathrm{M}}_{\mathrm{in}}
|
|
320
|
+
|
|
321
|
+
With chemical exergy enabled:
|
|
322
|
+
|
|
323
|
+
.. math::
|
|
324
|
+
\dot{C}_{\mathrm{F,cold}} = \dot{C}^{\mathrm{M}}_{\mathrm{in}}
|
|
325
|
+
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in}}
|
|
326
|
+
|
|
327
|
+
For hot inlets (:math:`T_{\mathrm{in}} \geq T_{\mathrm{out}}`):
|
|
328
|
+
|
|
329
|
+
Without chemical exergy:
|
|
330
|
+
|
|
331
|
+
.. math::
|
|
332
|
+
\dot{C}_{\mathrm{F,hot}} = -\dot{m}_{\mathrm{in}} \cdot c^{\mathrm{T}}_{\mathrm{in}}
|
|
333
|
+
\cdot e^{\mathrm{T}}_{\mathrm{in}}
|
|
334
|
+
+ \left(\dot{C}^{\mathrm{T}}_{\mathrm{in}}
|
|
335
|
+
+ \dot{C}^{\mathrm{M}}_{\mathrm{in}}\right)
|
|
336
|
+
|
|
337
|
+
With chemical exergy enabled:
|
|
338
|
+
|
|
339
|
+
.. math::
|
|
340
|
+
\dot{C}_{\mathrm{F,hot}} = -\dot{m}_{\mathrm{in}} \cdot c^{\mathrm{T}}_{\mathrm{in}}
|
|
341
|
+
\cdot e^{\mathrm{T}}_{\mathrm{in}}
|
|
342
|
+
+ \left(\dot{C}^{\mathrm{T}}_{\mathrm{in}}
|
|
343
|
+
+ \dot{C}^{\mathrm{M}}_{\mathrm{in}}
|
|
344
|
+
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in}}\right)
|
|
345
|
+
|
|
346
|
+
Total fuel cost (without chemical exergy):
|
|
347
|
+
|
|
348
|
+
.. math::
|
|
349
|
+
\dot{C}_{\mathrm{F}} = \sum \dot{C}_{\mathrm{F,cold}}
|
|
350
|
+
+ \sum \dot{C}_{\mathrm{F,hot}}
|
|
351
|
+
- \dot{C}^{\mathrm{M}}_{\mathrm{out}}
|
|
352
|
+
|
|
353
|
+
Total fuel cost (with chemical exergy):
|
|
354
|
+
|
|
355
|
+
.. math::
|
|
356
|
+
\dot{C}_{\mathrm{F}} = \sum \dot{C}_{\mathrm{F,cold}}
|
|
357
|
+
+ \sum \dot{C}_{\mathrm{F,hot}}
|
|
358
|
+
- \dot{C}^{\mathrm{M}}_{\mathrm{out}}
|
|
359
|
+
- \dot{C}^{\mathrm{CH}}_{\mathrm{out}}
|
|
360
|
+
|
|
361
|
+
**Case 2: Outlet at ambient temperature (dissipative)**
|
|
362
|
+
|
|
363
|
+
When :math:`|T_{\mathrm{out}} - T_0| < 10^{-6}`:
|
|
364
|
+
|
|
365
|
+
.. math::
|
|
366
|
+
\dot{C}_{\mathrm{F}} = \sum_{\mathrm{in}} \dot{C}^{\mathrm{TOT}}_{\mathrm{in}}
|
|
367
|
+
|
|
368
|
+
**Case 3: Outlet below ambient temperature**
|
|
369
|
+
|
|
370
|
+
When :math:`T_{\mathrm{out}} < T_0`:
|
|
371
|
+
|
|
372
|
+
For hot inlets (:math:`T_{\mathrm{in}} > T_{\mathrm{out}}`):
|
|
373
|
+
|
|
374
|
+
Without chemical exergy:
|
|
375
|
+
|
|
376
|
+
.. math::
|
|
377
|
+
\dot{C}_{\mathrm{F,hot}} = \dot{C}^{\mathrm{M}}_{\mathrm{in}}
|
|
378
|
+
|
|
379
|
+
With chemical exergy enabled:
|
|
380
|
+
|
|
381
|
+
.. math::
|
|
382
|
+
\dot{C}_{\mathrm{F,hot}} = \dot{C}^{\mathrm{M}}_{\mathrm{in}}
|
|
383
|
+
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in}}
|
|
384
|
+
|
|
385
|
+
For cold inlets (:math:`T_{\mathrm{in}} \leq T_{\mathrm{out}}`):
|
|
386
|
+
|
|
387
|
+
Without chemical exergy:
|
|
388
|
+
|
|
389
|
+
.. math::
|
|
390
|
+
\dot{C}_{\mathrm{F,cold}} = -\dot{m}_{\mathrm{in}} \cdot c^{\mathrm{T}}_{\mathrm{in}}
|
|
391
|
+
\cdot e^{\mathrm{T}}_{\mathrm{in}}
|
|
392
|
+
+ \left(\dot{C}^{\mathrm{T}}_{\mathrm{in}}
|
|
393
|
+
+ \dot{C}^{\mathrm{M}}_{\mathrm{in}}\right)
|
|
394
|
+
|
|
395
|
+
With chemical exergy enabled:
|
|
396
|
+
|
|
397
|
+
.. math::
|
|
398
|
+
\dot{C}_{\mathrm{F,cold}} = -\dot{m}_{\mathrm{in}} \cdot c^{\mathrm{T}}_{\mathrm{in}}
|
|
399
|
+
\cdot e^{\mathrm{T}}_{\mathrm{in}}
|
|
400
|
+
+ \left(\dot{C}^{\mathrm{T}}_{\mathrm{in}}
|
|
401
|
+
+ \dot{C}^{\mathrm{M}}_{\mathrm{in}}
|
|
402
|
+
+ \dot{C}^{\mathrm{CH}}_{\mathrm{in}}\right)
|
|
403
|
+
|
|
404
|
+
Total fuel cost (without chemical exergy):
|
|
405
|
+
|
|
406
|
+
.. math::
|
|
407
|
+
\dot{C}_{\mathrm{F}} = \sum \dot{C}_{\mathrm{F,hot}}
|
|
408
|
+
+ \sum \dot{C}_{\mathrm{F,cold}}
|
|
409
|
+
- \dot{C}^{\mathrm{M}}_{\mathrm{out}}
|
|
410
|
+
|
|
411
|
+
Total fuel cost (with chemical exergy):
|
|
412
|
+
|
|
413
|
+
.. math::
|
|
414
|
+
\dot{C}_{\mathrm{F}} = \sum \dot{C}_{\mathrm{F,hot}}
|
|
415
|
+
+ \sum \dot{C}_{\mathrm{F,cold}}
|
|
416
|
+
- \dot{C}^{\mathrm{M}}_{\mathrm{out}}
|
|
417
|
+
- \dot{C}^{\mathrm{CH}}_{\mathrm{out}}
|
|
418
|
+
|
|
419
|
+
**Calculated exergoeconomic indicators:**
|
|
420
|
+
|
|
421
|
+
.. math::
|
|
422
|
+
c_{\mathrm{F}} = \frac{\dot{C}_{\mathrm{F}}}{\dot{E}_{\mathrm{F}}}
|
|
423
|
+
|
|
424
|
+
.. math::
|
|
425
|
+
c_{\mathrm{P}} = \frac{\dot{C}_{\mathrm{P}}}{\dot{E}_{\mathrm{P}}}
|
|
426
|
+
|
|
427
|
+
.. math::
|
|
428
|
+
\dot{C}_{\mathrm{D}} = c_{\mathrm{F}} \cdot \dot{E}_{\mathrm{D}}
|
|
429
|
+
|
|
430
|
+
.. math::
|
|
431
|
+
r = \frac{c_{\mathrm{P}} - c_{\mathrm{F}}}{c_{\mathrm{F}}}
|
|
432
|
+
|
|
433
|
+
.. math::
|
|
434
|
+
f = \frac{\dot{Z}}{\dot{Z} + \dot{C}_{\mathrm{D}}}
|
|
435
|
+
|
|
289
436
|
Parameters
|
|
290
437
|
----------
|
|
291
438
|
T0 : float
|
|
292
|
-
Ambient temperature
|
|
293
|
-
|
|
439
|
+
Ambient temperature (K).
|
|
440
|
+
chemical_exergy_enabled : bool, optional
|
|
441
|
+
If True, chemical exergy is considered in the calculations.
|
|
442
|
+
Default is False.
|
|
443
|
+
|
|
444
|
+
Attributes Set
|
|
445
|
+
--------------
|
|
446
|
+
C_P : float
|
|
447
|
+
Cost rate of product (currency/time).
|
|
448
|
+
C_F : float
|
|
449
|
+
Cost rate of fuel (currency/time).
|
|
450
|
+
c_P : float
|
|
451
|
+
Specific cost of product (currency/energy).
|
|
452
|
+
c_F : float
|
|
453
|
+
Specific cost of fuel (currency/energy).
|
|
454
|
+
C_D : float
|
|
455
|
+
Cost rate of exergy destruction (currency/time).
|
|
456
|
+
r : float
|
|
457
|
+
Relative cost difference (dimensionless).
|
|
458
|
+
f : float
|
|
459
|
+
Exergoeconomic factor (dimensionless).
|
|
460
|
+
|
|
294
461
|
Notes
|
|
295
462
|
-----
|
|
296
|
-
The
|
|
297
|
-
|
|
463
|
+
The mixer treats thermal, mechanical, and chemical exergy components
|
|
464
|
+
differently depending on whether inlets are "hot" or "cold" relative to
|
|
465
|
+
the outlet temperature. The distinction ensures proper cost allocation
|
|
466
|
+
for streams that provide heating versus those being heated.
|
|
467
|
+
|
|
468
|
+
Future development may include merging profits from dissipative components.
|
|
298
469
|
"""
|
|
299
470
|
self.C_P = 0
|
|
300
471
|
self.C_F = 0
|
|
301
472
|
if self.outl[0]["T"] > T0:
|
|
302
|
-
for i in self.inl:
|
|
473
|
+
for i in self.inl.values():
|
|
303
474
|
if i["T"] < self.outl[0]["T"]:
|
|
304
475
|
# cold inlets
|
|
305
|
-
self.C_F += i["C_M"]
|
|
476
|
+
self.C_F += i["C_M"]
|
|
477
|
+
if chemical_exergy_enabled:
|
|
478
|
+
self.C_F += i["C_CH"]
|
|
306
479
|
else:
|
|
307
480
|
# hot inlets
|
|
308
|
-
self.C_F += -
|
|
309
|
-
|
|
310
|
-
|
|
481
|
+
self.C_F += -i["m"] * i["c_T"] * i["e_T"] + (i["C_T"] + i["C_M"])
|
|
482
|
+
if chemical_exergy_enabled:
|
|
483
|
+
self.C_F += i["C_CH"]
|
|
484
|
+
self.C_F += -self.outl[0]["C_M"]
|
|
485
|
+
if chemical_exergy_enabled:
|
|
486
|
+
self.C_F += -self.outl[0]["C_CH"]
|
|
311
487
|
elif self.outl[0]["T"] - 1e-6 < T0 and self.outl[0]["T"] + 1e-6 > T0:
|
|
312
488
|
# dissipative
|
|
313
489
|
for i in self.inl:
|
|
@@ -316,18 +492,22 @@ class Mixer(Component):
|
|
|
316
492
|
for i in self.inl:
|
|
317
493
|
if i["T"] > self.outl[0]["T"]:
|
|
318
494
|
# hot inlets
|
|
319
|
-
self.C_F += i["C_M"]
|
|
495
|
+
self.C_F += i["C_M"]
|
|
496
|
+
if chemical_exergy_enabled:
|
|
497
|
+
self.C_F += i["C_M"] + i["C_CH"]
|
|
320
498
|
else:
|
|
321
499
|
# cold inlets
|
|
322
|
-
self.C_F += -
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
500
|
+
self.C_F += -i["m"] * i["c_T"] * i["e_T"] + (i["C_T"] + i["C_M"])
|
|
501
|
+
if chemical_exergy_enabled:
|
|
502
|
+
self.C_F += i["C_CH"]
|
|
503
|
+
self.C_F += -self.outl[0]["C_M"]
|
|
504
|
+
if chemical_exergy_enabled:
|
|
505
|
+
self.C_F += -self.outl[0]["C_CH"]
|
|
506
|
+
self.C_P = self.C_F + self.Z_costs # +1/num_serving_comps * C_diff
|
|
326
507
|
# ToDo: add case that merge profits from dissipative component(s)
|
|
327
508
|
|
|
328
|
-
|
|
329
509
|
self.c_F = self.C_F / self.E_F
|
|
330
510
|
self.c_P = self.C_P / self.E_P
|
|
331
511
|
self.C_D = self.c_F * self.E_D
|
|
332
512
|
self.r = (self.c_P - self.c_F) / self.c_F
|
|
333
|
-
self.f = self.Z_costs / (self.Z_costs + self.C_D)
|
|
513
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D)
|
|
@@ -0,0 +1,173 @@
|
|
|
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 Splitter(Component):
|
|
10
|
+
r"""
|
|
11
|
+
Class for exergy analysis of splitters.
|
|
12
|
+
|
|
13
|
+
This class performs exergy analysis calculations for splitters with one
|
|
14
|
+
inlet stream and multiple outlet stream. For this component, it is not
|
|
15
|
+
reasonable to define exergy fuel and product in the same way as for other components,
|
|
16
|
+
since the splitter does not convert energy from one form to another.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
**kwargs : dict
|
|
21
|
+
Arbitrary keyword arguments passed to parent class.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
inl : dict
|
|
26
|
+
Dictionary containing inlet streams data with temperature, mass flows,
|
|
27
|
+
and specific exergies.
|
|
28
|
+
outl : dict
|
|
29
|
+
Dictionary containing outlet stream data with temperature, mass flows,
|
|
30
|
+
and specific exergies.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, **kwargs):
|
|
35
|
+
r"""Initialize splitter component with given parameters."""
|
|
36
|
+
super().__init__(**kwargs)
|
|
37
|
+
|
|
38
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
39
|
+
r"""
|
|
40
|
+
Calculate the exergy balance of the splitter.
|
|
41
|
+
|
|
42
|
+
Performs exergy balance calculations.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
T0 : float
|
|
47
|
+
Ambient temperature in :math:`\mathrm{K}`.
|
|
48
|
+
p0 : float
|
|
49
|
+
Ambient pressure in :math:`\mathrm{Pa}`.
|
|
50
|
+
split_physical_exergy : bool
|
|
51
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
52
|
+
|
|
53
|
+
Raises
|
|
54
|
+
------
|
|
55
|
+
ValueError
|
|
56
|
+
If the required inlet and outlet streams are not properly defined.
|
|
57
|
+
"""
|
|
58
|
+
# Ensure that the component has at least two inlets and one outlet.
|
|
59
|
+
if len(self.inl) < 1 or len(self.outl) < 2:
|
|
60
|
+
raise ValueError("Splitter requires at least one inlet and two outlets.")
|
|
61
|
+
outlet_list = list(self.outl.values())
|
|
62
|
+
inlet_list = list(self.inl.values())
|
|
63
|
+
E_in = sum(inlet.get("m", 0) * inlet.get("e_PH") for inlet in inlet_list)
|
|
64
|
+
E_out = sum(outlet.get("m", 0) * outlet.get("e_PH") for outlet in outlet_list)
|
|
65
|
+
self.E_P = np.nan
|
|
66
|
+
self.E_F = np.nan
|
|
67
|
+
self.E_D = E_in - E_out
|
|
68
|
+
self.epsilon = np.nan
|
|
69
|
+
|
|
70
|
+
# Log the results.
|
|
71
|
+
logging.info(
|
|
72
|
+
f"Exergy balance of Splitter {self.name} calculated: "
|
|
73
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
74
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
78
|
+
"""
|
|
79
|
+
Auxiliary equations for the splitter.
|
|
80
|
+
|
|
81
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
82
|
+
equality of specific exergy costs between the single inlet stream and each outlet stream.
|
|
83
|
+
Thermal and mechanical costs are always equated; chemical costs are equated only if enabled.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
A : numpy.ndarray
|
|
88
|
+
The current cost matrix.
|
|
89
|
+
b : numpy.ndarray
|
|
90
|
+
The current right-hand-side vector.
|
|
91
|
+
counter : int
|
|
92
|
+
The current row index in the matrix.
|
|
93
|
+
T0 : float
|
|
94
|
+
Ambient temperature (not used).
|
|
95
|
+
equations : list or dict
|
|
96
|
+
Data structure for storing equation labels.
|
|
97
|
+
chemical_exergy_enabled : bool
|
|
98
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
A : numpy.ndarray
|
|
103
|
+
The updated cost matrix.
|
|
104
|
+
b : numpy.ndarray
|
|
105
|
+
The updated right-hand-side vector.
|
|
106
|
+
counter : int
|
|
107
|
+
The updated row index after adding equations.
|
|
108
|
+
equations : list or dict
|
|
109
|
+
Updated structure with equation labels.
|
|
110
|
+
"""
|
|
111
|
+
inlet = self.inl[0]
|
|
112
|
+
|
|
113
|
+
# Thermal cost equality for each outlet
|
|
114
|
+
for outlet in self.outl.values():
|
|
115
|
+
A[counter, inlet["CostVar_index"]["T"]] = (1 / inlet["e_T"]) if inlet["e_T"] != 0 else 1
|
|
116
|
+
A[counter, outlet["CostVar_index"]["T"]] = (-1 / outlet["e_T"]) if outlet["e_T"] != 0 else -1
|
|
117
|
+
equations[counter] = {
|
|
118
|
+
"kind": "aux_equality",
|
|
119
|
+
"objects": [self.name, inlet["name"], outlet["name"]],
|
|
120
|
+
"property": "c_T",
|
|
121
|
+
}
|
|
122
|
+
b[counter] = 0
|
|
123
|
+
counter += 1
|
|
124
|
+
|
|
125
|
+
# Mechanical cost equality for each outlet
|
|
126
|
+
for outlet in self.outl.values():
|
|
127
|
+
A[counter, inlet["CostVar_index"]["M"]] = (1 / inlet["e_M"]) if inlet["e_M"] != 0 else 1
|
|
128
|
+
A[counter, outlet["CostVar_index"]["M"]] = (-1 / outlet["e_M"]) if outlet["e_M"] != 0 else -1
|
|
129
|
+
equations[counter] = {
|
|
130
|
+
"kind": "aux_equality",
|
|
131
|
+
"objects": [self.name, inlet["name"], outlet["name"]],
|
|
132
|
+
"property": "c_M",
|
|
133
|
+
}
|
|
134
|
+
b[counter] = 0
|
|
135
|
+
counter += 1
|
|
136
|
+
|
|
137
|
+
# Chemical cost equality for each outlet (if enabled)
|
|
138
|
+
if chemical_exergy_enabled:
|
|
139
|
+
for outlet in self.outl.values():
|
|
140
|
+
A[counter, inlet["CostVar_index"]["CH"]] = (1 / inlet["e_CH"]) if inlet["e_CH"] != 0 else 1
|
|
141
|
+
A[counter, outlet["CostVar_index"]["CH"]] = (-1 / outlet["e_CH"]) if outlet["e_CH"] != 0 else -1
|
|
142
|
+
equations[counter] = {
|
|
143
|
+
"kind": "aux_equality",
|
|
144
|
+
"objects": [self.name, inlet["name"], outlet["name"]],
|
|
145
|
+
"property": "c_CH",
|
|
146
|
+
}
|
|
147
|
+
b[counter] = 0
|
|
148
|
+
counter += 1
|
|
149
|
+
|
|
150
|
+
return A, b, counter, equations
|
|
151
|
+
|
|
152
|
+
def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False):
|
|
153
|
+
"""
|
|
154
|
+
The exergoeconomic balance for the Splitter component is not neglected,
|
|
155
|
+
as it does not perform any conversion of energy forms.
|
|
156
|
+
Instead, it is assumed that the specific costs of the inlet and outlet streams are equal.
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
T0 : float
|
|
162
|
+
Ambient temperature
|
|
163
|
+
chemical_exergy_enabled : bool, optional
|
|
164
|
+
If True, chemical exergy is considered in the calculations.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
self.C_P = np.nan
|
|
168
|
+
self.C_F = np.nan
|
|
169
|
+
self.c_F = np.nan
|
|
170
|
+
self.c_P = np.nan
|
|
171
|
+
self.C_D = np.nan
|
|
172
|
+
self.r = np.nan
|
|
173
|
+
self.f = np.nan
|