Introduction to tutorial 2
The problems consists of a thermal block with a source term. The problem equations are:
\[
\nabla \cdot (k \nabla T) = S
\]
where \(k\) is the diffusivity, \(T\) is the temperature and \(S\) is the source term. The problem discretised and formalized in matrix equation reads:
\[
AT = S
\]
where \(A\) is the matrix of interpolation coefficients, \(T\) is the vector of unknowns and \(S\) is the vector representing the source term. The domain is subdivided in 9 different parts and each part has parametrized diffusivity. See the image below for a clarification.
Both the full order and the reduced order problem are solved exploiting the parametric affine decomposition of the differential operators:
\[
A = \sum_{i=1}^N \theta_i(\mu) A_i
\]
For the operations performed by each command check the comments in the source 02thermalBlock.C file.
The ITHACAPODdict file
In this section are explained the main steps necessary to construct the tutorial N°2
The necessary header files
First of all let's have a look to the header files that needs to be included and what they are responsible for:
The standard C++ header for input/output stream objects:
The OpenFOAM header files:
#include "fvCFD.H"
#include "IOmanip.H"
#include "Time.H"
The header file of ITHACA-FV necessary for this tutorial
Header file of the ITHACAPOD class.
Header file of the ITHACAutilities namespace.
The Eigen library for matrix manipulation and linear and non-linear algebra operations:
And we define some mathematical constants and include the standard header for common math operations:
#define _USE_MATH_DEFINES
#include <cmath>
Implementation of the tutorial02 class
Then we can define the tutorial02 class as a child of the laplacianProblem class
Class to implement a full order laplacian parametrized problem.
Class where the tutorial number 2 is implemented.
{
public:
:
{}
tutorial02(int argc, char *argv[])
volScalarField S(IOobject("S", runTime.timeName(), mesh, IOobject::NO_READ, IOobject::AUTO_WRITE), T.mesh(), dimensionedScalar("zero", dimensionSet(0, 0, -1, 1, 0, 0, 0), 0))
The members of the class are the fields that needs to be manipulated during the resolution of the problem
Inside the class it is defined the offline solve method according to the specific parametrized problem that needs to be solved.
void offlineSolve(word folder="./ITHACAoutput/Offline/")
It perform an offline Solve.
{
If the offline solve has already been performed than read the existing snapshots
bool offline
Boolean variable, it is 1 if the Offline phase has already been computed, else 0.
{
}
PtrList< volScalarField > Tfield
List of snapshots for the solution.
Eigen::MatrixXd mu_samples
Matrix of parameters to be used for PODI, where each row corresponds to a sample point....
List< Eigen::MatrixXd > readMatrix(word folder, word mat_name)
Read a three dimensional matrix from a txt file in Eigen format.
void read_fields(PtrList< GeometricField< Type, PatchField, GeoMesh > > &Lfield, word Name, fileName casename, int first_snap, int n_snap)
Function to read a list of fields from the name of the field and casename.
else perform the offline solve where a loop over all the parameters is performed:
for (label
i = 0;
i <
mu.rows();
i++)
Eigen::MatrixXd mu
Row matrix of parameters.
{
for (label j = 0; j <
mu.cols() ; j++)
{
}
List< scalar > theta
Theta (coefficients of the affine expansion)
a 0 internal constant value is assigned before each solve command with the lines
void assignIF(T &s, G &value)
Assign internal field condition.
and the solve operation is performed, see also the laplacianProblem class for the definition of the methods
void truthSolve()
Perform a TruthSolve.
The we need also to implement a method to set/define the source term that may be problem dependent. In this case the source term is defined with a hat function:
void SetSource()
Define the source term function.
{
volScalarField
yPos =
T.mesh().C().component(vector::Y).ref();
volScalarField
xPos =
T.mesh().C().component(vector::X).ref();
{
}
forAll(example_CG.gList, solutionI)
label counter
Counter used for the output of the full order solutions.
}
Define by:
\[ S = \sin(\frac{\pi}{L}\cdot x) + \sin(\frac{\pi}{L}\cdot y) \]
where \(L\) is the dimension of the thermal block which is equal to 0.9.
With the following is defined a method to set compute the parameter of the affine expansion:
void compute_nu()
Compute the diffusivity in each subdomain.
{
The list of parameters is resized according to number of parametrized regions
PtrList< volScalarField > nu_list
Nu (diffusivity)
The nine different volScalarFields to identify the viscosity in each domain are initialized:
and the 9 different boxes are defined:
Eigen::MatrixXd Box1(2, 3);
Box1 << 0, 0, 0, 0.3, 0.3, 0.1;
Eigen::MatrixXd Box2(2, 3);
Box2 << 0.3, 0, 0, 0.6, 0.3, 0.1;
Eigen::MatrixXd Box3(2, 3);
Box3 << 0.6, 0, 0, 0.91, 0.3, 0.1;
Eigen::MatrixXd Box4(2, 3);
Box4 << 0, 0.3, 0, 0.3, 0.6, 0.1;
Eigen::MatrixXd Box5(2, 3);
Box5 << 0.3, 0.3, 0, 0.6, 0.6, 0.1;
Eigen::MatrixXd Box6(2, 3);
Box6 << 0.6, 0.3, 0, 0.91, 0.6, 0.1;
Eigen::MatrixXd Box7(2, 3);
Box7 << 0, 0.6, 0, 0.3, 0.91, 0.1;
Eigen::MatrixXd Box8(2, 3);
Box8 << 0.3, 0.61, 0, 0.6, 0.91, 0.1;
Eigen::MatrixXd Box9(2, 3);
and for each of the defined boxes the relative diffusivity field is set to 1 inside the box and remain 0 elsewhere:
void setBoxToValue(GeometricField< Type, fvPatchField, volMesh > &field, Eigen::MatrixXd Box, Type value)
Set value of a volScalarField to a constant inside a given box.
See also the ITHACAutilities::setBoxToValue for more details.
The list of diffusivity fields is set with:
Definition of the main function
Once the tutorial02 class is defined the main function is defined, an example of type tutorial02 is constructed:
the number of parameter is set:
example.Pnumber = 9;
example.setParameters();
the range of the parameters is defined:
example.mu_range.col(0) = Eigen::MatrixXd::Ones(9, 1) * 0.001;
example.mu_range.col(1) = Eigen::MatrixXd::Ones(9, 1) * 0.1;
and 500 random combinations of the parameters are generated:
the size of the list of values that are multiplying the affine forms is set:
the source term is defined, the compute_nu and assemble_operator functions are called
example.SetSource();
example.compute_nu();
example.assemble_operator();
then the Offline full order Solve is performed:
Once the Offline solve is performed the modes ar obtained using the ITHACAPOD::getModes function:
void getModes(PtrList< GeometricField< Type, PatchField, GeoMesh > > &snapshots, PtrList< GeometricField< Type, PatchField, GeoMesh > > &modes, word fieldName, bool podex, bool supex, bool sup, label nmodes, bool correctBC)
Computes the bases or reads them for a field.
and the projection is performed onto the POD modes using 10 modes
example.project(NmodesTproj);
Once the projection is performed we can construct a reduced object:
Class to solve the online reduced problem associated with a Laplace problem.
and solve the reduced problem for some values of the parameters:
for (
int i = 0;
i < FOM_test.mu.rows();
i++)
{
reduced.solveOnline(FOM_test.mu.row(
i));
}
Finally, once the online solve has been performed we can reconstruct the solution:
reduced.reconstruct("./ITHACAoutput/Reconstruction");
The plain program
Here there's the plain code
#include <iostream>
#include "fvCFD.H"
#include "IOmanip.H"
#include "Time.H"
#include <Eigen/Dense>
#define _USE_MATH_DEFINES
#include <cmath>
{
public:
:
{}
{
{
}
else
{
List<scalar> mu_now(9);
scalar IF = 0;
for (label
i = 0;
i <
mu.rows();
i++)
{
for (label j = 0; j <
mu.cols() ; j++)
{
}
}
}
}
{
volScalarField
yPos =
T.mesh().C().component(vector::Y).ref();
volScalarField
xPos =
T.mesh().C().component(vector::X).ref();
{
}
}
{
Eigen::MatrixXd Box1(2, 3);
Box1 << 0, 0, 0, 0.3, 0.3, 0.1;
Eigen::MatrixXd Box2(2, 3);
Box2 << 0.3, 0, 0, 0.6, 0.3, 0.1;
Eigen::MatrixXd Box3(2, 3);
Box3 << 0.6, 0, 0, 0.91, 0.3, 0.1;
Eigen::MatrixXd Box4(2, 3);
Box4 << 0, 0.3, 0, 0.3, 0.6, 0.1;
Eigen::MatrixXd Box5(2, 3);
Box5 << 0.3, 0.3, 0, 0.6, 0.6, 0.1;
Eigen::MatrixXd Box6(2, 3);
Box6 << 0.6, 0.3, 0, 0.91, 0.6, 0.1;
Eigen::MatrixXd Box7(2, 3);
Box7 << 0, 0.6, 0, 0.3, 0.91, 0.1;
Eigen::MatrixXd Box8(2, 3);
Box8 << 0.3, 0.61, 0, 0.6, 0.91, 0.1;
Eigen::MatrixXd Box9(2, 3);
Box9 << 0.6, 0.6, 0, 0.9, 0.91, 0.1;
}
{
{
}
}
};
int main(
int argc,
char* argv[])
{
#if OPENFOAM > 1812
argList::addOption("stage", "offline", "Perform offline stage");
argList::addOption("stage", "online", "Perform online stage");
HashTable<string> validParOptions;
validParOptions.set
(
"stage",
"offline"
);
validParOptions.set
(
"stage",
"online"
);
Pstream::addValidParOptions(validParOptions);
if (example._args().get("stage").match("offline"))
{
}
else if (example._args().get("stage").match("online"))
{
}
else
{
Info << "Pass '-stage offline', '-stage online'" << endl;
}
#else
if (argc == 1)
{
Info << "Pass 'offline' or 'online' as first arguments."
<< endl;
exit(0);
}
int argc_proc = argc - 1;
char* argv_proc[argc_proc];
argv_proc[0] = argv[0];
if (argc > 2)
{
std::copy(argv + 2, argv + argc, argv_proc + 1);
}
argc--;
if (std::strcmp(argv[1], "offline") == 0)
{
}
else if (std::strcmp(argv[1], "online") == 0)
{
}
else
{
Info << "Pass offline, online" << endl;
}
#endif
exit(0);
}
{
int NmodesTout = para->
ITHACAdict->lookupOrDefault<
int>(
"NmodesTout", 15);
int NmodesTproj = para->
ITHACAdict->lookupOrDefault<
int>(
"NmodesTproj", 10);
example.
mu_range.col(0) = Eigen::MatrixXd::Ones(9, 1) * 0.001;
example.
mu_range.col(1) = Eigen::MatrixXd::Ones(9, 1) * 0.1;
NmodesTout);
FOM_test.
mu_range.col(0) = Eigen::MatrixXd::Ones(9, 1) * 0.001;
FOM_test.
mu_range.col(1) = Eigen::MatrixXd::Ones(9, 1) * 0.1;
FOM_test.
theta.resize(9);
}
{
for (
int i = 0;
i < FOM_test.
mu.rows();
i++)
{
reduced.solveOnline(FOM_test.
mu.row(
i));
}
reduced.reconstruct("./ITHACAoutput/Reconstruction");
reduced.Trec);
}
void online_stage(tutorial02 &example, tutorial02 &FOM_test)
void offline_stage(tutorial02 &example, tutorial02 &FOM_test)
Header file of the reducedLaplacian class.
Class for the definition of some general parameters, the parameters must be defined from the file ITH...
IOdictionary * ITHACAdict
Dictionary for input objects from file.
static ITHACAparameters * getInstance()
Gets an instance of ITHACAparameters, to be used if the instance is already existing.
PtrList< fvScalarMatrix > operator_list
List of operators.
autoPtr< volScalarField > _T
Temperature field.
void project(label Nmodes)
Perform a projection onto the POD modes.
autoPtr< volScalarField > _nu
Diffusivity.
autoPtr< fvMesh > _mesh
Mesh.
volScalarModes Tmodes
List of POD modes.
autoPtr< volScalarField > _S
Source Term.
autoPtr< Time > _runTime
Time.
label Pnumber
Number of parameters.
label Tnumber
Dimension of the training set (used only when gerating parameters without input)
void genRandPar()
Generate Random Numbers.
Eigen::MatrixXd mu_range
Range of the parameter spaces.
void setParameters()
Set Parameters Problems.
bool podex
Boolean variable, it is 1 if the POD has already been computed, else 0.
volScalarField & S
Source term field.
volScalarField & nu
Diffusivity field.
void assemble_operator()
Construct the operator_list where each term of the affine decomposition is stored.
volScalarField & T
[tutorial02] Temperature field
Header file of the laplacianProblem class.
double errorL2Rel(GeometricField< T, fvPatchField, volMesh > &field1, GeometricField< T, fvPatchField, volMesh > &field2, List< label > *labels)
Computes the relative error between two geometric Fields in L2 norm.