exerpy 0.0.2__py3-none-any.whl → 0.0.3__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 (39) hide show
  1. exerpy/__init__.py +2 -4
  2. exerpy/analyses.py +597 -297
  3. exerpy/components/__init__.py +3 -0
  4. exerpy/components/combustion/base.py +53 -35
  5. exerpy/components/component.py +8 -8
  6. exerpy/components/heat_exchanger/base.py +186 -119
  7. exerpy/components/heat_exchanger/condenser.py +96 -60
  8. exerpy/components/heat_exchanger/simple.py +237 -137
  9. exerpy/components/heat_exchanger/steam_generator.py +46 -41
  10. exerpy/components/helpers/cycle_closer.py +61 -34
  11. exerpy/components/helpers/power_bus.py +117 -0
  12. exerpy/components/nodes/deaerator.py +176 -58
  13. exerpy/components/nodes/drum.py +50 -39
  14. exerpy/components/nodes/flash_tank.py +218 -43
  15. exerpy/components/nodes/mixer.py +249 -69
  16. exerpy/components/nodes/splitter.py +173 -0
  17. exerpy/components/nodes/storage.py +130 -0
  18. exerpy/components/piping/valve.py +311 -115
  19. exerpy/components/power_machines/generator.py +105 -38
  20. exerpy/components/power_machines/motor.py +111 -39
  21. exerpy/components/turbomachinery/compressor.py +181 -63
  22. exerpy/components/turbomachinery/pump.py +182 -63
  23. exerpy/components/turbomachinery/turbine.py +182 -74
  24. exerpy/functions.py +388 -263
  25. exerpy/parser/from_aspen/aspen_config.py +57 -48
  26. exerpy/parser/from_aspen/aspen_parser.py +373 -280
  27. exerpy/parser/from_ebsilon/__init__.py +2 -2
  28. exerpy/parser/from_ebsilon/check_ebs_path.py +15 -19
  29. exerpy/parser/from_ebsilon/ebsilon_config.py +328 -226
  30. exerpy/parser/from_ebsilon/ebsilon_functions.py +205 -38
  31. exerpy/parser/from_ebsilon/ebsilon_parser.py +392 -255
  32. exerpy/parser/from_ebsilon/utils.py +16 -11
  33. exerpy/parser/from_tespy/tespy_config.py +32 -1
  34. exerpy/parser/from_tespy/tespy_parser.py +151 -0
  35. {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/METADATA +43 -2
  36. exerpy-0.0.3.dist-info/RECORD +48 -0
  37. exerpy-0.0.2.dist-info/RECORD +0 -44
  38. {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/WHEEL +0 -0
  39. {exerpy-0.0.2.dist-info → exerpy-0.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -2,8 +2,7 @@ import logging
2
2
 
3
3
  import numpy as np
4
4
 
5
- from exerpy.components.component import Component
6
- from exerpy.components.component import component_registry
5
+ from exerpy.components.component import Component, component_registry
7
6
 
8
7
 
9
8
  @component_registry
@@ -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['T']
129
- e_out_PH = first_outlet['e_PH']
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['T'] != T_out or outlet['e_PH'] != e_out_PH:
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
- m_out_total = sum(outlet.get('m', 0) for outlet in outlet_list)
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['T'] < T_out:
148
- if inlet['T'] >= T0:
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['m'] * (e_out_PH - inlet['e_PH'])
149
+ self.E_P += inlet["m"] * (e_out_PH - inlet["e_PH"])
151
150
  else: # inlet['T'] < T0
152
- self.E_P += inlet['m'] * e_out_PH
153
- self.E_F += inlet['m'] * inlet['e_PH']
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['m'] * (inlet['e_PH'] - e_out_PH)
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['m'] * inlet['e_PH']
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['T'] > T_out:
167
- if inlet['T'] >= T0:
168
- self.E_P += inlet['m'] * e_out_PH
169
- self.E_F += inlet['m'] * inlet['e_PH']
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['m'] * (e_out_PH - inlet['e_PH'])
170
+ self.E_P += inlet["m"] * (e_out_PH - inlet["e_PH"])
172
171
  else: # inlet['T'] <= T_out
173
- self.E_F += inlet['m'] * (inlet['e_PH'] - e_out_PH)
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 exergy balance calculated: "
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] = f"aux_mixing_chem_{self.outl[0]['name']}"
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
- equations[counter + chem_row] = f"aux_mixing_mech_{self.outl[0]['name']}"
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 calculations for the mixer.
281
-
282
- This method calculates various exergoeconomic parameters including:
283
- - Cost rates of product (C_P) and fuel (C_F)
284
- - Specific cost of product (c_P) and fuel (c_F)
285
- - Cost rate of exergy destruction (C_D)
286
- - Relative cost difference (r)
287
- - Exergoeconomic factor (f)
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 exergoeconomic balance considers thermal (T), chemical (CH),
297
- and mechanical (M) exergy components for the inlet and outlet streams.
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"] + i["C_CH"]
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 += - i["M"] * i["C_T"] * i["e_T"] + (
309
- i["C_T"] + i["C_M"] + i["C_CH"])
310
- self.C_F += (-self.outl[0]["C_M"] - self.outl[0]["C_CH"])
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"] + i["C_CH"]
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 += - i["M"] * i["C_T"] * i["e_T"] + (
323
- i["C_T"] + i["C_M"] + i["C_CH"])
324
- self.C_F += (-self.outl[0]["C_M"] - self.outl[0]["C_CH"])
325
- self.C_P = self.C_F + self.Z_costs # +1/num_serving_comps * C_diff
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