To understand the how P1 model is used and implemented in coalChemistryFoam, we use tutorial simplifiedSiwek as an example.
P1 model’s euqtion:
In the code, it looks like this:
1 | solve |
which is defined in Foam::radiationModels::P1::calculate in P1.C. Here, gamma (the diffusivity), a_ (the absorption coefficient ), G_, e_ (the emission coefficient), pow4(T_), E_ (the emission contribution).
where
Before starting, the radiationProperties file used in tutorial simplifiedSiwek will be listed here:
1 | solverFreq 1; |
In coalChemistryFoam.C
createFields.H is included. In createFields.H, createRadiationModel.H is included. In createRadiationModel.H:
1 | autoPtr<radiationModel> radiation(radiationModel::New(thermo.T())); |
a object called radiation is constructed, which has type of a pointer to class type radiationModel. autoPtr is similar to a smart pointer in C++. This radiationModel object is called in EEqn.H radiation->correct(); Of course, in calss radiationModel, there is a member function called virtual void correct() which is a virtual function. In its correct() function, it calls calculate() which in this class is a pure virtual function. It means this function will be called in its sub classes. Of course, P1::calculate() will be called in this case. In EEqn.H, radiation->Sh(thermo, he) is also called.
In radiationModel folder, there is a file called radiationModelNew.C. In this file, Foam::autoPtr<Foam::radiationModel> Foam::radiationModel::New(const volScalarField& T) is defined.
1 | Foam::autoPtr<Foam::radiationModel> Foam::radiationModel::New |
The modelType here has a type of word and is initialised as "P1". This is done by IOdictionary(radIO).lookup("radiationModel") >> modelType. Finally, the constructor of the radiationModel will be called in radiationModel.C. This is done by the last line of the Foam::autoPtr<Foam::radiationModel> Foam::radiationModel::New. Here a smart pointer with a type radiationModel is returned. This pointer is initialized by cstrIter()(T). Here, cstrIter has a type of TConstructorTable::iterator where TConstructorTable is exactly the HashTable type, defined by run time selection Macro. Therefore, cstrIter has a type of HashTable<dictionaryConstructorPtr, word, string::hash>::iterator. The iterator class is defined inside the HashTable class, it has an operator() defined in the class body which is called here (cstrIter()(T)). Here, the cstrIter() actually represent the P1 model. Therefore, return autoPtr<radiationModel>(cstrIter()(T)); will call the constructor of radiationModels::P1 class, which is a subclass of Foam::radiationModel:
1 | Foam::radiationModels::P1::P1(const volScalarField& T) |
In the constructor of radiationModels::P1 class, it will first initialize its parent class radiationModel by calling its constructor(shown below). Then, its members (G_, qr_, a_, e_, E_) will be initialized. The definitions of those variables are shown below:
1 | //- Incident radiation / [W/m^2] |
The constructor of the Foam::radiationModel is shown here:
1 | Foam::radiationModel::radiationModel(const word& type, const volScalarField& T) |
The use of IOobject class has been explained here. There is a initialise() function called in the constructor which is also defined in radiationModel.C.
1 | void Foam::radiationModel::initialise() |
Then we go back to initialise(). It shoud be noted here, the class radiationModel is a subclass of IOdictionary which is a subclass of dictionary. Therefore when we called e.g. radiationModels::absorptionEmissionModel::New(*this, mesh_) which is defined in absorptionEmissionModelNew.C:
1 | Foam::autoPtr<Foam::radiationModels::absorptionEmissionModel> |
the *this which is a radiationModel object is substituted. absorptionEmission_, scatter_, scatter_ are defined in radiationModel.H:
1 | autoPtr<radiationModels::absorptionEmissionModel> |
In the initialise() function, the three autoPtr smart pointers will call each respected constructors by calling the reset function. The first one:
1 | absorptionEmission_.reset |
will call the absorptionEmissionModel::New, which is quite similar to Foam::radiationModel::New:
1 | Foam::autoPtr<Foam::radiationModels::absorptionEmissionModel> |
Here in the radiationProperties file, const word modelType(dict.lookup("absorptionEmissionModel")); will result in binary. Therefore, return autoPtr<absorptionEmissionModel>(cstrIter()(dict, mesh)); will call the constructor of class binary in binary.C as binary is a subclass of absorptionEmissionModel:
1 | Foam::radiationModels::absorptionEmissionModels::binary::binary |
Here the absorptionEmissionModel::New will be called again. The use of subDict and optionalSubDict has been explained here. The typeName here is binary, which means typeName + "Coeffs" is binaryCoeffs. Therefore, coeffsDict_ will be:
1 | { |
So, coeffsDict_.subDict("model1") in this case will be a dictionary of:
1 | { |
Vice versa the coeffsDict_.subDict("model2"). Let’s go forward. The binary constructor will also initialize its members model1_ and model2_, which are defined in binary.H:
1 | //- First absorption model |
These 2 members have a type of pointer to absorptionEmissionModel. When we initialize it, it will call the absorptionEmissionModel::New function again. Now we will go through this function again to make everything clear. When we call const word modelType(dict.lookup("absorptionEmissionModel")); in absorptionEmissionModel::New function, it will return a constant to modelType. Then, return autoPtr<absorptionEmissionModel>(cstrIter()(dict, mesh)); will call the constructor of class constant in constantAbsorptionEmission.C:
1 | Foam::radiationModels::absorptionEmissionModels::constant::constant |
Here, we will first call the constructor of absorptionEmissionModel. This is the first time we call the constructor of the base class absorptionEmissionModel:
1 | Foam::radiationModels::absorptionEmissionModel::absorptionEmissionModel |
This constructor will initialize two protected members declared in absorptionEmissionModel.H:
1 | protected: |
Those protected members can be used in its subclass e.g. constant. Then the coeffsDict_ will be initialized as coeffsDict_(dict.optionalSubDict(typeName + "Coeffs")), where typeName here is constant. Therefore coeffsDict_ is:
1 | constantCoeffs |
Then, a_, e_ E_ will be initialized with the values in absorptivity, emissivity, E. For the initialization of model2_, there is only a little bit difference from that of model1_. Because the model2_ is initialized with type cloud. It is defined in a totally different folder (src/lagrangian/intermediate/submodels/addOns/radiation/absorptionEmission). This class which is called cloud is also a subclass of absorptionEmissionModel defined in cloudAbsorptionEmission.H. In cloudAbsorptionEmission.C:
1 | Foam::radiationModels::absorptionEmissionModels::cloud::cloud |
Here, no further explanation is needed for how the constructor is working. Due to its similarity to the constructor of class constant. For scatter model, it will be similar to absorptionEmissionModel, we won’t go through it again.
In coalChemistryFoam.C we include EEqn.H. In EEqn.H, the radiation object which was constructed will be used. It is used as radiation->Sh(thermo, he) and radiation->correct(). The radiation->correct() calls void Foam::radiationModels::P1::calculate(). One thing here we have to be pretty clear about is where the pointer absorptionEmission_ is pointing to !!!!!!! As we do the .reset in the initialise() function, we call the radiationModels::absorptionEmissionModel::New(*this, mesh_) which return a pointer points to binary class. Therefore, here the absorptionEmission_ pointing to the binary class. So I will note here once more, the absorptionEmission_ points to the binary class. Here, I will show the Foam::radiationModels::P1::calculate() of P1.C:
1 | void Foam::radiationModels::P1::calculate() |
First, the calculate function initialize the volScalarField members we need e.g. a_, e_, E_, a0, gamma. Then we will solve the P1 equation in solve. I will explain here how a_ is initialized in this calculate() function to represent how these volScalarField are initialized. Here, I will note it once more, the absorptionEmission_ is pointing to binary class, which is a subclass of absorptionEmission class. absorptionEmission_->a() will call function a(). But a() only exist in absorptionEmission class. Therefore we will call this a() in absorptionEmission class first. In absorptionEmissionModel.H:
1 | //- Absorption coefficient (net) |
1 | Foam::tmp<Foam::volScalarField> |
In aDisp(bandI) + aCont(bandI), function aDisp and aCont in class binary will be called. function aDisp and aCont are virtual functions firstly defined in absorptionEmissionModel class. As we mentioned, binary is a subclass of absorptionEmissionModel and it also has the aDisp and aCont functions. As absorptionEmission_ is pointing to class binary, the aDisp and aCont function in binary class will be called. I will show aCont function as an example here to see how the function is called. In binary.H:
1 | //- Absorption coefficient for continuous phase |
In binary.C:
1 | Foam::tmp<Foam::volScalarField> |
Members model1_ and model2_ are defined here. Both are smart pointers. model1_ points to class constant while model2_ points to class radiationModels::absorptionEmissionModels::cloud. In constantAbsorptionEmission.C:
1 | Foam::tmp<Foam::volScalarField> |
It means aCont will be initialized with the a_ we input in the radiationProperties file. a_ is initialized here (in the constant constructor). But in class Foam::radiationModels::absorptionEmissionModels::cloud , there is no function called aCont. It is reasonable, because in particle field, there shouldn’t be a aCont function. As virtual function’s polymorphism in C++, model2_->aCont will call the aCont() function in its base class absorptionEmissionModel:
1 | Foam::tmp<Foam::volScalarField> |
As you see, it will be initialized with zeros in this volScalarField.
After all these initialization, we will solve the P1 model’s equation in solve() and return a source term to the energy equation in EEqn.H:
1 | { |
The Sh() is defined in radiationModel, which is not existing in P1 class:
1 | Foam::tmp<Foam::fvScalarMatrix> Foam::radiationModel::Sh |
The calculation of Ru and Rp are defined in P1 as they are virtual functions both in radiationModel and P1 class:
1 | Foam::tmp<Foam::DimensionedField<Foam::scalar, Foam::volMesh>> |
and
1 | Foam::tmp<Foam::volScalarField> Foam::radiationModels::P1::Rp() const |
Here we will explain the model’s equations e.g. how the radiation term acts on the energy transport equation. The energy source term returned to the heat transport equation here is the Sh(). It is calculated as(in the code):
where,
results in,
In ANSYS FLUENT, the form of the equation is actually the same:
It should be mentioned here,





