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,323 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from exerpy.components.component import Component
|
|
4
|
+
from exerpy.components.component import component_registry
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@component_registry
|
|
8
|
+
class Condenser(Component):
|
|
9
|
+
"""
|
|
10
|
+
Condenser component class.
|
|
11
|
+
|
|
12
|
+
This class represents a condenser within the system, responsible for
|
|
13
|
+
calculating the exergy balance specific to condensation processes.
|
|
14
|
+
It evaluates the exergy interactions between multiple inlet and outlet
|
|
15
|
+
streams to determine exergy loss and exergy destruction.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
E_L : float
|
|
20
|
+
Exergy loss associated with heat transfer (difference in physical exergy
|
|
21
|
+
between specific outlet and inlet streams).
|
|
22
|
+
E_D : float
|
|
23
|
+
Exergy destruction, calculated as the difference between the primary
|
|
24
|
+
inlet and outlet streams minus exergy loss (E_L), representing
|
|
25
|
+
irreversibilities in the condensation process.
|
|
26
|
+
E_P : None
|
|
27
|
+
Exergy product, not defined for a condenser as there is no exergy output
|
|
28
|
+
intended for productive use.
|
|
29
|
+
E_F : None
|
|
30
|
+
Exergy fuel, typically undefined for a condenser as it does not involve
|
|
31
|
+
an external exergy input for production purposes.
|
|
32
|
+
epsilon : None
|
|
33
|
+
Exergy efficiency, not applicable to a condenser due to the nature of
|
|
34
|
+
exergy interactions focused on loss and destruction.
|
|
35
|
+
|
|
36
|
+
Methods
|
|
37
|
+
-------
|
|
38
|
+
__init__(**kwargs)
|
|
39
|
+
Initializes the Condenser component with given parameters.
|
|
40
|
+
calc_exergy_balance(T0, p0)
|
|
41
|
+
Calculates the exergy balance of the condenser.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, **kwargs):
|
|
45
|
+
"""
|
|
46
|
+
Initialize the Condenser component.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
**kwargs : dict
|
|
51
|
+
Arbitrary keyword arguments passed to the base class initializer.
|
|
52
|
+
"""
|
|
53
|
+
super().__init__(**kwargs)
|
|
54
|
+
|
|
55
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Calculate the exergy balance of the condenser.
|
|
58
|
+
|
|
59
|
+
This method computes exergy loss and exergy destruction based on the inlet
|
|
60
|
+
and outlet streams involved in the condensation process.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
T0 : float
|
|
65
|
+
Reference temperature in Kelvin.
|
|
66
|
+
p0 : float
|
|
67
|
+
Reference pressure in Pascals.
|
|
68
|
+
split_physical_exergy : bool
|
|
69
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
70
|
+
|
|
71
|
+
Raises
|
|
72
|
+
------
|
|
73
|
+
ValueError
|
|
74
|
+
If the condenser does not have exactly two inlets and two outlets.
|
|
75
|
+
|
|
76
|
+
Calculation Details
|
|
77
|
+
-------------------
|
|
78
|
+
The exergy balance is determined based on exergy transfer due to heat loss (`E_L`)
|
|
79
|
+
and the exergy destruction within the system:
|
|
80
|
+
|
|
81
|
+
- **Exergy Loss (E_L)**:
|
|
82
|
+
\[
|
|
83
|
+
E_L = \dot{m}_{\mathrm{out,1}} \cdot (e_{\mathrm{PH,out,1}} - e_{\mathrm{PH,in,1}})
|
|
84
|
+
\]
|
|
85
|
+
Represents the exergy loss due to heat transfer from the process.
|
|
86
|
+
|
|
87
|
+
- **Exergy Destruction (E_D)**:
|
|
88
|
+
\[
|
|
89
|
+
E_D = \dot{m}_{\mathrm{out,0}} \cdot (e_{\mathrm{PH,in,0}} - e_{\mathrm{PH,out,0}}) - E_L
|
|
90
|
+
\]
|
|
91
|
+
Accounts for the irreversibilities and losses in the condenser.
|
|
92
|
+
|
|
93
|
+
Note
|
|
94
|
+
----
|
|
95
|
+
Exergy product (E_P) and exergy fuel (E_F) are generally undefined in a
|
|
96
|
+
condenser due to the focus on exergy loss rather than productive exergy usage.
|
|
97
|
+
"""
|
|
98
|
+
# Ensure that the component has both inlet and outlet streams
|
|
99
|
+
if len(self.inl) < 2 or len(self.outl) < 2:
|
|
100
|
+
raise ValueError("Condenser requires two inlets and two outlets.")
|
|
101
|
+
|
|
102
|
+
# Calculate exergy loss (E_L) for the heat transfer process
|
|
103
|
+
self.E_L = self.outl[1]['m'] * (self.outl[1]['e_PH'] - self.inl[1]['e_PH'])
|
|
104
|
+
|
|
105
|
+
# Calculate exergy destruction (E_D)
|
|
106
|
+
self.E_D = self.outl[0]['m'] * (self.inl[0]['e_PH'] - self.outl[0]['e_PH']) - self.E_L
|
|
107
|
+
|
|
108
|
+
# Exergy fuel and product are not typically defined for a condenser
|
|
109
|
+
self.E_F = None
|
|
110
|
+
self.E_P = None
|
|
111
|
+
self.epsilon = None
|
|
112
|
+
|
|
113
|
+
# Log the exergy balance results
|
|
114
|
+
logging.info(f"Condenser exergy balance calculated: E_D={self.E_D}, E_L={self.E_L}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
118
|
+
"""
|
|
119
|
+
Auxiliary equations for the condenser.
|
|
120
|
+
|
|
121
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
122
|
+
the following auxiliary cost relations:
|
|
123
|
+
|
|
124
|
+
(1) Thermal auxiliary equation based on temperature cases:
|
|
125
|
+
- Case 1 (T > T0): c_T(hot_inlet)/E_T(hot_in) = c_T(hot_outlet)/E_T(hot_out)
|
|
126
|
+
F-principle: specific thermal exergy costs equalized in hot stream
|
|
127
|
+
- Case 2 (T <= T0): c_T(cold_inlet)/E_T(cold_in) = c_T(cold_outlet)/E_T(cold_out)
|
|
128
|
+
F-principle: specific thermal exergy costs equalized in cold stream
|
|
129
|
+
- Case 3 (mixed temperatures): c_T(hot_outlet)/E_T(hot_out) = c_T(cold_outlet)/E_T(cold_out)
|
|
130
|
+
P-principle: equal specific costs of thermal exergy in outlets
|
|
131
|
+
|
|
132
|
+
(2) c_M(hot_inlet)/E_M(hot_in) = c_M(hot_outlet)/E_M(hot_out)
|
|
133
|
+
- F-principle: specific mechanical exergy costs equalized in hot stream
|
|
134
|
+
|
|
135
|
+
(3) c_M(cold_inlet)/E_M(cold_in) = c_M(cold_outlet)/E_M(cold_out)
|
|
136
|
+
- F-principle: specific mechanical exergy costs equalized in cold stream
|
|
137
|
+
|
|
138
|
+
(4-5) Chemical exergy cost equations (if enabled) for hot and cold streams
|
|
139
|
+
- F-principle: specific chemical exergy costs equalized between inlets/outlets
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
A : numpy.ndarray
|
|
144
|
+
The current cost matrix.
|
|
145
|
+
b : numpy.ndarray
|
|
146
|
+
The current right-hand-side vector.
|
|
147
|
+
counter : int
|
|
148
|
+
The current row index in the matrix.
|
|
149
|
+
T0 : float
|
|
150
|
+
Ambient temperature.
|
|
151
|
+
equations : dict
|
|
152
|
+
Dictionary for storing equation labels.
|
|
153
|
+
chemical_exergy_enabled : bool
|
|
154
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
A : numpy.ndarray
|
|
159
|
+
The updated cost matrix.
|
|
160
|
+
b : numpy.ndarray
|
|
161
|
+
The updated right-hand-side vector.
|
|
162
|
+
counter : int
|
|
163
|
+
The updated row index.
|
|
164
|
+
equations : dict
|
|
165
|
+
Updated dictionary with equation labels.
|
|
166
|
+
"""
|
|
167
|
+
# Equality equation for mechanical and chemical exergy costs.
|
|
168
|
+
def set_equal(A, row, in_item, out_item, var):
|
|
169
|
+
if in_item["e_" + var] != 0 and out_item["e_" + var] != 0:
|
|
170
|
+
A[row, in_item["CostVar_index"][var]] = 1 / in_item["e_" + var]
|
|
171
|
+
A[row, out_item["CostVar_index"][var]] = -1 / out_item["e_" + var]
|
|
172
|
+
elif in_item["e_" + var] == 0 and out_item["e_" + var] != 0:
|
|
173
|
+
A[row, in_item["CostVar_index"][var]] = 1
|
|
174
|
+
elif in_item["e_" + var] != 0 and out_item["e_" + var] == 0:
|
|
175
|
+
A[row, out_item["CostVar_index"][var]] = 1
|
|
176
|
+
else:
|
|
177
|
+
A[row, in_item["CostVar_index"][var]] = 1
|
|
178
|
+
A[row, out_item["CostVar_index"][var]] = -1
|
|
179
|
+
|
|
180
|
+
# Thermal fuel rule on hot stream: c_T_in0 = c_T_out0.
|
|
181
|
+
def set_thermal_f_hot(A, row):
|
|
182
|
+
if self.inl[0]["e_T"] != 0 and self.outl[0]["e_T"] != 0:
|
|
183
|
+
A[row, self.inl[0]["CostVar_index"]["T"]] = 1 / self.inl[0]["E_T"]
|
|
184
|
+
A[row, self.outl[0]["CostVar_index"]["T"]] = -1 / self.outl[0]["E_T"]
|
|
185
|
+
elif self.inl[0]["e_T"] == 0 and self.outl[0]["e_T"] != 0:
|
|
186
|
+
A[row, self.inl[0]["CostVar_index"]["T"]] = 1
|
|
187
|
+
elif self.inl[0]["e_T"] != 0 and self.outl[0]["e_T"] == 0:
|
|
188
|
+
A[row, self.outl[0]["CostVar_index"]["T"]] = 1
|
|
189
|
+
else:
|
|
190
|
+
A[row, self.inl[0]["CostVar_index"]["T"]] = 1
|
|
191
|
+
A[row, self.outl[0]["CostVar_index"]["T"]] = -1
|
|
192
|
+
|
|
193
|
+
# Thermal fuel rule on cold stream: c_T_in1 = c_T_out1.
|
|
194
|
+
def set_thermal_f_cold(A, row):
|
|
195
|
+
if self.inl[1]["e_T"] != 0 and self.outl[1]["e_T"] != 0:
|
|
196
|
+
A[row, self.inl[1]["CostVar_index"]["T"]] = 1 / self.inl[1]["E_T"]
|
|
197
|
+
A[row, self.outl[1]["CostVar_index"]["T"]] = -1 / self.outl[1]["E_T"]
|
|
198
|
+
elif self.inl[1]["e_T"] == 0 and self.outl[1]["e_T"] != 0:
|
|
199
|
+
A[row, self.inl[1]["CostVar_index"]["T"]] = 1
|
|
200
|
+
elif self.inl[1]["e_T"] != 0 and self.outl[1]["e_T"] == 0:
|
|
201
|
+
A[row, self.outl[1]["CostVar_index"]["T"]] = 1
|
|
202
|
+
else:
|
|
203
|
+
A[row, self.inl[1]["CostVar_index"]["T"]] = 1
|
|
204
|
+
A[row, self.outl[1]["CostVar_index"]["T"]] = -1
|
|
205
|
+
|
|
206
|
+
# Thermal product rule: Equate the two outlet thermal costs (c_T_out0 = c_T_out1).
|
|
207
|
+
def set_thermal_p_rule(A, row):
|
|
208
|
+
if self.outl[0]["e_T"] != 0 and self.outl[1]["e_T"] != 0:
|
|
209
|
+
A[row, self.outl[0]["CostVar_index"]["T"]] = 1 / self.outl[0]["E_T"]
|
|
210
|
+
A[row, self.outl[1]["CostVar_index"]["T"]] = -1 / self.outl[1]["E_T"]
|
|
211
|
+
elif self.outl[0]["e_T"] == 0 and self.outl[1]["e_T"] != 0:
|
|
212
|
+
A[row, self.outl[0]["CostVar_index"]["T"]] = 1
|
|
213
|
+
elif self.outl[0]["e_T"] != 0 and self.outl[1]["e_T"] == 0:
|
|
214
|
+
A[row, self.outl[1]["CostVar_index"]["T"]] = 1
|
|
215
|
+
else:
|
|
216
|
+
A[row, self.outl[0]["CostVar_index"]["T"]] = 1
|
|
217
|
+
A[row, self.outl[1]["CostVar_index"]["T"]] = -1
|
|
218
|
+
|
|
219
|
+
# Determine the thermal case based on temperatures.
|
|
220
|
+
# Case 1: All temperatures > T0.
|
|
221
|
+
if all([c["T"] > T0 for c in list(self.inl.values()) + list(self.outl.values())]):
|
|
222
|
+
set_thermal_f_hot(A, counter + 0)
|
|
223
|
+
equations[counter] = f"aux_f_rule_hot_{self.name}"
|
|
224
|
+
# Case 2: All temperatures <= T0.
|
|
225
|
+
elif all([c["T"] <= T0 for c in self.inl + self.outl]):
|
|
226
|
+
set_thermal_f_cold(A, counter + 0)
|
|
227
|
+
equations[counter] = f"aux_f_rule_cold_{self.name}"
|
|
228
|
+
# Case 3: Mixed temperatures: inl[0]["T"] > T0 and outl[1]["T"] > T0, while outl[0]["T"] <= T0 and inl[1]["T"] <= T0.
|
|
229
|
+
elif (self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and
|
|
230
|
+
self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0):
|
|
231
|
+
set_thermal_p_rule(A, counter + 0)
|
|
232
|
+
equations[counter] = f"aux_p_rule_{self.name}"
|
|
233
|
+
# Case 4: Mixed temperatures: inl[0]["T"] > T0, inl[1]["T"] <= T0, and both outl[0]["T"] and outl[1]["T"] <= T0.
|
|
234
|
+
elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
|
|
235
|
+
self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0):
|
|
236
|
+
set_thermal_f_cold(A, counter + 0)
|
|
237
|
+
equations[counter] = f"aux_f_rule_cold_{self.name}"
|
|
238
|
+
# Case 5: Mixed temperatures: inl[0]["T"] > T0, inl[1]["T"] <= T0, and both outl[0]["T"] and outl[1]["T"] > T0.
|
|
239
|
+
elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
|
|
240
|
+
self.outl[0]["T"] > T0 and self.outl[1]["T"] > T0):
|
|
241
|
+
set_thermal_f_hot(A, counter + 0)
|
|
242
|
+
equations[counter] = f"aux_f_rule_hot_{self.name}"
|
|
243
|
+
# Case 6: Mixed temperatures (dissipative case): inl[0]["T"] > T0, inl[1]["T"] <= T0, outl[0]["T"] > T0, and outl[1]["T"] <= T0.
|
|
244
|
+
elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
|
|
245
|
+
self.outl[0]["T"] > T0 and self.outl[1]["T"] <= T0):
|
|
246
|
+
print("you shouldn't see this")
|
|
247
|
+
return
|
|
248
|
+
# Case 7: Default case.
|
|
249
|
+
else:
|
|
250
|
+
set_thermal_f_hot(A, counter + 0)
|
|
251
|
+
equations[counter] = f"aux_f_rule_hot_{self.name}"
|
|
252
|
+
|
|
253
|
+
# Mechanical equations (always added)
|
|
254
|
+
set_equal(A, counter + 1, self.inl[0], self.outl[0], "M")
|
|
255
|
+
set_equal(A, counter + 2, self.inl[1], self.outl[1], "M")
|
|
256
|
+
equations[counter + 1] = f"aux_equality_mech_{self.outl[0]['name']}"
|
|
257
|
+
equations[counter + 2] = f"aux_equality_mech_{self.outl[1]['name']}"
|
|
258
|
+
|
|
259
|
+
# Only add chemical auxiliary equations if chemical exergy is enabled.
|
|
260
|
+
if chemical_exergy_enabled:
|
|
261
|
+
set_equal(A, counter + 3, self.inl[0], self.outl[0], "CH")
|
|
262
|
+
set_equal(A, counter + 4, self.inl[1], self.outl[1], "CH")
|
|
263
|
+
equations[counter + 3] = f"aux_equality_chem_{self.outl[0]['name']}"
|
|
264
|
+
equations[counter + 4] = f"aux_equality_chem_{self.outl[1]['name']}"
|
|
265
|
+
num_aux_eqs = 5
|
|
266
|
+
else:
|
|
267
|
+
# Skip chemical auxiliary equations.
|
|
268
|
+
num_aux_eqs = 3
|
|
269
|
+
|
|
270
|
+
for i in range(num_aux_eqs):
|
|
271
|
+
b[counter + i] = 0
|
|
272
|
+
|
|
273
|
+
return A, b, counter + num_aux_eqs, equations
|
|
274
|
+
|
|
275
|
+
def exergoeconomic_balance(self, T0):
|
|
276
|
+
"""
|
|
277
|
+
Perform exergoeconomic balance calculations for the condenser.
|
|
278
|
+
|
|
279
|
+
This method calculates various exergoeconomic parameters including:
|
|
280
|
+
- Cost rates of product (C_P) and fuel (C_F)
|
|
281
|
+
- Specific cost of product (c_P) and fuel (c_F)
|
|
282
|
+
- Cost rate of exergy destruction (C_D)
|
|
283
|
+
- Relative cost difference (r)
|
|
284
|
+
- Exergoeconomic factor (f)
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
T0 : float
|
|
289
|
+
Ambient temperature
|
|
290
|
+
|
|
291
|
+
Notes
|
|
292
|
+
-----
|
|
293
|
+
The exergoeconomic balance considers thermal (T), chemical (CH),
|
|
294
|
+
and mechanical (M) exergy components for the inlet and outlet streams.
|
|
295
|
+
"""
|
|
296
|
+
if all([c["T"] > T0 for c in list(self.inl.values()) + list(self.outl.values())]):
|
|
297
|
+
self.C_P = self.outl[1]["C_T"] - self.inl[1]["C_T"]
|
|
298
|
+
self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (
|
|
299
|
+
self.inl[1]["C_M"] - self.outl[1]["C_M"])
|
|
300
|
+
elif all([c["T"] <= T0 for c in list(self.inl.values()) + list(self.outl.values())]):
|
|
301
|
+
self.C_P = self.outl[0]["C_T"] - self.inl[0]["C_T"]
|
|
302
|
+
self.C_F = self.inl[1]["C_PH"] - self.outl[1]["C_PH"] + (
|
|
303
|
+
self.inl[0]["C_M"] - self.outl[0]["C_M"])
|
|
304
|
+
elif (self.inl[0]["T"] > T0 and self.outl[1]["T"] > T0 and
|
|
305
|
+
self.outl[0]["T"] <= T0 and self.inl[1]["T"] <= T0):
|
|
306
|
+
self.C_P = self.outl[0]["C_T"] + self.outl[1]["C_T"]
|
|
307
|
+
self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (
|
|
308
|
+
self.outl[0]["C_M"] + self.outl[1]["C_M"])
|
|
309
|
+
elif (self.inl[0]["T"] > T0 and self.inl[1]["T"] <= T0 and
|
|
310
|
+
self.outl[0]["T"] <= T0 and self.outl[1]["T"] <= T0):
|
|
311
|
+
self.C_P = self.outl[0]["C_T"]
|
|
312
|
+
self.C_F = self.inl[0]["C_PH"] + self.inl[1]["C_PH"] - (
|
|
313
|
+
self.outl[1]["C_PH"] + self.outl[0]["C_M"])
|
|
314
|
+
else:
|
|
315
|
+
self.C_P = self.outl[1]["C_T"]
|
|
316
|
+
self.C_F = self.inl[0]["C_PH"] - self.outl[0]["C_PH"] + (
|
|
317
|
+
self.inl[1]["C_PH"] - self.outl[1]["C_M"])
|
|
318
|
+
|
|
319
|
+
self.c_F = self.C_F / self.E_F
|
|
320
|
+
self.c_P = self.C_P / self.E_P
|
|
321
|
+
self.C_D = self.c_F * self.E_D
|
|
322
|
+
self.r = (self.c_P - self.c_F) / self.c_F
|
|
323
|
+
self.f = self.Z_costs / (self.Z_costs + self.C_D)
|
|
@@ -0,0 +1,358 @@
|
|
|
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 SimpleHeatExchanger(Component):
|
|
11
|
+
r"""
|
|
12
|
+
Class for exergy analysis of simple heat exchangers.
|
|
13
|
+
|
|
14
|
+
This class performs exergy analysis calculations for simple heat exchangers with
|
|
15
|
+
one primary flow stream and heat transfer. The exergy product and fuel definitions
|
|
16
|
+
vary based on the direction of heat transfer and temperature levels relative to
|
|
17
|
+
ambient temperature.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
**kwargs : dict
|
|
22
|
+
Arbitrary keyword arguments passed to parent class.
|
|
23
|
+
Optional parameter 'dissipative' (bool) to indicate if the component
|
|
24
|
+
is considered fully dissipative.
|
|
25
|
+
|
|
26
|
+
Attributes
|
|
27
|
+
----------
|
|
28
|
+
E_F : float
|
|
29
|
+
Exergy fuel of the component :math:`\dot{E}_\mathrm{F}` in :math:`\mathrm{W}`.
|
|
30
|
+
E_P : float
|
|
31
|
+
Exergy product of the component :math:`\dot{E}_\mathrm{P}` in :math:`\mathrm{W}`.
|
|
32
|
+
E_D : float
|
|
33
|
+
Exergy destruction of the component :math:`\dot{E}_\mathrm{D}` in :math:`\mathrm{W}`.
|
|
34
|
+
epsilon : float
|
|
35
|
+
Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`.
|
|
36
|
+
inl : dict
|
|
37
|
+
Dictionary containing inlet stream data with temperature, mass flows,
|
|
38
|
+
enthalpies, and specific exergies.
|
|
39
|
+
outl : dict
|
|
40
|
+
Dictionary containing outlet stream data with temperature, mass flows,
|
|
41
|
+
enthalpies, and specific exergies.
|
|
42
|
+
|
|
43
|
+
Notes
|
|
44
|
+
-----
|
|
45
|
+
The exergy analysis considers three main cases based on heat transfer direction
|
|
46
|
+
and temperatures relative to ambient temperature :math:`T_0`:
|
|
47
|
+
|
|
48
|
+
Case 1 - **Heat Release** (:math:`\dot{Q} < 0`):
|
|
49
|
+
|
|
50
|
+
a) Both temperatures above ambient:
|
|
51
|
+
|
|
52
|
+
.. math::
|
|
53
|
+
\dot{E}_\mathrm{P} &= \dot{m} \cdot (e^\mathrm{T}_\mathrm{in} -
|
|
54
|
+
e^\mathrm{T}_\mathrm{out})\\
|
|
55
|
+
\dot{E}_\mathrm{F} &= \dot{m} \cdot (e^\mathrm{PH}_\mathrm{in} -
|
|
56
|
+
e^\mathrm{PH}_\mathrm{out})
|
|
57
|
+
|
|
58
|
+
b) Inlet above, outlet below ambient:
|
|
59
|
+
|
|
60
|
+
.. math::
|
|
61
|
+
\dot{E}_\mathrm{P} &= \dot{m}_\mathrm{out} \cdot e^\mathrm{T}_\mathrm{out}\\
|
|
62
|
+
\dot{E}_\mathrm{F} &= \dot{m}_\mathrm{in} \cdot e^\mathrm{T}_\mathrm{in} +
|
|
63
|
+
\dot{m}_\mathrm{out} \cdot e^\mathrm{T}_\mathrm{out} +
|
|
64
|
+
(\dot{m}_\mathrm{in} \cdot e^\mathrm{M}_\mathrm{in} -
|
|
65
|
+
\dot{m}_\mathrm{out} \cdot e^\mathrm{M}_\mathrm{out})
|
|
66
|
+
|
|
67
|
+
c) Both temperatures below ambient:
|
|
68
|
+
|
|
69
|
+
.. math::
|
|
70
|
+
\dot{E}_\mathrm{P} &= \dot{m}_\mathrm{out} \cdot
|
|
71
|
+
(e^\mathrm{T}_\mathrm{out} - e^\mathrm{T}_\mathrm{in})\\
|
|
72
|
+
\dot{E}_\mathrm{F} &= \dot{E}_\mathrm{P} + \dot{m}_\mathrm{in} \cdot
|
|
73
|
+
(e^\mathrm{M}_\mathrm{in} - e^\mathrm{M}_\mathrm{out})
|
|
74
|
+
|
|
75
|
+
Case 2 - **Heat Addition** (:math:`\dot{Q} > 0`):
|
|
76
|
+
|
|
77
|
+
a) Both temperatures above ambient:
|
|
78
|
+
|
|
79
|
+
.. math::
|
|
80
|
+
\dot{E}_\mathrm{P} &= \dot{m}_\mathrm{out} \cdot
|
|
81
|
+
(e^\mathrm{PH}_\mathrm{out} - e^\mathrm{PH}_\mathrm{in})\\
|
|
82
|
+
\dot{E}_\mathrm{F} &= \dot{m}_\mathrm{out} \cdot
|
|
83
|
+
(e^\mathrm{T}_\mathrm{out} - e^\mathrm{T}_\mathrm{in})
|
|
84
|
+
|
|
85
|
+
b) Inlet below, outlet above ambient:
|
|
86
|
+
|
|
87
|
+
.. math::
|
|
88
|
+
\dot{E}_\mathrm{P} &= \dot{m}_\mathrm{out} \cdot
|
|
89
|
+
(e^\mathrm{T}_\mathrm{out} + e^\mathrm{T}_\mathrm{in})\\
|
|
90
|
+
\dot{E}_\mathrm{F} &= \dot{m}_\mathrm{in} \cdot e^\mathrm{T}_\mathrm{in} +
|
|
91
|
+
(\dot{m}_\mathrm{in} \cdot e^\mathrm{M}_\mathrm{in} -
|
|
92
|
+
\dot{m}_\mathrm{out} \cdot e^\mathrm{M}_\mathrm{out})
|
|
93
|
+
|
|
94
|
+
c) Both temperatures below ambient:
|
|
95
|
+
|
|
96
|
+
.. math::
|
|
97
|
+
\dot{E}_\mathrm{P} &= \dot{m}_\mathrm{in} \cdot
|
|
98
|
+
(e^\mathrm{T}_\mathrm{in} - e^\mathrm{T}_\mathrm{out}) +
|
|
99
|
+
(\dot{m}_\mathrm{out} \cdot e^\mathrm{M}_\mathrm{out} -
|
|
100
|
+
\dot{m}_\mathrm{in} \cdot e^\mathrm{M}_\mathrm{in})\\
|
|
101
|
+
\dot{E}_\mathrm{F} &= \dot{m}_\mathrm{in} \cdot
|
|
102
|
+
(e^\mathrm{T}_\mathrm{in} - e^\mathrm{T}_\mathrm{out})
|
|
103
|
+
|
|
104
|
+
Case 3 - **Dissipative** (it is not possible to specify the exergy product :math:`\dot{E}_\mathrm{P}` for this component):
|
|
105
|
+
|
|
106
|
+
.. math::
|
|
107
|
+
\dot{E}_\mathrm{P} &= \mathrm{NaN}\\
|
|
108
|
+
\dot{E}_\mathrm{F} &= \dot{m}_\mathrm{in} \cdot
|
|
109
|
+
(e^\mathrm{PH}_\mathrm{in} - e^\mathrm{PH}_\mathrm{out})
|
|
110
|
+
|
|
111
|
+
For all cases, the exergy destruction is calculated as:
|
|
112
|
+
|
|
113
|
+
.. math::
|
|
114
|
+
\dot{E}_\mathrm{D} = \dot{E}_\mathrm{F} - \dot{E}_\mathrm{P}
|
|
115
|
+
|
|
116
|
+
Where:
|
|
117
|
+
- :math:`e^\mathrm{T}`: Thermal exergy
|
|
118
|
+
- :math:`e^\mathrm{PH}`: Physical exergy
|
|
119
|
+
- :math:`e^\mathrm{M}`: Mechanical exergy
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, **kwargs):
|
|
123
|
+
r"""Initialize simple heat exchanger component with given parameters."""
|
|
124
|
+
super().__init__(**kwargs)
|
|
125
|
+
|
|
126
|
+
def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None:
|
|
127
|
+
r"""
|
|
128
|
+
Calculate the exergy balance of the simple heat exchanger.
|
|
129
|
+
|
|
130
|
+
Performs exergy balance calculations considering both heat transfer direction
|
|
131
|
+
and temperature levels relative to ambient temperature.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
T0 : float
|
|
136
|
+
Ambient temperature in :math:`\mathrm{K}`.
|
|
137
|
+
p0 : float
|
|
138
|
+
Ambient pressure in :math:`\mathrm{Pa}`.
|
|
139
|
+
split_physical_exergy : bool
|
|
140
|
+
Flag indicating whether physical exergy is split into thermal and mechanical components.
|
|
141
|
+
|
|
142
|
+
Raises
|
|
143
|
+
------
|
|
144
|
+
ValueError
|
|
145
|
+
If the required inlet and outlet streams are not properly defined or
|
|
146
|
+
exceed the maximum allowed number.
|
|
147
|
+
"""
|
|
148
|
+
# Validate the number of inlets and outlets
|
|
149
|
+
if not hasattr(self, 'inl') or not hasattr(self, 'outl') or len(self.inl) < 1 or len(self.outl) < 1:
|
|
150
|
+
msg = "SimpleHeatExchanger requires at least one inlet and one outlet as well as one heat flow."
|
|
151
|
+
logging.error(msg)
|
|
152
|
+
raise ValueError(msg)
|
|
153
|
+
if len(self.inl) > 2 or len(self.outl) > 2:
|
|
154
|
+
msg = "SimpleHeatExchanger requires a maximum of two inlets and two outlets."
|
|
155
|
+
logging.error(msg)
|
|
156
|
+
raise ValueError(msg)
|
|
157
|
+
|
|
158
|
+
# Extract inlet and outlet streams
|
|
159
|
+
inlet = self.inl[0]
|
|
160
|
+
outlet = self.outl[0]
|
|
161
|
+
|
|
162
|
+
# Calculate heat transfer Q
|
|
163
|
+
Q = outlet['m'] * outlet['h'] - inlet['m'] * inlet['h']
|
|
164
|
+
|
|
165
|
+
# Initialize E_P and E_F
|
|
166
|
+
self.E_P = 0.0
|
|
167
|
+
self.E_F = 0.0
|
|
168
|
+
|
|
169
|
+
# Case 1: Heat is released (Q < 0)
|
|
170
|
+
if Q < 0:
|
|
171
|
+
if inlet['T'] >= T0 and outlet['T'] >= T0:
|
|
172
|
+
if split_physical_exergy:
|
|
173
|
+
self.E_P = np.nan if getattr(self, 'dissipative', False) else inlet['m'] * (inlet['e_T'] - outlet['e_T'])
|
|
174
|
+
else:
|
|
175
|
+
self.E_P = np.nan if getattr(self, 'dissipative', False) else inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
|
|
176
|
+
self.E_F = inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
|
|
177
|
+
|
|
178
|
+
elif inlet['T'] >= T0 and outlet['T'] < T0:
|
|
179
|
+
if split_physical_exergy:
|
|
180
|
+
self.E_P = outlet['m'] * outlet['e_T']
|
|
181
|
+
self.E_F = (inlet['m'] * inlet['e_T'] + outlet['m'] * outlet['e_T'] +
|
|
182
|
+
(inlet['m'] * inlet['e_M'] - outlet['m'] * outlet['e_M']))
|
|
183
|
+
else:
|
|
184
|
+
self.E_P = outlet['m'] * outlet['e_PH']
|
|
185
|
+
self.E_F = inlet['m'] * inlet['e_PH']
|
|
186
|
+
|
|
187
|
+
elif inlet['T'] <= T0 and outlet['T'] <= T0:
|
|
188
|
+
if split_physical_exergy:
|
|
189
|
+
self.E_P = outlet['m'] * (outlet['e_T'] - inlet['e_T'])
|
|
190
|
+
self.E_F = self.E_P + inlet['m'] * (inlet['e_M'] - outlet['m'] * outlet['e_M'])
|
|
191
|
+
else:
|
|
192
|
+
self.E_P = np.nan if getattr(self, 'dissipative', False) else \
|
|
193
|
+
outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
|
|
194
|
+
self.E_F = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
|
|
195
|
+
|
|
196
|
+
else:
|
|
197
|
+
# Unimplemented corner case
|
|
198
|
+
logging.warning(
|
|
199
|
+
"SimpleHeatExchanger: unimplemented case (Q < 0, T_in < T0 < T_out?)."
|
|
200
|
+
)
|
|
201
|
+
self.E_P = np.nan
|
|
202
|
+
self.E_F = np.nan
|
|
203
|
+
|
|
204
|
+
# Case 2: Heat is added (Q > 0)
|
|
205
|
+
elif Q > 0:
|
|
206
|
+
if inlet['T'] >= T0 and outlet['T'] >= T0:
|
|
207
|
+
if split_physical_exergy:
|
|
208
|
+
self.E_P = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
|
|
209
|
+
self.E_F = outlet['m'] * (outlet['e_T'] - inlet['e_T'])
|
|
210
|
+
else:
|
|
211
|
+
self.E_P = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
|
|
212
|
+
self.E_F = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
|
|
213
|
+
elif inlet['T'] < T0 and outlet['T'] > T0:
|
|
214
|
+
if split_physical_exergy:
|
|
215
|
+
self.E_P = outlet['m'] * (outlet['e_T'] + inlet['e_T'])
|
|
216
|
+
self.E_F = (inlet['m'] * inlet['e_T'] +
|
|
217
|
+
(inlet['m'] * inlet['e_M'] - outlet['m'] * outlet['e_M']))
|
|
218
|
+
else:
|
|
219
|
+
self.E_P = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
|
|
220
|
+
self.E_F = outlet['m'] * (outlet['e_PH'] - inlet['e_PH'])
|
|
221
|
+
|
|
222
|
+
elif inlet['T'] < T0 and outlet['T'] < T0:
|
|
223
|
+
if split_physical_exergy:
|
|
224
|
+
self.E_P = np.nan if getattr(self, 'dissipative', False) else \
|
|
225
|
+
inlet['m'] * (inlet['e_T'] - outlet['e_T']) + \
|
|
226
|
+
(outlet['m'] * outlet['e_M'] - inlet['m'] * inlet['e_M'])
|
|
227
|
+
self.E_F = inlet['m'] * (inlet['e_T'] - outlet['e_T'])
|
|
228
|
+
else:
|
|
229
|
+
self.E_P = np.nan if getattr(self, 'dissipative', False) else \
|
|
230
|
+
inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
|
|
231
|
+
self.E_F = inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
|
|
232
|
+
else:
|
|
233
|
+
logging.warning(
|
|
234
|
+
"SimpleHeatExchanger: unimplemented case (Q > 0, T_in > T0 > T_out?)."
|
|
235
|
+
)
|
|
236
|
+
self.E_P = np.nan
|
|
237
|
+
self.E_F = np.nan
|
|
238
|
+
|
|
239
|
+
# Case 3: Fully dissipative or Q == 0
|
|
240
|
+
else:
|
|
241
|
+
self.E_P = np.nan
|
|
242
|
+
self.E_F = inlet['m'] * (inlet['e_PH'] - outlet['e_PH'])
|
|
243
|
+
|
|
244
|
+
# Calculate exergy destruction
|
|
245
|
+
if np.isnan(self.E_P):
|
|
246
|
+
self.E_D = self.E_F
|
|
247
|
+
else:
|
|
248
|
+
self.E_D = self.E_F - self.E_P
|
|
249
|
+
|
|
250
|
+
# Calculate exergy efficiency
|
|
251
|
+
self.epsilon = self.calc_epsilon()
|
|
252
|
+
|
|
253
|
+
# Log the results
|
|
254
|
+
logging.info(
|
|
255
|
+
f"SimpleHeatExchanger exergy balance calculated: "
|
|
256
|
+
f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, "
|
|
257
|
+
f"Efficiency={self.epsilon:.2%}"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled):
|
|
262
|
+
"""
|
|
263
|
+
Auxiliary equations for the simple heat exchanger.
|
|
264
|
+
|
|
265
|
+
This function adds rows to the cost matrix A and the right-hand-side vector b to enforce
|
|
266
|
+
the following auxiliary cost relations:
|
|
267
|
+
|
|
268
|
+
(1) Thermal exergy cost equation:
|
|
269
|
+
- For heat release (T_in > T_out > T0): F-principle is applied
|
|
270
|
+
1/E_T_in * C_T_in - 1/E_T_out * C_T_out = 0
|
|
271
|
+
- For heat addition (T_in < T_out > T0): P-principle is applied
|
|
272
|
+
1/ΔE_T * (C_T_out - C_T_in) = 1/ΔE_M * (C_M_out - C_M_in)
|
|
273
|
+
|
|
274
|
+
(2) Mechanical exergy cost equation:
|
|
275
|
+
1/E_M_in * C_M_in - 1/E_M_out * C_M_out = 0
|
|
276
|
+
- F-principle: specific mechanical exergy costs equalized between inlet/outlet
|
|
277
|
+
|
|
278
|
+
(3) Chemical exergy cost equation (if enabled):
|
|
279
|
+
1/E_CH_in * C_CH_in - 1/E_CH_out * C_CH_out = 0
|
|
280
|
+
- F-principle: specific chemical exergy costs equalized between inlet/outlet
|
|
281
|
+
|
|
282
|
+
Parameters
|
|
283
|
+
----------
|
|
284
|
+
A : numpy.ndarray
|
|
285
|
+
The current cost matrix.
|
|
286
|
+
b : numpy.ndarray
|
|
287
|
+
The current right-hand-side vector.
|
|
288
|
+
counter : int
|
|
289
|
+
The current row index in the matrix.
|
|
290
|
+
T0 : float
|
|
291
|
+
Ambient temperature.
|
|
292
|
+
equations : dict
|
|
293
|
+
Dictionary for storing equation labels.
|
|
294
|
+
chemical_exergy_enabled : bool
|
|
295
|
+
Flag indicating whether chemical exergy auxiliary equations should be added.
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
A : numpy.ndarray
|
|
300
|
+
The updated cost matrix.
|
|
301
|
+
b : numpy.ndarray
|
|
302
|
+
The updated right-hand-side vector.
|
|
303
|
+
counter : int
|
|
304
|
+
The updated row index.
|
|
305
|
+
equations : dict
|
|
306
|
+
Updated dictionary with equation labels.
|
|
307
|
+
"""
|
|
308
|
+
# --- Thermal cost equation (row counter) ---
|
|
309
|
+
if self.inl[0]["T"] > T0 and self.outl[0]["T"] > T0:
|
|
310
|
+
if self.inl[0]["T"] > self.outl[0]["T"]:
|
|
311
|
+
# Heat is released (turbine-like behavior, f‑rule).
|
|
312
|
+
A[counter, self.inl[0]["CostVar_index"]["T"]] = (1 / self.inl[0]["e_T"]
|
|
313
|
+
if self.inl[0]["e_T"] != 0 else 1)
|
|
314
|
+
A[counter, self.outl[0]["CostVar_index"]["T"]] = (-1 / self.outl[0]["e_T"]
|
|
315
|
+
if self.outl[0]["e_T"] != 0 else -1)
|
|
316
|
+
equations[counter] = f"aux_f_rule_{self.name}"
|
|
317
|
+
elif self.inl[0]["T"] < self.outl[0]["T"]:
|
|
318
|
+
# Heat is injected (compressor-like behavior, p‑rule):
|
|
319
|
+
dET = self.outl[0]["e_T"] - self.inl[0]["e_T"]
|
|
320
|
+
dEM = self.outl[0]["e_M"] - self.inl[0]["e_M"]
|
|
321
|
+
if dET != 0 and dEM != 0:
|
|
322
|
+
A[counter, self.inl[0]["CostVar_index"]["T"]] = -1 / dET
|
|
323
|
+
A[counter, self.outl[0]["CostVar_index"]["T"]] = 1 / dET
|
|
324
|
+
A[counter, self.inl[0]["CostVar_index"]["M"]] = 1 / dEM
|
|
325
|
+
A[counter, self.outl[0]["CostVar_index"]["M"]] = -1 / dEM
|
|
326
|
+
equations[counter] = f"aux_p_rule_{self.name}"
|
|
327
|
+
else:
|
|
328
|
+
logging.warning("SimpleHeatExchanger: dET or dEM is zero; case not implemented.")
|
|
329
|
+
equations[counter] = "aux_unimpl_HEX"
|
|
330
|
+
else:
|
|
331
|
+
logging.warning("SimpleHeatExchanger: Inlet and outlet temperatures are equal; case not implemented.")
|
|
332
|
+
equations[counter] = "aux_unimpl_HEX"
|
|
333
|
+
else:
|
|
334
|
+
logging.warning("SimpleHeatExchanger: Cases with T_in or T_out below T0 are not implemented.")
|
|
335
|
+
equations[counter] = "aux_unimpl_HEX"
|
|
336
|
+
b[counter] = 0
|
|
337
|
+
|
|
338
|
+
# --- Mechanical cost equality (row counter+1) ---
|
|
339
|
+
A[counter+1, self.inl[0]["CostVar_index"]["M"]] = (1 / self.inl[0]["e_M"]
|
|
340
|
+
if self.inl[0]["e_M"] != 0 else 1)
|
|
341
|
+
A[counter+1, self.outl[0]["CostVar_index"]["M"]] = (-1 / self.outl[0]["e_M"]
|
|
342
|
+
if self.outl[0]["e_M"] != 0 else 1)
|
|
343
|
+
equations[counter+1] = f"aux_equality_mech_{self.outl[0]['name']}"
|
|
344
|
+
b[counter+1] = 0
|
|
345
|
+
|
|
346
|
+
# --- Chemical cost equality (conditionally added) ---
|
|
347
|
+
if chemical_exergy_enabled:
|
|
348
|
+
A[counter+2, self.inl[0]["CostVar_index"]["CH"]] = (1 / self.inl[0]["e_CH"]
|
|
349
|
+
if self.inl[0]["e_CH"] != 0 else 1)
|
|
350
|
+
A[counter+2, self.outl[0]["CostVar_index"]["CH"]] = (-1 / self.outl[0]["e_CH"]
|
|
351
|
+
if self.outl[0]["e_CH"] != 0 else 1)
|
|
352
|
+
equations[counter+2] = f"aux_equality_chem_{self.outl[0]['name']}"
|
|
353
|
+
b[counter+2] = 0
|
|
354
|
+
counter += 3
|
|
355
|
+
else:
|
|
356
|
+
counter += 2
|
|
357
|
+
|
|
358
|
+
return A, b, counter, equations
|