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.
Files changed (44) hide show
  1. exerpy/__init__.py +12 -0
  2. exerpy/analyses.py +1711 -0
  3. exerpy/components/__init__.py +16 -0
  4. exerpy/components/combustion/__init__.py +0 -0
  5. exerpy/components/combustion/base.py +248 -0
  6. exerpy/components/component.py +126 -0
  7. exerpy/components/heat_exchanger/__init__.py +0 -0
  8. exerpy/components/heat_exchanger/base.py +449 -0
  9. exerpy/components/heat_exchanger/condenser.py +323 -0
  10. exerpy/components/heat_exchanger/simple.py +358 -0
  11. exerpy/components/heat_exchanger/steam_generator.py +264 -0
  12. exerpy/components/helpers/__init__.py +0 -0
  13. exerpy/components/helpers/cycle_closer.py +104 -0
  14. exerpy/components/nodes/__init__.py +0 -0
  15. exerpy/components/nodes/deaerator.py +318 -0
  16. exerpy/components/nodes/drum.py +164 -0
  17. exerpy/components/nodes/flash_tank.py +89 -0
  18. exerpy/components/nodes/mixer.py +332 -0
  19. exerpy/components/piping/__init__.py +0 -0
  20. exerpy/components/piping/valve.py +394 -0
  21. exerpy/components/power_machines/__init__.py +0 -0
  22. exerpy/components/power_machines/generator.py +168 -0
  23. exerpy/components/power_machines/motor.py +173 -0
  24. exerpy/components/turbomachinery/__init__.py +0 -0
  25. exerpy/components/turbomachinery/compressor.py +318 -0
  26. exerpy/components/turbomachinery/pump.py +310 -0
  27. exerpy/components/turbomachinery/turbine.py +351 -0
  28. exerpy/data/Ahrendts.json +90 -0
  29. exerpy/functions.py +637 -0
  30. exerpy/parser/__init__.py +0 -0
  31. exerpy/parser/from_aspen/__init__.py +0 -0
  32. exerpy/parser/from_aspen/aspen_config.py +61 -0
  33. exerpy/parser/from_aspen/aspen_parser.py +721 -0
  34. exerpy/parser/from_ebsilon/__init__.py +38 -0
  35. exerpy/parser/from_ebsilon/check_ebs_path.py +74 -0
  36. exerpy/parser/from_ebsilon/ebsilon_config.py +1055 -0
  37. exerpy/parser/from_ebsilon/ebsilon_functions.py +181 -0
  38. exerpy/parser/from_ebsilon/ebsilon_parser.py +660 -0
  39. exerpy/parser/from_ebsilon/utils.py +79 -0
  40. exerpy/parser/from_tespy/tespy_config.py +23 -0
  41. exerpy-0.0.1.dist-info/METADATA +158 -0
  42. exerpy-0.0.1.dist-info/RECORD +44 -0
  43. exerpy-0.0.1.dist-info/WHEEL +4 -0
  44. 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