Source code for libICEpost.src.thermophysicalModels.specie.reactions.functions

#####################################################################
#                                 DOC                               #
#####################################################################

"""
@author: F. Ramognino       <federico.ramognino@polimi.it>
Last update:        10/06/2024
"""

#####################################################################
#                               IMPORT                              #
#####################################################################

import os
from typing import Iterable, Any

import yaml

from libICEpost.src.base.Functions.typeChecking import checkType

from libICEpost.src.thermophysicalModels.specie.specie.Mixture import Mixture, mixtureBlend
from libICEpost.src.thermophysicalModels.specie.specie.Molecule import Molecule
from libICEpost.src.thermophysicalModels.specie.reactions.Reaction.StoichiometricReaction import StoichiometricReaction
from libICEpost.src.thermophysicalModels.specie.reactions.ReactionModel.Stoichiometry import Stoichiometry
from libICEpost.src.thermophysicalModels.thermoModels.thermoMixture.ThermoMixture import ThermoMixture

from io import StringIO
import cantera as ct

from libICEpost.Database import database

#TODO caching (memoization package handles also unhashable types)
from functools import lru_cache
from libICEpost.GLOBALS import __CACHE_SIZE__

#############################################################################
#                              MAIN FUNCTIONS                               #
#############################################################################
[docs] @lru_cache(maxsize=__CACHE_SIZE__) def computeAlphaSt(air:Mixture, fuel:Mixture, *, oxidizer:Molecule=database.chemistry.specie.Molecules.O2) -> float: """ Compute the stoichiometric air-fuel ratio given air and fuel mixture compositions. Args: air (Mixture): The air mixture composition fuel (Mixture): The fuel mixture composition oxidizer (Molecule, optional): The oxidizing molecule. Defaults to database.chemistry.specie.Molecules.O2. Returns: float """ # Create mechanism with thermophysical from database of janaf7 with StringIO() as tempf: makeEquilibriumMechanism(tempf, set([s.specie.name for s in air] + [s.specie.name for s in fuel] + [oxidizer.name]), overwrite=True) mix = ct.Solution(yaml=tempf.getvalue()) mix.X = {s.specie.name: s.X for s in air} stoich_air_fuel_ratio = mix.stoich_air_fuel_ratio({s.specie.name: s.X for s in fuel}, {s.specie.name: s.X for s in air}, basis="mole") return stoich_air_fuel_ratio
#############################################################################
[docs] @lru_cache(maxsize=__CACHE_SIZE__) def computeAlpha(air:Mixture, fuel:Mixture, reactants:Mixture, *, oxidizer:Molecule=database.chemistry.specie.Molecules.O2) -> float: """ Compute the air-fuel ratio given air, fuel, and reactants mixture compositions. Args: air (Mixture): The air mixture composition fuel (Mixture): The fuel mixture composition reactants (Mixture): The reactants mixture composition oxidizer (Molecule, optional): The oxidizing molecule. Defaults to database.chemistry.specie.Molecules.O2. Returns: float """ #Procedure: # 1) Isolate air based on its composition (preserve proportion of mass/mole fractions) # 2) Isolate fuel based on its composition (preserve proportion of mass/mole fractions) # 3) Compute ratio of their mass fractions in full mixture # 1) yAir, remainder = reactants.subtractMixture(air) # 2) yFuel, remainder = remainder.subtractMixture(fuel) yFuel *= (1. - yAir) # 3) return yAir/yFuel
#############################################################################
[docs] def makeEquilibriumMechanism(path_or_stream:str|Any, species:Iterable[str], *, overwrite:bool=False) -> None: """ Create a mechanism (in yaml format) for computation of chemical equilibrium (with cantera) with the desired specie. The thermophysical properties are based on NASA polynomials, which are looked-up in the corresponding database. File structure: phases: - name: gas thermo: ideal-gas elements: [C, H, N, ...] species: [AR, N2, HE, H2, ...] kinetics: gas state: {T: 300.0, P: 1 atm} species: - name: CO2 composition: {C: 1, O:2} thermo: model: NASA7 temperature-ranges: [200.0, 1000.0, 6000.0] data: - [...] #Low coefficients - [...] #High coefficients - ... Args: path_or_stream (str or stream): The path where to save the mechanism in .yaml format or a writable stream. species (Iterable[Molecule]): The list of specie to use in the mechanism. overwrite (bool, optional): Overwrite if found? Defaults to False. """ # Check for the path_or_stream checkType(species, Iterable, "species") [checkType(s, str, f"species[{ii}]") for ii, s in enumerate(species)] # Make species a set (remove duplicate) species = set(species) species_list = list(species) # Determine if path_or_stream is a string (file path) or a stream is_path = isinstance(path_or_stream, str) if is_path: path = path_or_stream if not path.endswith(".yaml"): path += ".yaml" # Check path if not overwrite and os.path.isfile(path): raise IOError(f"Path '{path}' exists. Set 'overwrite' to True to overwrite.") # Load the databases from libICEpost.Database.chemistry.thermo.Thermo.janaf7 import janaf7_db, janaf7 from libICEpost.Database.chemistry.specie.Molecules import Molecules # Find the atoms atoms:list[str] = [] for s in species_list: specie = Molecules[s] for a in specie: if not a.atom.name in atoms: atoms.append(a.atom.name) output = {} output["phases"] = \ [ { "name":"gas", "thermo":"ideal-gas", "elements":atoms, "species":species_list, "kinetics":"gas", "state":{"T": 300.0, "P": 101325.0} } ] output["species"] = \ [ { "name":s, "composition":{a.atom.name:float(a.n) for a in Molecules[s]}, "thermo": { "model":"NASA7", "temperature-ranges":[float(janaf7_db[s].Tlow), float(janaf7_db[s].Tth), float(janaf7_db[s].Thigh)], "data":[[float(v) for v in janaf7_db[s].cpLow], [float(v) for v in janaf7_db[s].cpHigh]] } } for s in species_list ] output["reactions"] = [] if is_path: with open(path, 'w') as yaml_file: yaml.dump(output, yaml_file, default_flow_style=False) else: yaml.dump(output, path_or_stream, default_flow_style=False)
#############################################################################
[docs] @lru_cache(maxsize=__CACHE_SIZE__) def computeLHV(fuel:Molecule|str|Mixture, *, fatal=True) -> float: """ Compute the lower heating value (LHV) of a molecule. This must be stored in the database of fuels (database.chemistry.specie.Fuels), so that it has an oxidation reaction in the corresponding database (database.chemistry.reactions.StoichiometricReaction). Args: fuel (Molecule|str|Mixture): Either the molecule, the name of the molecule, or a Mixture in case of multi-component fuel. fatal (bool, optional): Raise error if fuel not found in database? Defaults to True. Returns: float: The LHV [J/kg] """ from libICEpost.Database.chemistry.reactions.StoichiometricReaction import StoichiometricReaction_db, StoichiometricReaction from libICEpost.src.thermophysicalModels.thermoModels.thermoMixture.ThermoMixture import ThermoMixture from libICEpost.Database.chemistry.specie.Molecules import Fuels checkType(fuel, (str, Molecule, Mixture), "fuel") #From Molecule or str if isinstance(fuel, Molecule): if isinstance(fuel, str): fuel = Fuels[fuel] #If fuel is not in the database, return 0 and raise warning if not fuel.name + "-ox" in StoichiometricReaction_db: if fatal: raise ValueError(f"Fuel '{fuel.name}' not found in database. Cannot compute LHV.") return 0.0 oxReact:StoichiometricReaction = StoichiometricReaction_db[fuel.name + "-ox"] reactants = ThermoMixture(oxReact.reactants,thermoType={"Thermo":"janaf7", "EquationOfState":"PerfectGas"}) products = ThermoMixture(oxReact.products,thermoType={"Thermo":"janaf7", "EquationOfState":"PerfectGas"}) return (reactants.Thermo.hf() - products.Thermo.hf())/oxReact.reactants.Y[oxReact.reactants.index(fuel)] #From Mixture elif isinstance(fuel, Mixture): # LHV = sum(Yi*LHVi) return sum([computeLHV(f.specie, fatal=fatal)*f.Y for f in fuel])
#############################################################################
[docs] @lru_cache(maxsize=__CACHE_SIZE__) def computeMixtureEnergy(mixture:Mixture, oxidizer:Molecule=database.chemistry.specie.Molecules.O2) -> float: """ Compute the energy of a mixture based on the LHV of fuels contained. Computes stoichiometric combustion based on the fuels in the database (database.chemistry.specie.Fuels). Attributes: mixture (Mixture): The mixture. oxidizer (Molecule, optional): The oxidizing agend. Defaults to database.chemistry.specie.Molecules.O2. Returns: float: The avaliable chemical energy of the mixture [J/kg] """ reactionModel = Stoichiometry(mixture) #Build thermodynamic models of mixture based on janaf and perfect gas thermoType = \ { "EquationOfState":"PerfectGas", "Thermo":"janaf7" } reactants = ThermoMixture(reactionModel.reactants, thermoType=thermoType) products = ThermoMixture(reactionModel.products, thermoType=thermoType) return (reactants.Thermo.hf() - products.Thermo.hf())