Source code for exerpy.components.combustion.base

import logging

from exerpy.components.component import Component, component_registry


[docs] @component_registry class CombustionChamber(Component): r""" Class for exergy and exergoeconomic analysis of combustion chambers. This class performs exergy and exergoeconomic analysis calculations for combustion chambers, considering both thermal and mechanical exergy flows, as well as chemical exergy flows. The exergy product is defined based on thermal and mechanical exergy differences, while the exergy fuel is based on chemical exergy differences. Attributes ---------- E_F : float Exergy fuel of the component :math:`\dot{E}_\mathrm{F}` in :math:`\mathrm{W}`. E_P : float Exergy product of the component :math:`\dot{E}_\mathrm{P}` in :math:`\mathrm{W}`. E_D : float Exergy destruction of the component :math:`\dot{E}_\mathrm{D}` in :math:`\mathrm{W}`. epsilon : float Exergetic efficiency of the component :math:`\varepsilon` in :math:`-`. inl : dict Dictionary containing inlet stream data with mass flows and specific exergies. outl : dict Dictionary containing outlet stream data with mass flows and specific exergies. Z_costs : float Investment cost rate of the component in currency/h. C_P : float Cost of product stream :math:`\dot{C}_P` in currency/h. C_F : float Cost of fuel stream :math:`\dot{C}_F` in currency/h. C_D : float Cost of exergy destruction :math:`\dot{C}_D` in currency/h. c_P : float Specific cost of product stream (currency per unit exergy). c_F : float Specific cost of fuel stream (currency per unit exergy). r : float Relative cost difference, :math:`(c_P - c_F)/c_F`. f : float Exergoeconomic factor, :math:`\dot{Z}/(\dot{Z} + \dot{C}_D)`. Ex_C_col : dict Custom cost coefficients collection passed via `kwargs`. Notes ----- This component requires the calculation of both physical and chemical exergy. """ def __init__(self, **kwargs): r""" Initialize the combustion chamber component. Parameters ---------- **kwargs : dict Arbitrary keyword arguments. Recognized keys: - Ex_C_col (dict): custom cost coefficients, default {} - Z_costs (float): investment cost rate (currency/h), default 0.0 """ super().__init__(**kwargs) # Initialize additional attributes if necessary self.Ex_C_col = kwargs.get("Ex_C_col", {}) self.Z_costs = kwargs.get("Z_costs", 0.0) # Cost rate in currency/h
[docs] def calc_exergy_balance(self, T0: float, p0: float, split_physical_exergy) -> None: r""" Compute the exergy balance of the combustion chamber. .. math:: \dot{E}_P = \dot{E}^{\mathrm{PH}}_{\text{out}} - \bigl(\dot{E}^{\mathrm{PH}}_{\text{in},1} + \dot{E}^{\mathrm{PH}}_{\text{in},2}\bigr) .. math:: \dot{E}_F = \dot{E}^{\mathrm{CH}}_{\text{in},1} + \dot{E}^{\mathrm{CH}}_{\text{in},2} - \dot{E}^{\mathrm{CH}}_{\text{out}} Parameters ---------- T0 : float Ambient temperature (K). p0 : float Ambient pressure (Pa). split_physical_exergy : bool Whether to split thermal and mechanical exergy. Raises ------ ValueError If fewer than two inlets or no outlets are defined. """ # Check for necessary inlet and outlet data if not hasattr(self, "inl") or not hasattr(self, "outl") or len(self.inl) < 2 or len(self.outl) < 1: msg = "CombustionChamber requires at least two inlets (air and fuel) and one outlet (exhaust)." logging.error(msg) raise ValueError(msg) # Calculate total physical exergy of outlets total_E_P_out = sum(outlet["m"] * outlet["e_PH"] for outlet in self.outl.values()) # Calculate total physical exergy of inlets total_E_P_in = sum(inlet["m"] * inlet["e_PH"] for inlet in self.inl.values()) # Exergy Product (E_P) self.E_P = total_E_P_out - total_E_P_in # Calculate total chemical exergy of inlets total_E_F_in = sum(inlet["m"] * inlet["e_CH"] for inlet in self.inl.values()) # Calculate total chemical exergy of outlets total_E_F_out = sum(outlet["m"] * outlet["e_CH"] for outlet in self.outl.values()) # Exergy Fuel (E_F) self.E_F = total_E_F_in - total_E_F_out # Exergy destruction (difference between exergy fuel and exergy product) self.E_D = self.E_F - self.E_P # Exergetic efficiency (epsilon) self.epsilon = self.calc_epsilon() # Log the results logging.info( f"Exergy balance of CombustionChamber {self.name} calculated: " f"E_P={self.E_P:.2f}, E_F={self.E_F:.2f}, E_D={self.E_D:.2f}, " f"Efficiency={self.epsilon:.2%}" )
[docs] def aux_eqs(self, A, b, counter, T0, equations, chemical_exergy_enabled): r""" Add auxiliary cost equations for the combustion chamber. This method appends two rows to the cost matrix to enforce: 1. F rule for mechanical exergy: .. math:: -\frac{1}{\dot{E}^{\mathrm{M}}_{\text{out}}}\,\dot{C}^{\mathrm{M}}_{\text{out}} + \frac{\dot m_{1}}{\dot m_{1} + \dot m_{2}} \frac{1}{\dot{E}^{\mathrm{M}}_{\text{in},1}}\,\dot{C}^{\mathrm{M}}_{\text{in},1} + \frac{\dot m_{2}}{\dot m_{1} + \dot m_{2}} \frac{1}{\dot{E}^{\mathrm{M}}_{\text{in},2}}\,\dot{C}^{\mathrm{M}}_{\text{in},2} = 0 2. F rule for chemical exergy: .. math:: -\frac{1}{\dot{E}^{\mathrm{CH}}_{\text{out}}}\,\dot{C}^{\mathrm{CH}}_{\text{out}}{} + \frac{\dot m_{1}}{\dot m_{1} + \dot m_{2}} \frac{1}{\dot{E}^{\mathrm{CH}}_{\text{in},1}}\,\dot{C}^{\mathrm{CH}}_{\text{in},1} + \frac{\dot m_{2}}{\dot m_{1} + \dot m_{2}} \frac{1}{\dot{E}^{\mathrm{CH}}_{\text{in},2}}\,\dot{C}^{\mathrm{CH}}_{\text{in},2} = 0 Parameters ---------- A : numpy.ndarray Current cost matrix. b : numpy.ndarray Current RHS vector. counter : int Starting row index. T0 : float Ambient temperature. equations : dict or list Structure for equation labels. chemical_exergy_enabled : bool Must be True to include chemical exergy mixing. Returns ------- A : numpy.ndarray Updated cost matrix. b : numpy.ndarray Updated RHS vector. counter : int Updated row index. equations : dict or list Updated labels. Raises ------ ValueError If chemical_exergy_enabled is False. """ # For the combustion chamber, chemical exergy is mandatory. if not chemical_exergy_enabled: raise ValueError( "Chemical exergy is mandatory for the combustion chamber!", "Please make sure that your exergy analysis consider the chemical exergy.", ) # Convert inlet and outlet dictionaries to lists for ordered access. inlets = list(self.inl.values()) outlets = list(self.outl.values()) # --- Mechanical cost auxiliary equation --- if outlets[0]["e_M"] != 0 and inlets[0]["e_M"] != 0 and inlets[1]["e_M"] != 0: A[counter, outlets[0]["CostVar_index"]["M"]] = -1 / outlets[0]["E_M"] A[counter, inlets[0]["CostVar_index"]["M"]] = ( (1 / inlets[0]["E_M"]) * inlets[0]["m"] / (inlets[0]["m"] + inlets[1]["m"]) ) A[counter, inlets[1]["CostVar_index"]["M"]] = ( (1 / inlets[1]["E_M"]) * inlets[1]["m"] / (inlets[0]["m"] + inlets[1]["m"]) ) else: # pressure can only decrease in the combustion chamber (case with p_inlet = p0 and p_outlet < p0 NOT considered) A[counter, outlets[0]["CostVar_index"]["M"]] = 1 equations[counter] = f"aux_mixing_mech_{self.outl[0]['name']}" equations[counter] = { "kind": "aux_mixing", "objects": [self.name, self.inl[0]["name"], self.inl[1]["name"], self.outl[0]["name"]], "property": "c_M", } # --- Chemical cost auxiliary equation --- if outlets[0]["e_CH"] != 0 and inlets[0]["e_CH"] != 0 and inlets[1]["e_CH"] != 0: A[counter + 1, outlets[0]["CostVar_index"]["CH"]] = -1 / outlets[0]["E_CH"] A[counter + 1, inlets[0]["CostVar_index"]["CH"]] = ( (1 / inlets[0]["E_CH"]) * inlets[0]["m"] / (inlets[0]["m"] + inlets[1]["m"]) ) A[counter + 1, inlets[1]["CostVar_index"]["CH"]] = ( (1 / inlets[1]["E_CH"]) * inlets[1]["m"] / (inlets[0]["m"] + inlets[1]["m"]) ) elif inlets[0]["e_CH"] == 0: A[counter + 1, inlets[0]["CostVar_index"]["CH"]] = 1 elif inlets[1]["e_CH"] == 0: A[counter + 1, inlets[1]["CostVar_index"]["CH"]] = 1 equations[counter + 1] = { "kind": "aux_mixing", "objects": [self.name, self.inl[0]["name"], self.inl[1]["name"], self.outl[0]["name"]], "property": "c_CH", } # Set the right-hand side entries to zero. b[counter] = 0 b[counter + 1] = 0 return [A, b, counter + 2, equations]
[docs] def exergoeconomic_balance(self, T0, chemical_exergy_enabled=False): r""" Perform exergoeconomic cost balance for the combustion chamber. .. math:: \dot{C}^{\mathrm{PH}}_{\text{in},1} + \dot{C}^{\mathrm{CH}}_{\text{in},1} + \dot{C}^{\mathrm{CH}}_{\text{in},2} + \dot{C}^{\mathrm{PH}}_{\text{in},2} - \dot{C}^{\mathrm{PH}}_{\text{out}} - \dot{C}^{\mathrm{CH}}_{\text{out}} + \dot{Z} = 0 This method computes cost coefficients and ratios: .. math:: \dot{C}_P = \dot{C}^{\mathrm{T}}_{\text{out}} - \bigl(\dot{C}^{\mathrm{T}}_{\text{in},1} + \dot{C}^{\mathrm{T}}_{\text{in},2}\bigr) .. math:: \dot{C}_F = \dot{C}^{\mathrm{CH}}_{\text{in},1} + \dot{C}^{\mathrm{CH}}_{\text{in},2} - \dot{C}^{\mathrm{CH}}_{\text{out}} + \dot{C}^{\mathrm{M}}_{\text{in},1} + \dot{C}^{\mathrm{M}}_{\text{in},2} - \dot{C}^{\mathrm{M}}_{\text{out}} Parameters ---------- T0 : float Ambient temperature (K). chemical_exergy_enabled : bool, optional If True, chemical exergy is considered in the calculations. """ self.C_P = self.outl[0]["C_T"] - (self.inl[0]["C_T"] + self.inl[1]["C_T"]) self.C_F = ( self.inl[0]["C_CH"] + self.inl[1]["C_CH"] - self.outl[0]["C_CH"] + self.inl[0]["C_M"] + self.inl[1]["C_M"] - self.outl[0]["C_M"] ) self.c_F = self.C_F / self.E_F self.c_P = self.C_P / self.E_P self.C_D = self.c_F * self.E_D self.r = (self.c_P - self.c_F) / self.c_F self.f = self.Z_costs / (self.Z_costs + self.C_D)