Contribution: Bilger Mixture Fraction Function Object
Dear all,
I recently created a new function object for computing the Bilger mixture fraction from species data. I'm not sure what the correct channel is to contribute the code, since I cannot create a fork here on gitlab. For example, should I upload a git patch file for code review?
Here are more information about the new function object:
The Bilger mixture fraction indicates the mixing ratio of fuel and oxidizer (kg fuel / kg mixture) and is often used in combustion simulations, either as part of a combustion model or to study the structure of flames. By having a function object that computes the mixture fraction from the resolved chemical species, users can create their own lookup tables for mixture fraction based combustion models or simply use it for post processing/visualization.
My implementation is based on a pull request I did some time ago for the open-source thermo-chemical library Cantera (https://github.com/Cantera/cantera/pull/851). It computes the mixture fraction based on the elemental composition of fuel, oxidizer and the mixture.
Similar functionality has been requested before (link, link, link, link). There are also some user implementations (link, link), however, they only work for specific solvers and do not make use of the information provided by the reactingMixture
types.
I tested my implementation with the following solvers to make sure that it is compatible with a wide range of solvers and models:
-
combustion:
reactingFoam, rhoReactingFoam, rhoBuoyantReactingFoam, chemFoam, fireFoam
-
lagrangian:
sprayFoam, coalChemistryFoam, reactingParcelFoam
-
heatTransfer:
chtMultiRegionFoam
-
multiphase:
reactingTwoPhaseEulerFoam
I also ran the test cases from the Cantera test suite to validate the results. Everything is tested with the current develop branch (1d544540) and gcc 10.
Let me know if this is of interest and if so what the best way is to start the contribution process.
Kind regards,
Thorsten
No child items are currently assigned. Use child items to break down this issue into smaller parts.
Link issues together to show that they're related. Learn more.
Activity
- Maintainer
- Developer
Hi, If you can encapsulate the code into a function object and attached a small tutorial case with for testing, we can have a look and introduce your contribution. We'd need how you'd like to be referenced for your contribution. .ie. name, institute, reference, etc. Thanks
- Author
Thanks for the instructions.
Attached you find in contribution_zirwes.zip
- a git patch file for the contribution (
contribution_zirwes/feature-BilgerMixtureFraction.patch
) - a stand-alone version of the function object and a test case
The only difference between the code in the patch file and the standalone version is that the patch modifies the existing Make/files and options files, while the stand-alone version provides its own Make directory. Additionally, the patch also changes an existing tutorial case to showcase this function object.
Compile and Run the Code
- To compile the code, go to
standalone/BilgerMixtureFraction
and compile withwmake
. - To run the code, go to
testcase/counterFlowFlame2D
and run the case withblockMesh && reactingFoam
This testcase is an exact copy of
$FOAM_TUTORIALS/combustion/reactingFoam/laminar/counterFlowFlame2D
, the only difference being thefunctions
entry insystem/controlDict
There will be an additional field written called
f_Bilger
. The test case also contains a picture (results.png
) which shows the timestep t=0.5s. On the right of the picture, the mixture fraction field is zero for the oxidizer (on the right, inlet for air) and 1 for the fuel (on the left, inlet for fuel). The stoichiometric mixture fraction for methane/air is at f=0.055. The white line shows the iso-line of f=0.055, which marks the position of the flame (same position as highest temperature on the left).What the Code does
The function object computes the (Bilger) mixture fraction from:
f_{\mathrm{Bilger}} \equiv \frac{\beta-\beta_{\mathrm{ox}}} {\beta_{\mathrm{fuel}}-\beta_{\mathrm{ox}}}with
\beta = 2\frac{Y_C}{W_C}+2\frac{Y_S}{W_S}+\frac{1}{2}\frac{Y_H}{W_H} -\frac{Y_O}{W_O}where
Y_eis the elemental mass fraction of elementeandW_eits atomic weight. The subscribt\mathrm{ox}denotes the oxidizer composition,\mathrm{fuel}the fuel composition and the one without subscript is the value of\betafor the current mixture in the cell. The code considers carbon, hydrogen, sulfur and oxygen. A reference to the original literature by Bilger is included in the header file.The elemental mass fraction is defined as
Y_e\equiv\sum_k \frac{a_{e,k}W_e}{W_k}Y_k, where the subscriptkdenotes the chemical species anda_{e,k}is the number of atoms of elementein speciesk. Substituting this in the expression for\beta, it can be written as:\beta = 2\sum_k\frac{a_{C,k}Y_k}{W_k} + 2\sum_k\frac{a_{S,k}Y_k}{W_k} + \frac{1}{2}\sum_k\frac{a_{H,k}Y_k}{W_k}-\sum_k\frac{a_{O,k}Y_k}{W_k}which is what is implemented in the code. The user has to provide a composition for the fuel and oxidizer (which is usually given by the inlets for fuel and oxidizer, but there are some cases where they might be defined differently) in the following way:
mixtureFraction { type BilgerMixtureFraction; fuel { composition moleFractions; // optional entry CH4 1; } oxidiser { composition massFractions; // optional entry O2 0.23; N2 0.77; } //result "my_f_Bilger"; // optional alternative name for the field //phase gas; // optional entry to support multi-phase solvers //region fluid; // optional entry to support multi-region solvers (as part of OpenFOAM's function object implementation, not my code) }
The sub dicts
fuel
andoxidizer
contain the composition of fuel and oxidizer. They can contain an arbitrary number of species. The optional entrycomposition
determines if the specified values are interpreted as either mass or mole fractions. The input is automatically normalized to 1, soO2 2.3; N2 7.7;
would be a valid entry too.If you want to use this function object with a multi-phase solver, include the
phase
keyword. If you want to use it with a multi-region solver, include theregion
keyword. If you want to change the name of the field, include theresult
keyword. Of course, the information about the elemental composition for each species has to be provided in the case setup (for some tutorial cases, depending on the combustion model, this information might not be present but can easily be added by the user. No changes necessary for the counterflow tutorial case).All details given here are also included in the description at the top of the header file.
Some Remarks about the Code
- I put the function object in
$FOAM_SOURCE/thermophysicalModels/reactionThermo/functionObjects
, as I feel this makes the most sense. - I added the function object to the counterflow tutorial case of
reactingFoam
to showcase its functionality, because I think this is the most likely place users would search for this function object ($FOAM_TUTORIALS/combustion/reactingFoam/counterFlowFlame2D
). - Following the naming scheme in OpenFOAM (e.g.
ft
for the turbulent mixture fraction) and to avoid clashes with other types of existing mixture fractions (singleMixtureFraction
,SootMixtureFraction
), I named the function objectBilgerMixtureFraction
and the resulting fieldf_Bilger
. - The
getElementInformation
member function is quite verbose, but I think there might be no other way to support all possible types of reacting mixtures. - Both "oxidizer" and "oxidiser" spellings are supported to avoid confusion.
- I tried to include as many "sanity checks" as possible to catch invalid entries.
Attribution
Regarding the reference of the contribution: I looked at the code to see what other people did. As written in the patch file in
contribution_zirwes/feature-BilgerMixtureFraction.patch
, I added my name to- the headers of the two source files of the function object
- the
CONTRIBUTORS.md
file in the main directory
My full reference is:
Thorsten Zirwes, Karlsruhe Institute of Technology (Steinbuch Centre for Computing & Engler-Bunte-Institute)
I used the code in the following publication:
- T. Zirwes, F. Zhang, P. Habisreuther, M. Hansinger, H. Bockhorn, M. Pfitzner, and D. Trimis, “Quasi-DNS dataset of a piloted flame with inhomogeneous inlet conditions,” Flow, Turbulence and Combustion, vol. 104, pp. 997–1027, 2020 (DOI:10.1007/s10494-019-00081-5)
Let me know if you want me to do any changes to the code or if I should provide more information.
Kind regards,
Thorsten
Edited by Thorsten Zirwes - a git patch file for the contribution (
- Developer
Hi Thorsten, Thanks for the very detailed examples and documentation. It is a very nice contribution. @kuti Please proceed with this contribution and let me know if you have any questions
- Kutalmış Berçin assigned to @kuti
assigned to @kuti
- Kutalmış Berçin changed milestone to %v2012
changed milestone to %v2012
- Kutalmış Berçin added contribution label
added contribution label
- Maintainer
Dear @g3,
Will be doing few style and consistency changes, if you don't mind. You can follow the branch (but just started to checks - and Friday night :) ), and please feel free to suggest anything you want along the way: feature-Bilger-mixture-fraction-fo
Few questions (definitely more will come), if possible:
- Is there any minimal verification example, preferably a comparison to an analytical solution, a high-fidelity simulation or an experiment? (e.g.
f=0.05
plot is an analytical solution?) - Does
\beta
have a descriptive name? its units?
Many thanks.
Edited by Kutalmış Berçin - Is there any minimal verification example, preferably a comparison to an analytical solution, a high-fidelity simulation or an experiment? (e.g.
- Author
Great to see the quick progress :)
Sure, feel free to modify the code.
- I think \betais usually called "coupling function". I double-checked it in the textbook by Peters (ISBN-10 : 0521660823). Since I cannot link the contents of the book here, you can see this lecture (slide 67)
- the unit of \betais
kmol / kg
There are plenty analytical solutions:
Example 1: stoichiometric mixture fraction of methane/air
For methane, we have
\mathrm{CH}_4 + 2\mathrm{O}_2 \rightarrow \mathrm{CO}_2 + 2\mathrm{H}_2\mathrm{O}. Or in other words: the molar ratio of methane to oxygen is 1:2 for stoichiometric combustion. Let's assume the air composition in terms of mass fractions isY_{\mathrm{O}_2}=0.23,\ Y_{\mathrm{N}_2}=0.77. We can convert the mass fractionsYto mole fractionsXwithX_k=Y_k\frac{W}{W_k},\ W=\left(\sum_k \frac{Y_k}{W_k}\right)^{-1}where
Wis the mean molecular weight of the mixture. So we can get the following equations:X_{\mathrm{O}_2}=2X_{\mathrm{CH}_4} \rightarrow Y_{\mathrm{O}_2}\frac{W}{W_{\mathrm{O}_2}}=2Y_{\mathrm{CH}_4}\frac{W}{W_{\mathrm{CH}_4}}\\ \frac{Y_{\mathrm{O}_2}}{Y_{\mathrm{N}_2}} = \frac{0.23}{0.77}\\ Y_{\mathrm{O}_2}+Y_{\mathrm{CH}_4}+Y_{\mathrm{N}_2}=1The first equation is the 2:1 ratio of methane and air in a stoichiometric mixture, the second equation is the ratio of oxygen and N2 in the air, and the third equation is that the sum of all mass fractions have to be unity. Solving the equations yields:
Y_{\mathrm{CH}_4} = \left(1+2\frac{W_{\mathrm{O}_2}}{W_{\mathrm{CH}_4}}\left(1+\frac{0.77}{0.23}\right)\right)^{-1}Using the atomic masses as defined here, the result is:
Y_{\mathrm{CH}_4} = 0.0545137147773594210...Since methane is the only fuel, the mass fraction of methane in the mixture is also the mass fraction of the fuel in the mixture, and therefore
Y_{\mathrm{CH}_4}=f_{\mathrm{Bilger}}.Example 2: Specifying a mixture fraction
Let's say we choose an arbitrary composition for the fuel and mixture in mass fractions (e.g. for a case where some exhaust gas is directed back into an engine):
Y_{k,\mathrm{fuel}} = \begin{cases} \mathrm{CH}_4=0.6\\ \mathrm{O}_2=0.02\\ \mathrm{N}_2=0.2\\ \mathrm{CO}=0.15\\ \mathrm{CO}_2=0.03\\ \end{cases} ,\ Y_{k,\mathrm{oxidiser}} = \begin{cases} \mathrm{CH}_4=0.01\\ \mathrm{O}_2=0.2\\ \mathrm{N}_2=0.7\\ \mathrm{CO}=0.05\\ \mathrm{CO}_2=0.04\\ \end{cases}Let's say we want to create a mixture with mixture fraction
f=0.1. This means, we have to mix 1 kg of the fuel with 9 kg of the oxidizer. So the resulting mixture has the composition:Y_{k,\mathrm{mixture}} = \frac{1}{10}\begin{cases} \mathrm{CH}_4=1\times 0.6+9\times 0.01 = 0.69\\ \mathrm{O}_2=1\times0.02 +9\times 0.2 = 1.82\\ \mathrm{N}_2=1\times 0.2+9\times 0.7 = 6.5\\ \mathrm{CO}=1\times 0.15 + 9\times 0.05 = 0.6\\ \mathrm{CO}_2=1\times 0.03+9\times 0.04 = 0.39\\ \end{cases}The
\frac{1}{10}in front is to normalize all mass fractions to add up to unity, because0.69+1.82+6.5+0.6+0.39=10. If the function object is used for the mixture above, it should figure out that the mixture was created from 1 part fuel and 9 parts oxidizer and outputf_{\mathrm{Bilger}}=0.1Example 3: pure air or pure fuel
The trivial analytic solutions are if the mixture composition is equal to either the fuel or oxidizer composition. In this case,
f_{\mathrm{Bilger}}=1for pure fuel andf_{\mathrm{Bilger}}=0for pure oxidizer.Test cases
I think the quickest way to validate the results is to use
chemFoam
and the tutorial case from$FOAM_TUTORIALS/combustion/chemFoam/gri
.Example 1:
A testcase for example 1 is here: example1.tar.gz
Just run the case with
chemFoam
. The relevant entries are insystem/controlDict
mixtureFraction { type BilgerMixtureFraction; fuel { CH4 1; // fuel is pure methane } oxidiser // as specified in example 1 above { O2 0.23; N2 0.77; } }
and in
constant/initialProperties
:fractionBasis mole; fractions { CH4 1; O2 2; // stoichiometric mixture; CH4:O2 = 1:2 N2 #eval "2*(0.77/0.23)*(15.99940/14.00670)"; }
The relevant output is in
postProcessing/fieldMinMax1/0/fieldMinMax.dat
. For example:# Time CO2 O2 f_Bilger CH4 1e-07 1.517e-18 2.175e-01 5.451e-2 5.451e-2 ... 0.01 8.268e-2 2.293e-2 5.451e-2 1.091e-2
OpenFOAM result vs. analytical solution for f_Bilger:
- 0.05451371477735941 (OpenFOAM)
- 0.054513714777359421 (analytical)
Also, the value of f_Bilger is the same for each time step, even though the mixture burns (CO2 goes up, O2 and CH4 go down), because the elemental composition of the mixture stays constant (as it should).
Example 2:
Again, using the chemFoam case and setting fuel, oxidizer and mixture according to example 2: example2.tar.gz
mixtureFraction { type BilgerMixtureFraction; fuel { CH4 0.6; O2 0.02; N2 0.2; CO 0.15; CO2 0.03; } oxidiser { CH4 0.01; O2 0.2; N2 0.7; CO 0.05; CO2 0.04; } }
and for the initial conditions:
fractionBasis mass; fractions { CH4 0.69; O2 1.82; N2 6.5; CO 0.6; CO2 0.39; }
The value of f_Bilger is:
- 0.1 (analytical)
- 0.09999999999999996 (OpenFOAM)
Example 3:
The trivial example cases are here: example_pure_air.tar.gz example_pure_fuel.tar.gz
In these cases, the initial conditions are either pure methane or pure air. Accordingly, the mixture fraction is 1 or 0, respectively.
Example 4: More complex case
If you want a more complex example, the
counterFlowFlame2D
case from the original contribution can be used. It has the special property that carbon only appears in the fuel (in form of CH4) but not in the oxidizer. Additionally, carbon only exists in CH4 and CO2. Therefore, the fuel can be detected by tracing the carbon atom by looking at CH4 and CO2 only (assuming constant mean molecular weight, which is not exactly true in this case. So technically it is not the analytical solution, but it is close enough):f_{\mathrm{Bilger}}=f_{\mathrm{fuel}}\approx Y_{\mathrm{CH}_4} + \frac{W_{\mathrm{CH}_4}}{W_{\mathrm{CO}_2}}Y_{\mathrm{CO}_2}In the
counterFlowFlame2D
case from my last post, add insystem/controlDict
readFields { type readFields; libs (fieldFunctionObjects); fields (CO2 CH4); }
Create a new dummy file:
cp 0.5/O2 0.5/fValidation
and set the alternative definition of the mixture fraction via
setExprFields -withFunctionObjects -latestTime
expressions ( fValidation { field fValidation; dimensions [0 0 0 0 0 0 0]; expression # // be careful: boundary conditions are not changed by this CH4 + (12.01115+4*1.00797)/(12.01115+2*15.99940)*CO2 #}; } );
The full case is here: counterFlowFlame2D_validation.tar.gz
The result is shown below:
Closing remarks:
Now that I looked at the
chemFoam
cases, maybe it makes sense to streamline the "composition" option. This means, replacing the "composition" option name with the name "fractionBasis" and the names of "moleFractions" and "massFractions" with "mass" and "mole". Then it is consistent withchemFoam
.Edited by Thorsten Zirwes - I think
- Maintainer
Dear @g3,
Many thanks for all these high-quality details.
- I have changed
composition
entry tofractionBasis
,moleFractions
tomole
andmassFractions
tomass
.
Remarks:
- If you don't mind, I will remove
oxidiZer
support (It was definitely good idea, but need to chop it for the uniformity with the rest of the code and simplify things). - I have moved some of the constructor-level functions into the
read
function to make the objects runtime modifiable.
- I have changed
- Author
yeah, sounds good to me
- Maintainer
Dear @g3,
Would you also consider to submit a short communication/technical letter to https://journal.openfoam.com/index.php/ofj ? just a suggestion.
- Author
Sure, good idea. I will prepare a report about the implementation, validation and some examples from my own work
- Kutalmış Berçin mentioned in merge request !393 (merged)
mentioned in merge request !393 (merged)
- Maintainer
Dear @g3 , your contribution has been merged.
If possible, please let us know if anything is problematic, and reopen the ticket.
Thank you very much for your contribution!
- Kutalmış Berçin closed
closed
- Bulut Tekgul mentioned in issue #1977 (closed)
mentioned in issue #1977 (closed)