exerpy 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exerpy/__init__.py +12 -0
- exerpy/analyses.py +1711 -0
- exerpy/components/__init__.py +16 -0
- exerpy/components/combustion/__init__.py +0 -0
- exerpy/components/combustion/base.py +248 -0
- exerpy/components/component.py +126 -0
- exerpy/components/heat_exchanger/__init__.py +0 -0
- exerpy/components/heat_exchanger/base.py +449 -0
- exerpy/components/heat_exchanger/condenser.py +323 -0
- exerpy/components/heat_exchanger/simple.py +358 -0
- exerpy/components/heat_exchanger/steam_generator.py +264 -0
- exerpy/components/helpers/__init__.py +0 -0
- exerpy/components/helpers/cycle_closer.py +104 -0
- exerpy/components/nodes/__init__.py +0 -0
- exerpy/components/nodes/deaerator.py +318 -0
- exerpy/components/nodes/drum.py +164 -0
- exerpy/components/nodes/flash_tank.py +89 -0
- exerpy/components/nodes/mixer.py +332 -0
- exerpy/components/piping/__init__.py +0 -0
- exerpy/components/piping/valve.py +394 -0
- exerpy/components/power_machines/__init__.py +0 -0
- exerpy/components/power_machines/generator.py +168 -0
- exerpy/components/power_machines/motor.py +173 -0
- exerpy/components/turbomachinery/__init__.py +0 -0
- exerpy/components/turbomachinery/compressor.py +318 -0
- exerpy/components/turbomachinery/pump.py +310 -0
- exerpy/components/turbomachinery/turbine.py +351 -0
- exerpy/data/Ahrendts.json +90 -0
- exerpy/functions.py +637 -0
- exerpy/parser/__init__.py +0 -0
- exerpy/parser/from_aspen/__init__.py +0 -0
- exerpy/parser/from_aspen/aspen_config.py +61 -0
- exerpy/parser/from_aspen/aspen_parser.py +721 -0
- exerpy/parser/from_ebsilon/__init__.py +38 -0
- exerpy/parser/from_ebsilon/check_ebs_path.py +74 -0
- exerpy/parser/from_ebsilon/ebsilon_config.py +1055 -0
- exerpy/parser/from_ebsilon/ebsilon_functions.py +181 -0
- exerpy/parser/from_ebsilon/ebsilon_parser.py +660 -0
- exerpy/parser/from_ebsilon/utils.py +79 -0
- exerpy/parser/from_tespy/tespy_config.py +23 -0
- exerpy-0.0.1.dist-info/METADATA +158 -0
- exerpy-0.0.1.dist-info/RECORD +44 -0
- exerpy-0.0.1.dist-info/WHEEL +4 -0
- exerpy-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from exerpy.components.component import Component
|
|
6
|
+
from exerpy.components.component import component_registry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@component_registry
|
|
10
|
+
class Drum(Component):
|
|
11
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
12
|
+
r"""
|
|
13
|
+
Calculate exergy balance of a merge.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
T0 : float
|
|
18
|
+
Ambient temperature T0 / K.
|
|
19
|
+
p0 : float
|
|
20
|
+
Ambient pressure in :math:`\mathrm{Pa}`.
|
|
21
|
+
split_physical_exergy : bool
|
|
22
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
23
|
+
|
|
24
|
+
Note
|
|
25
|
+
----
|
|
26
|
+
Please note, that the exergy balance accounts for physical exergy only.
|
|
27
|
+
|
|
28
|
+
.. math::
|
|
29
|
+
|
|
30
|
+
\dot{E}_\mathrm{P} = \sum \dot{E}_{\mathrm{out,}j}^\mathrm{PH}\\
|
|
31
|
+
\dot{E}_\mathrm{F} = \sum \dot{E}_{\mathrm{in,}i}^\mathrm{PH}
|
|
32
|
+
"""
|
|
33
|
+
self.E_P = (
|
|
34
|
+
self.outl[0]['e_PH'] * self.outl[0]['m']
|
|
35
|
+
+ self.outl[1]['e_PH'] * self.outl[1]['m']
|
|
36
|
+
)
|
|
37
|
+
self.E_F = (
|
|
38
|
+
self.inl[0]['e_PH'] * self.inl[0]['m']
|
|
39
|
+
+ self.inl[1]['e_PH'] * self.inl[1]['m']
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Calculate exergy destruction and efficiency
|
|
43
|
+
self.E_D = self.E_F - self.E_P
|
|
44
|
+
self.epsilon = self.calc_epsilon()
|
|
45
|
+
|
|
46
|
+
# Log the results
|
|
47
|
+
logging.info(
|
|
48
|
+
f"Drum exergy balance calculated: "
|
|
49
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
50
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
54
|
+
"""
|
|
55
|
+
Auxiliary equations for the drum.
|
|
56
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
57
|
+
the following auxiliary cost relations:
|
|
58
|
+
(1-2) Chemical exergy cost equations (if enabled)
|
|
59
|
+
- F-principle: specific chemical exergy costs equalized between inlet and both outlets
|
|
60
|
+
- First equation balances inlet with outlet 0
|
|
61
|
+
- Second equation balances inlet with outlet 1
|
|
62
|
+
(3) Thermal exergy cost equation
|
|
63
|
+
- P-principle: specific thermal exergy costs are equalized between both outlets
|
|
64
|
+
(4) Mechanical exergy cost equation
|
|
65
|
+
- P-principle: specific mechanical exergy costs are equalized between both outlets
|
|
66
|
+
(5) Thermal-Mechanical coupling for outlet 0
|
|
67
|
+
- P-principle: thermal and mechanical specific costs must be equal at outlet 0
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
A : numpy.ndarray
|
|
71
|
+
The current cost matrix.
|
|
72
|
+
b : numpy.ndarray
|
|
73
|
+
The current right-hand-side vector.
|
|
74
|
+
counter : int
|
|
75
|
+
The current row index in the matrix.
|
|
76
|
+
T0 : float
|
|
77
|
+
Ambient temperature.
|
|
78
|
+
equations : dict
|
|
79
|
+
Dictionary for storing equation labels.
|
|
80
|
+
chemical_exergy_enabled : bool
|
|
81
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
A : numpy.ndarray
|
|
85
|
+
The updated cost matrix.
|
|
86
|
+
b : numpy.ndarray
|
|
87
|
+
The updated right-hand-side vector.
|
|
88
|
+
counter : int
|
|
89
|
+
The updated row index.
|
|
90
|
+
equations : dict
|
|
91
|
+
Updated dictionary with equation labels.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
# --- Chemical cost auxiliary equations ---
|
|
95
|
+
if chemical_exergy_enabled:
|
|
96
|
+
# Equation 1: Balance between inlet 0 and outlet 0 for chemical exergy
|
|
97
|
+
if self.inl[0]["e_CH"] != 0:
|
|
98
|
+
A[counter, self.inl[0]["CostVar_index"]["CH"]] = 1 / self.inl[0]["E_CH"]
|
|
99
|
+
else:
|
|
100
|
+
A[counter, self.inl[0]["CostVar_index"]["CH"]] = 1
|
|
101
|
+
if self.outl[0]["e_CH"] != 0:
|
|
102
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = -1 / self.outl[0]["E_CH"]
|
|
103
|
+
else:
|
|
104
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = -1
|
|
105
|
+
equations[counter] = f"aux_drum_chem1_{self.outl[0]['name']}"
|
|
106
|
+
|
|
107
|
+
# Equation 2: Balance between inlet 0 and outlet 1 for chemical exergy
|
|
108
|
+
if self.inl[0]["e_CH"] != 0:
|
|
109
|
+
A[counter+1, self.inl[0]["CostVar_index"]["CH"]] = 1 / self.inl[0]["E_CH"]
|
|
110
|
+
else:
|
|
111
|
+
A[counter+1, self.inl[0]["CostVar_index"]["CH"]] = 1
|
|
112
|
+
if self.outl[1]["e_CH"] != 0:
|
|
113
|
+
A[counter+1, self.outl[1]["CostVar_index"]["CH"]] = -1 / self.outl[1]["E_CH"]
|
|
114
|
+
else:
|
|
115
|
+
A[counter+1, self.outl[1]["CostVar_index"]["CH"]] = -1
|
|
116
|
+
equations[counter+1] = f"aux_drum_chem2_{self.outl[1]['name']}"
|
|
117
|
+
chem_rows = 2
|
|
118
|
+
else:
|
|
119
|
+
chem_rows = 0
|
|
120
|
+
|
|
121
|
+
# --- Thermal cost auxiliary equation ---
|
|
122
|
+
# For thermal exergy, we balance the two outlets.
|
|
123
|
+
if (self.outl[0]["e_T"] != 0) and (self.outl[1]["e_T"] != 0):
|
|
124
|
+
A[counter+chem_rows, self.outl[0]["CostVar_index"]["T"]] = 1 / self.outl[0]["E_T"]
|
|
125
|
+
A[counter+chem_rows, self.outl[1]["CostVar_index"]["T"]] = -1 / self.outl[1]["E_T"]
|
|
126
|
+
elif self.outl[0]["e_T"] == 0 and self.outl[1]["e_T"] != 0:
|
|
127
|
+
A[counter+chem_rows, self.outl[0]["CostVar_index"]["T"]] = 1
|
|
128
|
+
elif self.outl[0]["e_T"] != 0 and self.outl[1]["e_T"] == 0:
|
|
129
|
+
A[counter+chem_rows, self.outl[1]["CostVar_index"]["T"]] = -1
|
|
130
|
+
else:
|
|
131
|
+
A[counter+chem_rows, self.outl[0]["CostVar_index"]["T"]] = 1
|
|
132
|
+
A[counter+chem_rows, self.outl[1]["CostVar_index"]["T"]] = -1
|
|
133
|
+
equations[counter+chem_rows] = f"aux_drum_therm_{self.outl[0]['name']}_{self.outl[1]['name']}"
|
|
134
|
+
|
|
135
|
+
# --- Mechanical cost auxiliary equation ---
|
|
136
|
+
if self.outl[0]["e_M"] != 0:
|
|
137
|
+
A[counter+chem_rows+1, self.outl[0]["CostVar_index"]["M"]] = 1 / self.outl[0]["E_M"]
|
|
138
|
+
else:
|
|
139
|
+
A[counter+chem_rows+1, self.outl[0]["CostVar_index"]["M"]] = 1
|
|
140
|
+
if self.outl[1]["e_M"] != 0:
|
|
141
|
+
A[counter+chem_rows+1, self.outl[1]["CostVar_index"]["M"]] = -1 / self.outl[1]["E_M"]
|
|
142
|
+
else:
|
|
143
|
+
A[counter+chem_rows+1, self.outl[1]["CostVar_index"]["M"]] = -1
|
|
144
|
+
equations[counter+chem_rows+1] = f"aux_drum_mech_{self.outl[0]['name']}_{self.outl[1]['name']}"
|
|
145
|
+
|
|
146
|
+
# --- Thermal-Mechanical coupling equation for outlet 0 ---
|
|
147
|
+
# This enforces that the thermal and mechanical cost components at outlet 0 are consistent.
|
|
148
|
+
if (self.outl[0]["e_T"] != 0) and (self.outl[0]["e_M"] != 0):
|
|
149
|
+
A[counter+chem_rows+2, self.outl[0]["CostVar_index"]["T"]] = 1 / self.outl[0]["E_T"]
|
|
150
|
+
A[counter+chem_rows+2, self.outl[0]["CostVar_index"]["M"]] = -1 / self.outl[0]["E_M"]
|
|
151
|
+
elif (self.outl[0]["e_T"] == 0) and (self.outl[0]["e_M"] == 0):
|
|
152
|
+
A[counter+chem_rows+2, self.outl[0]["CostVar_index"]["T"]] = 1
|
|
153
|
+
A[counter+chem_rows+2, self.outl[0]["CostVar_index"]["M"]] = -1
|
|
154
|
+
elif self.outl[0]["e_T"] == 0:
|
|
155
|
+
A[counter+chem_rows+2, self.outl[0]["CostVar_index"]["T"]] = 1
|
|
156
|
+
else:
|
|
157
|
+
A[counter+chem_rows+2, self.outl[0]["CostVar_index"]["M"]] = -1
|
|
158
|
+
equations[counter+chem_rows+2] = f"aux_drum_therm_mech_{self.outl[0]['name']}"
|
|
159
|
+
|
|
160
|
+
# Set the right-hand side entries to zero for all added rows.
|
|
161
|
+
for i in range(chem_rows + 3):
|
|
162
|
+
b[counter + i] = 0
|
|
163
|
+
|
|
164
|
+
return A, b, counter + chem_rows + 3, equations
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from exerpy.components.component import Component
|
|
6
|
+
from exerpy.components.component import component_registry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@component_registry
|
|
10
|
+
class FlashTank(Component):
|
|
11
|
+
r"""
|
|
12
|
+
Class for exergy analysis of flash tanks.
|
|
13
|
+
|
|
14
|
+
This class performs exergy analysis calculations for flash tanks where a feed
|
|
15
|
+
stream is partially flashed into two different outlet streams (e.g., vapor and liquid).
|
|
16
|
+
The exergy fuel is calculated using the physical/thermal exergy of the inlet streams,
|
|
17
|
+
while the exergy product is computed as the sum of the physical exergy of the outlet streams.
|
|
18
|
+
Exergy destruction is the difference between the fuel and product, and the efficiency is defined
|
|
19
|
+
as ε = E_P / E_F.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
**kwargs : dict
|
|
24
|
+
Arbitrary keyword arguments passed to the parent class.
|
|
25
|
+
|
|
26
|
+
Attributes
|
|
27
|
+
----------
|
|
28
|
+
E_F : float
|
|
29
|
+
Exergy fuel of the component (W), computed from the inlet streams.
|
|
30
|
+
E_P : float
|
|
31
|
+
Exergy product of the component (W), computed from the outlet streams.
|
|
32
|
+
E_D : float
|
|
33
|
+
Exergy destruction (W).
|
|
34
|
+
epsilon : float
|
|
35
|
+
Exergetic efficiency, defined as E_P/E_F.
|
|
36
|
+
inl : dict
|
|
37
|
+
Dictionary containing inlet streams data (e.g., temperature, mass flow, specific exergy).
|
|
38
|
+
outl : dict
|
|
39
|
+
Dictionary containing outlet streams data (e.g., temperature, mass flow, specific exergy).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, **kwargs):
|
|
43
|
+
r"""Initialize flash tank component with given parameters."""
|
|
44
|
+
super().__init__(**kwargs)
|
|
45
|
+
|
|
46
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy: bool) -> None:
|
|
47
|
+
r"""
|
|
48
|
+
Calculate the exergy balance of the flash tank.
|
|
49
|
+
|
|
50
|
+
The exergy fuel (E_F) is computed as the sum of the inlet streams' exergy.
|
|
51
|
+
If split_physical_exergy is True, the thermal exergy (e_T) is used;
|
|
52
|
+
otherwise, the physical exergy (e_PH) is used.
|
|
53
|
+
The exergy product (E_P) is calculated as the sum of the physical exergy of the
|
|
54
|
+
outlet streams. Exergy destruction (E_D) is the difference E_F - E_P, and
|
|
55
|
+
exergetic efficiency is ε = E_P / E_F.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
T0 : float
|
|
60
|
+
Ambient temperature in Kelvin.
|
|
61
|
+
p0 : float
|
|
62
|
+
Ambient pressure in Pascal.
|
|
63
|
+
split_physical_exergy : bool
|
|
64
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
65
|
+
"""
|
|
66
|
+
# Ensure that at least two inlet streams and two outlet streams are provided.
|
|
67
|
+
if len(self.inl) < 2 or len(self.outl) < 2:
|
|
68
|
+
raise ValueError("FlashTank requires at least two inlets and two outlets.")
|
|
69
|
+
|
|
70
|
+
if split_physical_exergy:
|
|
71
|
+
exergy_type = 'e_T'
|
|
72
|
+
else:
|
|
73
|
+
exergy_type = 'e_PH'
|
|
74
|
+
|
|
75
|
+
# Calculate exergy fuel (E_F) from inlet streams.
|
|
76
|
+
self.E_F = sum(inlet['m'] * inlet[exergy_type] for inlet in self.inl.values())
|
|
77
|
+
# Calculate exergy product (E_P) from outlet streams.
|
|
78
|
+
self.E_P = sum(outlet['m'] * outlet[exergy_type] for outlet in self.outl.values())
|
|
79
|
+
|
|
80
|
+
# Exergy destruction and efficiency.
|
|
81
|
+
self.E_D = self.E_F - self.E_P
|
|
82
|
+
self.epsilon = self.calc_epsilon()
|
|
83
|
+
|
|
84
|
+
# Log the results.
|
|
85
|
+
logging.info(
|
|
86
|
+
f"FlashTank exergy balance calculated: "
|
|
87
|
+
f"E_F = {self.E_F:.2f} W, E_P = {self.E_P:.2f} W, E_D = {self.E_D:.2f} W, "
|
|
88
|
+
f"Efficiency = {self.epsilon:.2%}"
|
|
89
|
+
)
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from exerpy.components.component import Component
|
|
6
|
+
from exerpy.components.component import component_registry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@component_registry
|
|
10
|
+
class Mixer(Component):
|
|
11
|
+
r"""
|
|
12
|
+
Class for exergy analysis of mixers.
|
|
13
|
+
|
|
14
|
+
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
|
|
17
|
+
relationships between inlet streams, outlet streams, and ambient conditions.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
**kwargs : dict
|
|
22
|
+
Arbitrary keyword arguments passed to parent class.
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
----------
|
|
26
|
+
E_F : float
|
|
27
|
+
Exergy fuel of the component :math:`\dot{E}_\mathrm{F}` in :math:`\mathrm{W}`.
|
|
28
|
+
E_P : float
|
|
29
|
+
Exergy product of the component :math:`\dot{E}_\mathrm{P}` in :math:`\mathrm{W}`.
|
|
30
|
+
E_D : float
|
|
31
|
+
Exergy destruction of the component :math:`\dot{E}_\mathrm{D}` in :math:`\mathrm{W}`.
|
|
32
|
+
epsilon : float
|
|
33
|
+
Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
|
|
34
|
+
inl : dict
|
|
35
|
+
Dictionary containing inlet streams data with temperature, mass flows,
|
|
36
|
+
and specific exergies.
|
|
37
|
+
outl : dict
|
|
38
|
+
Dictionary containing outlet stream data with temperature, mass flows,
|
|
39
|
+
and specific exergies.
|
|
40
|
+
|
|
41
|
+
Notes
|
|
42
|
+
-----
|
|
43
|
+
The exergy analysis accounts for physical exergy only. The equations for exergy
|
|
44
|
+
product and fuel are defined based on temperature relationships:
|
|
45
|
+
|
|
46
|
+
.. math::
|
|
47
|
+
|
|
48
|
+
\dot{E}_\mathrm{P} =
|
|
49
|
+
\begin{cases}
|
|
50
|
+
\begin{cases}
|
|
51
|
+
\sum_i \dot{m}_i \cdot (e_\mathrm{out}^\mathrm{PH} -
|
|
52
|
+
e_{\mathrm{in,}i}^\mathrm{PH})
|
|
53
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \mathrm{ & }
|
|
54
|
+
T_{\mathrm{in,}i} \geq T_0 \\
|
|
55
|
+
\sum_i \dot{m}_i \cdot e_\mathrm{out}^\mathrm{PH}
|
|
56
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \mathrm{ & }
|
|
57
|
+
T_{\mathrm{in,}i} < T_0 \\
|
|
58
|
+
\end{cases} & T_\mathrm{out} > T_0\\
|
|
59
|
+
\mathrm{not defined (nan)} & T_\mathrm{out} = T_0\\
|
|
60
|
+
\begin{cases}
|
|
61
|
+
\sum_i \dot{m}_i \cdot e_\mathrm{out}^\mathrm{PH}
|
|
62
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \mathrm{ & }
|
|
63
|
+
T_{\mathrm{in,}i} \geq T_0 \\
|
|
64
|
+
\sum_i \dot{m}_i \cdot (e_\mathrm{out}^\mathrm{PH} -
|
|
65
|
+
e_{\mathrm{in,}i}^\mathrm{PH})
|
|
66
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \mathrm{ & }
|
|
67
|
+
T_{\mathrm{in,}i} < T_0 \\
|
|
68
|
+
\end{cases} & T_\mathrm{out} < T_0\\
|
|
69
|
+
\end{cases}
|
|
70
|
+
|
|
71
|
+
\dot{E}_\mathrm{F} =
|
|
72
|
+
\begin{cases}
|
|
73
|
+
\begin{cases}
|
|
74
|
+
\sum_i \dot{m}_i \cdot (e_{\mathrm{in,}i}^\mathrm{PH} -
|
|
75
|
+
e_\mathrm{out}^\mathrm{PH})
|
|
76
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \\
|
|
77
|
+
\sum_i \dot{m}_i \cdot e_{\mathrm{in,}i}^\mathrm{PH}
|
|
78
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \mathrm{ & }
|
|
79
|
+
T_{\mathrm{in,}i} < T_0 \\
|
|
80
|
+
\end{cases} & T_\mathrm{out} > T_0\\
|
|
81
|
+
\sum_i \dot{m}_i \cdot e_{\mathrm{in,}i}^\mathrm{PH}
|
|
82
|
+
& T_\mathrm{out} = T_0\\
|
|
83
|
+
\begin{cases}
|
|
84
|
+
\sum_i \dot{m}_i \cdot e_{\mathrm{in,}i}^\mathrm{PH}
|
|
85
|
+
& T_{\mathrm{in,}i} > T_\mathrm{out} \mathrm{ & }
|
|
86
|
+
T_{\mathrm{in,}i} \geq T_0 \\
|
|
87
|
+
\sum_i \dot{m}_i \cdot (e_{\mathrm{in,}i}^\mathrm{PH} -
|
|
88
|
+
e_\mathrm{out}^\mathrm{PH})
|
|
89
|
+
& T_{\mathrm{in,}i} < T_\mathrm{out} \\
|
|
90
|
+
\end{cases} & T_\mathrm{out} < T_0\\
|
|
91
|
+
\end{cases}
|
|
92
|
+
|
|
93
|
+
\forall i \in \mathrm{mixer inlets}
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, **kwargs):
|
|
97
|
+
r"""Initialize mixer component with given parameters."""
|
|
98
|
+
super().__init__(**kwargs)
|
|
99
|
+
|
|
100
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
101
|
+
r"""
|
|
102
|
+
Calculate the exergy balance of the mixer.
|
|
103
|
+
|
|
104
|
+
Performs exergy balance calculations considering the temperature relationships
|
|
105
|
+
between inlet streams, outlet stream(s), and ambient conditions.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
T0 : float
|
|
110
|
+
Ambient temperature in :math:`\mathrm{K}`.
|
|
111
|
+
p0 : float
|
|
112
|
+
Ambient pressure in :math:`\mathrm{Pa}`.
|
|
113
|
+
split_physical_exergy : bool
|
|
114
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
115
|
+
|
|
116
|
+
Raises
|
|
117
|
+
------
|
|
118
|
+
ValueError
|
|
119
|
+
If the required inlet and outlet streams are not properly defined.
|
|
120
|
+
"""
|
|
121
|
+
# Ensure that the component has at least two inlets and one outlet.
|
|
122
|
+
if len(self.inl) < 2 or len(self.outl) < 1:
|
|
123
|
+
raise ValueError("Mixer requires at least two inlets and one outlet.")
|
|
124
|
+
|
|
125
|
+
# Compute effective outlet state by aggregating all outlet streams.
|
|
126
|
+
# Assume that all outlets share the same thermodynamic state.
|
|
127
|
+
outlet_list = list(self.outl.values())
|
|
128
|
+
first_outlet = outlet_list[0]
|
|
129
|
+
T_out = first_outlet['T']
|
|
130
|
+
e_out_PH = first_outlet['e_PH']
|
|
131
|
+
# Verify that all outlets have the same thermodynamic state.
|
|
132
|
+
for outlet in outlet_list:
|
|
133
|
+
if outlet['T'] != T_out or outlet['e_PH'] != e_out_PH:
|
|
134
|
+
msg = "All outlets in Mixer must have the same thermodynamic state."
|
|
135
|
+
logging.error(msg)
|
|
136
|
+
raise ValueError(msg)
|
|
137
|
+
# Sum the mass of all outlet streams (if needed for further analysis)
|
|
138
|
+
m_out_total = sum(outlet.get('m', 0) for outlet in outlet_list)
|
|
139
|
+
|
|
140
|
+
# Initialize exergy product and fuel.
|
|
141
|
+
self.E_P = 0
|
|
142
|
+
self.E_F = 0
|
|
143
|
+
|
|
144
|
+
# Case 1: Outlet temperature is greater than ambient.
|
|
145
|
+
if T_out > T0:
|
|
146
|
+
for _, inlet in self.inl.items():
|
|
147
|
+
# Case when inlet temperature is lower than outlet temperature.
|
|
148
|
+
if inlet['T'] < T_out:
|
|
149
|
+
if inlet['T'] >= T0:
|
|
150
|
+
# Contribution to exergy product from inlets above ambient.
|
|
151
|
+
self.E_P += inlet['m'] * (e_out_PH - inlet['e_PH'])
|
|
152
|
+
else: # inlet['T'] < T0
|
|
153
|
+
self.E_P += inlet['m'] * e_out_PH
|
|
154
|
+
self.E_F += inlet['m'] * inlet['e_PH']
|
|
155
|
+
else: # inlet['T'] > T_out
|
|
156
|
+
self.E_F += inlet['m'] * (inlet['e_PH'] - e_out_PH)
|
|
157
|
+
|
|
158
|
+
# Case 2: Outlet temperature equals ambient.
|
|
159
|
+
elif T_out == T0:
|
|
160
|
+
self.E_P = np.nan
|
|
161
|
+
for _, inlet in self.inl.items():
|
|
162
|
+
self.E_F += inlet['m'] * inlet['e_PH']
|
|
163
|
+
|
|
164
|
+
# Case 3: Outlet temperature is less than ambient.
|
|
165
|
+
else: # T_out < T0
|
|
166
|
+
for _, inlet in self.inl.items():
|
|
167
|
+
if inlet['T'] > T_out:
|
|
168
|
+
if inlet['T'] >= T0:
|
|
169
|
+
self.E_P += inlet['m'] * e_out_PH
|
|
170
|
+
self.E_F += inlet['m'] * inlet['e_PH']
|
|
171
|
+
else: # inlet['T'] < T0
|
|
172
|
+
self.E_P += inlet['m'] * (e_out_PH - inlet['e_PH'])
|
|
173
|
+
else: # inlet['T'] <= T_out
|
|
174
|
+
self.E_F += inlet['m'] * (inlet['e_PH'] - e_out_PH)
|
|
175
|
+
|
|
176
|
+
# Calculate exergy destruction and efficiency.
|
|
177
|
+
self.E_D = self.E_F - self.E_P
|
|
178
|
+
self.epsilon = self.calc_epsilon()
|
|
179
|
+
|
|
180
|
+
# Log the results.
|
|
181
|
+
logging.info(
|
|
182
|
+
f"Mixer exergy balance calculated: "
|
|
183
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
184
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
190
|
+
"""
|
|
191
|
+
Auxiliary equations for the mixer.
|
|
192
|
+
|
|
193
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
194
|
+
the following auxiliary cost relations:
|
|
195
|
+
|
|
196
|
+
(1) Chemical exergy cost equation (if enabled):
|
|
197
|
+
- F-principle: The specific chemical exergy cost of the outlet stream is calculated as
|
|
198
|
+
the weighted average of the specific chemical exergy costs of the inlet streams.
|
|
199
|
+
- For inlets with zero chemical exergy, their specific costs are directly transferred.
|
|
200
|
+
|
|
201
|
+
(2) Mechanical exergy cost equation:
|
|
202
|
+
- F-principle: The specific mechanical exergy cost of the outlet stream is calculated as
|
|
203
|
+
the weighted average of the specific mechanical exergy costs of the inlet streams.
|
|
204
|
+
- For inlets with zero mechanical exergy, their specific costs are directly transferred.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
A : numpy.ndarray
|
|
209
|
+
The current cost matrix.
|
|
210
|
+
b : numpy.ndarray
|
|
211
|
+
The current right-hand-side vector.
|
|
212
|
+
counter : int
|
|
213
|
+
The current row index in the matrix.
|
|
214
|
+
T0 : float
|
|
215
|
+
Ambient temperature (provided for consistency; not used in this function).
|
|
216
|
+
equations : list or dict
|
|
217
|
+
Data structure for storing equation labels.
|
|
218
|
+
chemical_exergy_enabled : bool
|
|
219
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
A : numpy.ndarray
|
|
224
|
+
The updated cost matrix.
|
|
225
|
+
b : numpy.ndarray
|
|
226
|
+
The updated right-hand-side vector.
|
|
227
|
+
counter : int
|
|
228
|
+
The updated row index (increased by 2 if chemical exergy is enabled, or by 1 otherwise).
|
|
229
|
+
equations : list or dict
|
|
230
|
+
Updated structure with equation labels.
|
|
231
|
+
"""
|
|
232
|
+
# --- Chemical cost auxiliary equation (conditionally added) ---
|
|
233
|
+
if chemical_exergy_enabled:
|
|
234
|
+
if self.outl[0]["e_CH"] != 0:
|
|
235
|
+
A[counter, self.outl[0]["CostVar_index"]["CH"]] = -1 / self.outl[0]["E_CH"]
|
|
236
|
+
# Iterate over inlet streams for chemical mixing.
|
|
237
|
+
for inlet in self.inl.values():
|
|
238
|
+
if inlet["e_CH"] != 0:
|
|
239
|
+
A[counter, inlet["CostVar_index"]["CH"]] = inlet["m"] / (self.outl[0]["m"] * inlet["E_CH"])
|
|
240
|
+
else:
|
|
241
|
+
A[counter, inlet["CostVar_index"]["CH"]] = 1
|
|
242
|
+
else:
|
|
243
|
+
# Outlet chemical exergy is zero: assign fallback for all inlets.
|
|
244
|
+
for inlet in self.inl.values():
|
|
245
|
+
A[counter, inlet["CostVar_index"]["CH"]] = 1
|
|
246
|
+
equations[counter] = f"aux_mixing_chem_{self.outl[0]['name']}"
|
|
247
|
+
chem_row = 1 # One row added for chemical equation.
|
|
248
|
+
else:
|
|
249
|
+
chem_row = 0 # No row added.
|
|
250
|
+
|
|
251
|
+
# --- Mechanical cost auxiliary equation ---
|
|
252
|
+
mech_row = 0 # This row will always be added.
|
|
253
|
+
if self.outl[0]["e_M"] != 0:
|
|
254
|
+
A[counter + chem_row, self.outl[0]["CostVar_index"]["M"]] = -1 / self.outl[0]["E_M"]
|
|
255
|
+
# Iterate over inlet streams for mechanical mixing.
|
|
256
|
+
for inlet in self.inl.values():
|
|
257
|
+
if inlet["e_M"] != 0:
|
|
258
|
+
A[counter + chem_row, inlet["CostVar_index"]["M"]] = inlet["m"] / (self.outl[0]["m"] * inlet["E_M"])
|
|
259
|
+
else:
|
|
260
|
+
A[counter + chem_row, inlet["CostVar_index"]["M"]] = 1
|
|
261
|
+
else:
|
|
262
|
+
for inlet in self.inl.values():
|
|
263
|
+
A[counter + chem_row, inlet["CostVar_index"]["M"]] = 1
|
|
264
|
+
equations[counter + chem_row] = f"aux_mixing_mech_{self.outl[0]['name']}"
|
|
265
|
+
|
|
266
|
+
# Set the right-hand side entries to zero for the added rows.
|
|
267
|
+
if chemical_exergy_enabled:
|
|
268
|
+
b[counter] = 0
|
|
269
|
+
b[counter + 1] = 0
|
|
270
|
+
counter += 2 # Two rows were added.
|
|
271
|
+
else:
|
|
272
|
+
b[counter] = 0
|
|
273
|
+
counter += 1 # Only one row was added.
|
|
274
|
+
|
|
275
|
+
return A, b, counter, equations
|
|
276
|
+
|
|
277
|
+
def exergoeconomic_balance(self, T0):
|
|
278
|
+
"""
|
|
279
|
+
Perform exergoeconomic balance calculations for the mixer.
|
|
280
|
+
|
|
281
|
+
This method calculates various exergoeconomic parameters including:
|
|
282
|
+
- Cost rates of product (C_P) and fuel (C_F)
|
|
283
|
+
- Specific cost of product (c_P) and fuel (c_F)
|
|
284
|
+
- Cost rate of exergy destruction (C_D)
|
|
285
|
+
- Relative cost difference (r)
|
|
286
|
+
- Exergoeconomic factor (f)
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
T0 : float
|
|
291
|
+
Ambient temperature
|
|
292
|
+
|
|
293
|
+
Notes
|
|
294
|
+
-----
|
|
295
|
+
The exergoeconomic balance considers thermal (T), chemical (CH),
|
|
296
|
+
and mechanical (M) exergy components for the inlet and outlet streams.
|
|
297
|
+
"""
|
|
298
|
+
self.C_P = 0
|
|
299
|
+
self.C_F = 0
|
|
300
|
+
if self.outl[0]["T"] > T0:
|
|
301
|
+
for i in self.inl:
|
|
302
|
+
if i["T"] < self.outl[0]["T"]:
|
|
303
|
+
# cold inlets
|
|
304
|
+
self.C_F += i["C_M"] + i["C_CH"]
|
|
305
|
+
else:
|
|
306
|
+
# hot inlets
|
|
307
|
+
self.C_F += - i["M"] * i["C_T"] * i["e_T"] + (
|
|
308
|
+
i["C_T"] + i["C_M"] + i["C_CH"])
|
|
309
|
+
self.C_F += (-self.outl[0]["C_M"] - self.outl[0]["C_CH"])
|
|
310
|
+
elif self.outl[0]["T"] - 1e-6 < T0 and self.outl[0]["T"] + 1e-6 > T0:
|
|
311
|
+
# dissipative
|
|
312
|
+
for i in self.inl:
|
|
313
|
+
self.C_F += i["C_TOT"]
|
|
314
|
+
else:
|
|
315
|
+
for i in self.inl:
|
|
316
|
+
if i["T"] > self.outl[0]["T"]:
|
|
317
|
+
# hot inlets
|
|
318
|
+
self.C_F += i["C_M"] + i["C_CH"]
|
|
319
|
+
else:
|
|
320
|
+
# cold inlets
|
|
321
|
+
self.C_F += - i["M"] * i["C_T"] * i["e_T"] + (
|
|
322
|
+
i["C_T"] + i["C_M"] + i["C_CH"])
|
|
323
|
+
self.C_F += (-self.outl[0]["C_M"] - self.outl[0]["C_CH"])
|
|
324
|
+
self.C_P = self.C_F + self.Z_costs # +1/num_serving_comps * C_diff
|
|
325
|
+
# ToDo: add case that merge profits from dissipative component(s)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
self.c_F = self.C_F / self.E_F
|
|
329
|
+
self.c_P = self.C_P / self.E_P
|
|
330
|
+
self.C_D = self.c_F * self.E_D
|
|
331
|
+
self.r = (self.c_P - self.c_F) / self.c_F
|
|
332
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D)
|
|
File without changes
|