def calc_lhv(self): r""" Calculate the lower heating value of the combustion chambers fuel. Returns ------- val : float Lower heating value of the combustion chambers fuel. .. math:: LHV = \sum_{fuels} \left(-\frac{\sum_i {\Delta H_f^0}_i - \sum_j {\Delta H_f^0}_j } {M_{fuel}} \cdot x_{fuel} \right)\\ \forall i \in \text{reation products},\\ \forall j \in \text{reation educts},\\ \forall fuel \in \text{fuels},\\ \Delta H_f^0: \text{molar formation enthalpy},\\ x_{fuel}: \text{mass fraction of fuel in fuel mixture} """ hf = {} hf['hydrogen'] = 0 hf['methane'] = -74.85 hf['ethane'] = -84.68 hf['propane'] = -103.8 hf['butane'] = -124.51 hf['O2'] = 0 hf['CO2'] = -393.5 # water (gaseous) hf['H2O'] = -241.8 lhv = 0 for f, x in self.fuel.val.items(): molar_masses[f] = CP.PropsSI('M', f) fl = set(list(hf.keys())).intersection( set([a.replace(' ', '') for a in CP.get_aliases(f)])) if len(fl) == 0: continue if list(fl)[0] in self.fuels(): structure = fluid_structure(f) n = {} for el in ['C', 'H', 'O']: if el in structure.keys(): n[el] = structure[el] else: n[el] = 0 lhv += (-(n['H'] / 2 * hf['H2O'] + n['C'] * hf['CO2'] - ((n['C'] + n['H'] / 4) * hf['O2'] + hf[list(fl)[0]])) / molar_masses[f] * 1000) * x return lhv
def fluid_header(Fluid): aliases = CP.get_aliases(str(Fluid)) aliases = ', '.join(['``' + a.strip() + '``' for a in aliases]) BTC = BibTeXerClass() EOSkey = CP.get_BibTeXKey(Fluid, "EOS") CP0key = CP.get_BibTeXKey(Fluid, "CP0") SURFACE_TENSIONkey = CP.get_BibTeXKey(Fluid, "SURFACE_TENSION") VISCOSITYkey = CP.get_BibTeXKey(Fluid, "VISCOSITY") CONDUCTIVITYkey = CP.get_BibTeXKey(Fluid, "CONDUCTIVITY") ECS_LENNARD_JONESkey = CP.get_BibTeXKey(Fluid, "ECS_LENNARD_JONES") ECS_FITSkey = CP.get_BibTeXKey(Fluid, "ECS_FITS") BibInfo = '' if EOSkey: BibInfo += '**Equation of State**: ' + BTC.entry2rst(EOSkey) + '\n\n' if CP0key: BibInfo += '**Ideal-Gas Specific Heat**: ' + BTC.entry2rst( CP0key) + '\n\n' if SURFACE_TENSIONkey: BibInfo += '**Surface Tension**: ' + BTC.entry2rst( SURFACE_TENSIONkey) + '\n\n' if VISCOSITYkey: BibInfo += '**Viscosity**: ' + BTC.entry2rst(VISCOSITYkey) + '\n\n' if CONDUCTIVITYkey: BibInfo += '**Conductivity**: ' + BTC.entry2rst( CONDUCTIVITYkey) + '\n\n' if ECS_LENNARD_JONESkey: BibInfo += '**Lennard-Jones Parameters for ECS**: ' + BTC.entry2rst( ECS_LENNARD_JONESkey) + '\n\n' if ECS_FITSkey: BibInfo += '**ECS Correction Fit**: ' + BTC.entry2rst( ECS_FITSkey) + '\n\n' return textwrap.dedent(""" ******************** {Fluid:s} ******************** Aliases ================================================================================ {Aliases:s} Bibliographic Information ========================= {Reference:s} """.format( Fluid=Fluid, Aliases=aliases, Reference=BibInfo, ))
def fluid_header(Fluid): aliases = CP.get_aliases(str(Fluid)) aliases = ', '.join(['``'+a.strip()+'``' for a in aliases]) BTC = BibTeXerClass() EOSkey = CP.get_BibTeXKey(Fluid, "EOS") CP0key = CP.get_BibTeXKey(Fluid, "CP0") SURFACE_TENSIONkey = CP.get_BibTeXKey(Fluid, "SURFACE_TENSION") VISCOSITYkey = CP.get_BibTeXKey(Fluid, "VISCOSITY") CONDUCTIVITYkey = CP.get_BibTeXKey(Fluid, "CONDUCTIVITY") ECS_LENNARD_JONESkey = CP.get_BibTeXKey(Fluid, "ECS_LENNARD_JONES") ECS_FITSkey = CP.get_BibTeXKey(Fluid, "ECS_FITS") BibInfo = '' if EOSkey: BibInfo += '**Equation of State**: ' + BTC.entry2rst(EOSkey) + '\n\n' if CP0key: BibInfo += '**Ideal-Gas Specific Heat**: ' + BTC.entry2rst(CP0key) + '\n\n' if SURFACE_TENSIONkey: BibInfo += '**Surface Tension**: ' + BTC.entry2rst(SURFACE_TENSIONkey) + '\n\n' if VISCOSITYkey: BibInfo += '**Viscosity**: ' + BTC.entry2rst(VISCOSITYkey) + '\n\n' if CONDUCTIVITYkey: BibInfo += '**Conductivity**: ' + BTC.entry2rst(CONDUCTIVITYkey) + '\n\n' if ECS_LENNARD_JONESkey: BibInfo += '**Lennard-Jones Parameters for ECS**: ' + BTC.entry2rst(ECS_LENNARD_JONESkey) + '\n\n' if ECS_FITSkey: BibInfo += '**ECS Correction Fit**: ' + BTC.entry2rst(ECS_FITSkey) + '\n\n' return textwrap.dedent( """ ******************** {Fluid:s} ******************** Aliases ================================================================================ {Aliases:s} Bibliographic Information ========================= {Reference:s} """.format(Fluid=Fluid, Aliases = aliases, Reference = BibInfo, ) )
def calc_lhv(self, f): r""" Calculate the lower heating value of the combustion chamber's fuel. - Source for fluids O2, H2O and CO2: :cite:`CODATA1989` - Source for all other fluids: :cite:`CRCHandbook2021` Parameters ---------- f : str Alias of the fuel. Returns ------- val : float Lower heating value of the combustion chambers fuel. .. math:: LHV = -\frac{\sum_i {\Delta H_f^0}_i - \sum_j {\Delta H_f^0}_j } {M_{fuel}}\\ \forall i \in \text{reation products},\\ \forall j \in \text{reation educts},\\ \Delta H_f^0: \text{molar formation enthalpy} """ hf = {} hf['hydrogen'] = 0 hf['methane'] = -74.6 hf['ethane'] = -84.0 hf['propane'] = -103.8 hf['butane'] = -125.7 hf['nDodecane'] = -289.4 hf[self.o2] = 0 hf[self.co2] = -393.51 # water (gaseous) hf[self.h2o] = -241.826 key = set(list(hf.keys())).intersection( set([a.replace(' ', '') for a in CP.get_aliases(f)])) val = (-(self.fuels[f]['H'] / 2 * hf[self.h2o] + self.fuels[f]['C'] * hf[self.co2] - ((self.fuels[f]['C'] + self.fuels[f]['H'] / 4) * hf[self.o2] + hf[list(key)[0]])) / molar_masses[f] * 1000) return val
def comp_init(self, nw): if not self.P.is_set: self.set_attr(P='var') msg = ('The power output of cogeneration units must be set! ' 'We are adding the power output of component ' + self.label + ' as custom variable of the system.') logging.info(msg) component.comp_init(self, nw) o2 = [x for x in nw.fluids if x in [ a.replace(' ', '') for a in CP.get_aliases('O2')]] if len(o2) == 0: msg = ('Missing oxygen in network fluids, component ' + self.label + ' of type ' + self.component() + ' requires oxygen in network fluids.') logging.error(msg) raise ValueError(msg) else: self.o2 = o2[0] h2o = [x for x in nw.fluids if x in [ a.replace(' ', '') for a in CP.get_aliases('H2O')]] if len(h2o) == 0: msg = ('Missing water in network fluids, component ' + self.label + ' of type ' + self.component() + ' requires water in network fluids.') logging.error(msg) raise ValueError(msg) else: self.h2o = h2o[0] h2 = [x for x in nw.fluids if x in [ a.replace(' ', '') for a in CP.get_aliases('H2')]] if len(h2) == 0: msg = ('Missing hydrogen in network fluids, component ' + self.label + ' of type ' + self.component() + ' requires hydrogen in network fluids.') logging.error(msg) raise ValueError(msg) else: self.h2 = h2[0] self.e0 = self.calc_e0() # number of mandatroy equations for # cooling loop fluids: num_fl # fluid composition at reactor inlets/outlets: 3 * num_fl # mass flow balances: 3 # pressure: 2 # energy balance: 1 # reactor outlet temperatures: 1 self.num_eq = self.num_nw_fluids * 4 + 7 for var in [self.e, self.eta, self.eta_char, self.Q, self.pr_c, self.zeta]: if var.is_set is True: self.num_eq += 1 self.mat_deriv = np.zeros(( self.num_eq, self.num_i + self.num_o + self.num_vars, self.num_nw_vars)) self.vec_res = np.zeros(self.num_eq) pos = self.num_nw_fluids * 4 self.mat_deriv[0:pos] = self.fluid_deriv() self.mat_deriv[pos:pos + 3] = self.mass_flow_deriv() self.mat_deriv[pos + 3:pos + 5] = self.pressure_deriv()
def stoich_flue_gas(self, nw): r""" Calculate the fluid composition of the stoichiometric flue gas. - uses one mole of fuel as reference quantity and :math:`\lambda=1` for stoichiometric flue gas calculation (no oxygen in flue gas) - calculate molar quantities of (reactive) fuel components to determine water and carbondioxide mass fraction in flue gas - calculate required molar quantity for oxygen and required fresh air mass - calculate residual mass fractions for non reactive components of fresh air in the flue gas - calculate flue gas fluid composition - generate custom fluid porperties Reactive components in fuel .. math:: m_{fuel} = \frac{1}{M_{fuel}}\\ m_{CO_2} = \sum_{i} \frac{x_{i} \cdot m_{fuel} \cdot num_{C,i} \cdot M_{CO_{2}}}{M_{i}}\\ m_{H_{2}O} = \sum_{i} \frac{x_{i} \cdot m_{fuel} \cdot num_{H,i} \cdot M_{H_{2}O}}{2 \cdot M_{i}}\\ \forall i \in \text{fuels in fuel vector},\\ num = \text{number of atoms in molecule} Other components of fuel vector .. math:: m_{fg,j} = x_{j} \cdot m_{fuel}\\ \forall j \in \text{non fuels in fuel vecotr, e.g. } CO_2,\\ m_{fg,j} = \text{mass of fluid component j in flue gas} Non-reactive components in air .. math:: n_{O_2} = \left( \frac{m_{CO_2}}{M_{CO_2}} + \frac{m_{H_{2}O}} {0,5 \cdot M_{H_{2}O}} \right) \cdot \lambda,\\ n_{O_2} = \text{mol of oxygen required}\\ m_{air} = \frac{n_{O_2} \cdot M_{O_2}}{x_{O_{2}, air}},\\ m_{air} = \text{required total air mass}\\ m_{fg,j} = x_{j, air} \cdot m_{air}\\ m_{fg, O_2} = 0,\\ m_{fg,j} = \text{mass of fluid component j in flue gas} Flue gas composition .. math:: x_{fg,j} = \frac{m_{fg, j}}{m_{air} + m_{fuel}} Parameters ---------- nw : tespy.networks.network.Network TESPy network to generate stoichiometric flue gas for. """ lamb = 1 n_fuel = 1 m_fuel = 1 / molar_mass_flow(self.fuel.val) * n_fuel m_fuel_fg = m_fuel m_co2 = 0 m_h2o = 0 molar_masses[self.h2o] = CP.PropsSI('M', self.h2o) molar_masses[self.co2] = CP.PropsSI('M', self.co2) molar_masses[self.o2] = CP.PropsSI('M', self.o2) self.fg = {} self.fg[self.co2] = 0 self.fg[self.h2o] = 0 for f, x in self.fuel.val.items(): fl = set(list(self.fuels())).intersection( set([a.replace(' ', '') for a in CP.get_aliases(f)])) if len(fl) == 0: if f in self.fg.keys(): self.fg[f] += x * m_fuel else: self.fg[f] = x * m_fuel else: n_fluid = x * m_fuel / molar_masses[f] m_fuel_fg -= n_fluid * molar_masses[f] structure = fluid_structure(f) n = {} for el in ['C', 'H', 'O']: if el in structure.keys(): n[el] = structure[el] else: n[el] = 0 m_co2 += n_fluid * n['C'] * molar_masses[self.co2] m_h2o += n_fluid * n['H'] / 2 * molar_masses[self.h2o] self.fg[self.co2] += m_co2 self.fg[self.h2o] += m_h2o n_o2 = (m_co2 / molar_masses[self.co2] + 0.5 * m_h2o / molar_masses[self.h2o]) * lamb m_air = n_o2 * molar_masses[self.o2] / self.air.val[self.o2] self.air_min = m_air / m_fuel for f, x in self.air.val.items(): if f != self.o2: if f in self.fg.keys(): self.fg[f] += m_air * x else: self.fg[f] = m_air * x m_fg = m_fuel + m_air for f in self.fg.keys(): self.fg[f] /= m_fg if not self.path.is_set: self.path.val = None TESPyFluid( self.fuel_alias.val, self.fuel.val, [1000, nw.p_range_SI[1]], path=self.path.val) TESPyFluid( self.fuel_alias.val + '_fg', self.fg, [1000, nw.p_range_SI[1]], path=self.path.val) msg = ( 'Generated lookup table for ' + self.fuel_alias.val + ' and for ' 'stoichiometric flue gas at component ' + self.label + '.') logging.debug(msg) if self.air_alias.val not in ['Air', 'air']: TESPyFluid( self.air_alias.val, self.air.val, [1000, nw.p_range_SI[1]], path=self.path.val) msg = ('Generated lookup table for ' + self.air_alias.val + ' at stoichiometric combustion chamber ' + self.label + '.') else: msg = ('Using CoolProp air at stoichiometric combustion chamber ' + self.label + '.') logging.debug(msg)
def comp_init(self, nw): if not self.fuel.is_set or not isinstance(self.fuel.val, dict): msg = ('You must specify the fuel composition for stoichimetric ' 'combustion chamber ' + self.label + '.') logging.error(msg) raise TESPyComponentError(msg) if not self.fuel_alias.is_set: msg = ('You must specify a fuel alias for stoichimetric ' 'combustion chamber ' + self.label + '.') logging.error(msg) raise TESPyComponentError(msg) if not self.air.is_set or not isinstance(self.air.val, dict): msg = ('You must specify the air composition for stoichimetric ' 'combustion chamber ' + self.label + '.') logging.error(msg) raise TESPyComponentError(msg) if not self.air_alias.is_set: msg = ('You must specify an air alias for stoichimetric ' 'combustion chamber ' + self.label + '.') logging.error(msg) raise TESPyComponentError(msg) Component.comp_init(self, nw) # adjust the names for required fluids according to naming in the # network air for f in list(self.air.val.keys()): alias = [x for x in self.nw_fluids if x in [ a.replace(' ', '') for a in CP.get_aliases(f)]] if len(alias) > 0: self.air.val[alias[0]] = self.air.val.pop(f) # fuel for f in list(self.fuel.val.keys()): alias = [x for x in self.air.val.keys() if x in [ a.replace(' ', '') for a in CP.get_aliases(f)]] if len(alias) > 0: self.fuel.val[alias[0]] = self.fuel.val.pop(f) # list of all fluids of air and fuel fluids = list(self.air.val.keys()) + list(self.fuel.val.keys()) # oxygen alias = [x for x in fluids if x in [ a.replace(' ', '') for a in CP.get_aliases('O2')]] if len(alias) == 0: msg = 'Oxygen missing in input fluids.' logging.error(msg) raise TESPyComponentError(msg) else: self.o2 = alias[0] # carbondioxide self.co2 = [x for x in self.nw_fluids if x in [ a.replace(' ', '') for a in CP.get_aliases('CO2')]] if len(self.co2) == 0: self.co2 = 'CO2' else: self.co2 = self.co2[0] # water self.h2o = [x for x in self.nw_fluids if x in [ a.replace(' ', '') for a in CP.get_aliases('H2O')]] if len(self.h2o) == 0: self.h2o = 'H2O' else: self.h2o = self.h2o[0] # calculate lower heating value of specified fuel self.lhv = self.calc_lhv() msg = ('Combustion chamber fuel (' + self.fuel_alias.val + ') LHV is ' + str(self.lhv) + ' for component ' + self.label + '.') logging.debug(msg) # generate fluid properties for stoichiometric flue gas self.stoich_flue_gas(nw)