Introduction
This tutorial considers a molten salt reactor (MSR) cavity represented by a 0.1 m × 0.1 m square domain discretized with 50 cells per side. The physical problem is transient heat transfer coupled with neutronics and fluid mechanics, modelled within the usmsrProblem class of ITHACA-FV. The reduced order model is built for a parameterized set of quantities relevant to reactor safety.
Parametrization
Three parameters are varied:
- kinematic viscosity nu of the fluid,
- total delayed neutron fraction betaTot (with fixed ratios beta_i/betaTot for each group),
- third decay heat group constant decLam3.
Sampling ranges are set to ±10 % around reference values (nu0, betaTot0, decLam30) with a Reynolds number of 100 based on nu0.
Sensitivity metrics
Two figures of merit are computed at 100 s of simulation time:
- total power P_tot,
- mean temperature T_m.
Temporal discretization during offline sampling uses $\Delta t = 0.1$ s, 31 runs, 150 s each, with snapshots every 5 s (which are configurable via the system/ITHACAdict file).
A detailed look into the code
Let's now have a look into the code of tutorial 15.
The necessary header files
The code relies on the MSR problem class and standard ITHACA utilities:
#include "usmsrProblem.H"
#include "ReducedUnsteadyMSR.H"
#include "LRSensitivity.H"
#include "Tm.H"
#include "Ptot.H"
Header file of the ITHACAstream class, it contains the implementation of several methods for input ou...
Additional headers include Eigen for linear algebra and chrono for timing.
Implementation of the msr class
The tutorial defines a child of usmsrProblem named msr. The constructor initializes references to all relevant fields (velocity, pressure, flux, precursors, temperature, decay constants, eddy viscosity, etc.) and reads sensitivity parameters:
{
public:
explicit msr(
int argc,
char* argv[])
:
U(_U()),
p(_p()),
flux(_flux()),
prec1(_prec1()),
...
TXS(_TXS())
{}
volVectorField& U;
volScalarField& p;
void offlineSolve()
{
{
List<scalar> mu_now(3);
for (
int i = 0; i <
mu.cols(); i++)
{
}
}
else
{
}
}
};
void readMSRfields()
Method to read all the fieds of the MSR problem in the offline folder, it also reads mu_samples matri...
void restart()
method to set all fields back to values in 0 folder
bool offline
Boolean variable, it is 1 if the Offline phase has already been computed, else 0.
Eigen::MatrixXd mu
Row matrix of parameters.
void truthSolve()
Perform a TruthSolve.
The offline solver cycles through parameter samples, updating the fluid properties and performing a full-order run. If offline is true, precomputed fields are simply read from disk.
The code also defines auxiliary classes (Tmlocal, Ploct, etc.) for sensitivity computations of mean temperature and total power. In particular, Tmlocal is for the domain-averaged temperature:
{
public:
explicit Tmlocal(
int argc,
char* argv[],
int Nsampled)
:
Tm(argc, argv, Nsampled),
T(_T())
{}
volScalarField& T;
};
Ploct computes the time-dependent counterpart of the total power, inherited from Ptot_time. It stores a reference to the power density field:
{
public:
explicit Ploct(
int argc,
char* argv[],
int Nsampled)
:
powerDens(_powerDens())
{}
volScalarField& powerDens;
};
Plocal is similar to Ploct, but likely for the non-time-indexed version of the total power figure of merit:
{
public:
explicit Plocal(
int argc,
char* argv[],
int Nsampled)
:
Ptot(argc, argv, Nsampled),
powerDens(_powerDens())
{}
volScalarField& powerDens;
};
Finally, Tmloct is the time-dependent version of the mean-temperature output object.
{
public:
explicit Tmloct(
int argc,
char* argv[],
int Nsampled)
:
T(_T())
{}
volScalarField& T;
};
Main function
The main routine constructs the msr object, sets parameter ranges and sampling (three parameters and 31 time samples by default), defines inlet patch indices and the simulation time settings:
prova.inletIndex.resize(1, 2);
prova.inletIndex(0, 0) = 0;
prova.inletIndex(0, 1) = 0;
prova.inletIndexT.resize(4, 1);
prova.inletIndexT(0, 0) = 0;
prova.inletIndexT(1, 0) = 1;
prova.inletIndexT(2, 0) = 2;
prova.inletIndexT(3, 0) = 3;
prova.Pnumber = 3;
prova.Tnumber = 31;
prova.setParameters();
int central = static_cast<int>(prova.Tnumber / 2);
double nu0 = 2.46E-06;
double betatot0 = 3.218E-05;
double dlam30 = 3.58e-04;
prova.mu_range(0, 0) = 1.1 * nu0;
prova.mu_range(0, 1) = 0.9 * nu0;
prova.mu_range(1, 0) = 1.1 * betatot0;
prova.mu_range(1, 1) = 0.9 * betatot0;
prova.mu_range(2, 0) = 1.1 * dlam30;
prova.mu_range(2, 1) = 0.9 * dlam30;
The parameters are sampled in a random way, and a central reference point is forced:
prova.genRandPar();
prova.mu(0, central) = nu0;
prova.mu(1, central) = betatot0;
prova.mu(2, central) = dlam30;
double tstart = std::time(0);
Eigen::MatrixXd arange(prova.Pnumber, 2);
Eigen::MatrixXd mu_f = prova.mu;
It then runs the offline stage and collects information on the elapsed time:
if (prova.offline == false)
{
for (int i = 0; i < prova.Pnumber; i++)
{
arange(i, 0) = prova.mu.row(i).minCoeff();
arange(i, 1) = prova.mu.row(i).maxCoeff();
}
}
else
{
}
prova.offlineSolve();
prova._nu().value() = nu0;
prova.change_viscosity(nu0);
double tend = std::time(0);
double deltat_offline;
deltat_offline = difftime(tend, tstart);
if (prova.offline == false)
{
std::ofstream diff_t("./ITHACAoutput/t_offline=" + name(deltat_offline));
}
Info << "Offline stage time= " << deltat_offline << endl;
void exportMatrix(Eigen::Matrix< T, -1, dim > &matrix, word Name, word type, word folder)
Export the reduced matrices in numpy (type=python), matlab (type=matlab) and txt (type=eigen) format ...
List< Eigen::MatrixXd > readMatrix(word folder, word mat_name)
Read a three dimensional matrix from a txt file in Eigen format.
After offline sampling, the lifting function for the velocity and the pressure is computed, then the reduced basis can be computed and the reduced PPE operators are extrated:
prova.liftSolve();
prova.liftSolveT();
prova.homogenizeU();
prova.homogenizeT();
prova.msrgetModesEVD();
Eigen::VectorXi NPrec(8);
NPrec(0) = prova.NPrecproj1;
NPrec(1) = prova.NPrecproj2;
NPrec(2) = prova.NPrecproj3;
NPrec(3) = prova.NPrecproj4;
NPrec(4) = prova.NPrecproj5;
NPrec(5) = prova.NPrecproj6;
NPrec(6) = prova.NPrecproj7;
NPrec(7) = prova.NPrecproj8;
Eigen::VectorXi NDec(3);
NDec(0) = prova.NDecproj1;
NDec(1) = prova.NDecproj2;
NDec(2) = prova.NDecproj3;
prova.projectPPE("./Matrices", prova.NUproj, prova.NPproj, prova.NFluxproj,
NPrec, prova.NTproj, NDec, prova.NCproj);
Then, the BC conditions for the velocity and temperature are set, the ROM object is initialized:
double umedio = 2.46E-03;
Eigen::MatrixXd Twall(4, 2);
Twall.setZero();
Twall(0, 0) = 850;
Twall(1, 0) = 950;
Twall(2, 0) = 950;
Twall(3, 0) = 950;
ridotto.tstart = 0;
ridotto.finalTime = 10;
ridotto.dt = 0.1;
Now the velocity and temperature inputs are initialized and passed online to the reduce order solver:
Eigen::MatrixXd vel_now(1, 1);
vel_now(0, 0) = umedio;
Eigen::MatrixXd temp_now = Twall;
The online ROM parameters using the central reference are now assigned to the ROM:
Eigen::VectorXd mu_on(3);
ridotto.nu = mu_f(0, central);
ridotto.btot = mu_f(1, central);
ridotto.b1 = prova.r1 * ridotto.btot;
ridotto.b2 = prova.r2 * ridotto.btot;
ridotto.b3 = prova.r3 * ridotto.btot;
ridotto.b4 = prova.r4 * ridotto.btot;
ridotto.b5 = prova.r5 * ridotto.btot;
ridotto.b6 = prova.r6 * ridotto.btot;
ridotto.b7 = prova.r7 * ridotto.btot;
ridotto.b8 = prova.r8 * ridotto.btot;
ridotto.dl3 = mu_f(2, central);
int ref_start = 465;
mu_on(0) = ridotto.nu;
mu_on(1) = ridotto.btot;
mu_on(2) = ridotto.dl3;
The ROM is then solved, and the solution is reconstructed and exported.
ridotto.solveOnline(vel_now, temp_now, mu_on, ref_start);
ridotto.reconstructAP("./ITHACAoutput/Reconstruction/", 10);
int tf = 21;
int tf_fom = 31;
int c = 0;
Some figures of merit are computed for the FOM database (average temperature and total power):
Eigen::MatrixXd err(10, tf);
Eigen::MatrixXd TmFOM(prova.Tnumber, tf);
Eigen::MatrixXd PtotFOM(prova.Tnumber, tf);
Eigen::MatrixXd TmROM(prova.Tnumber, tf);
Eigen::MatrixXd PtotROM(prova.Tnumber, tf);
The errors are then computed:
Info << "Computing errors on reference case..." << endl;
for (int i = ref_start; i < ref_start + tf; i++)
{
ridotto.FLUXREC[c]);
ridotto.PREC1REC[c]);
ridotto.PREC4REC[c]);
ridotto.PREC8REC[c]);
ridotto.DEC1REC[c]);
ridotto.DEC3REC[c]);
ridotto.POWERDENSREC[c]);
ridotto.TXSREC[c]);
c++;
}
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.
The same figures of merit are also computed for the ROM are then collected using the above-defined functions:
Tmloct Tmedia(argc, argv, prova.Tnumber);
Ploct Ptotale(argc, argv, prova.Tnumber);
c = 0;
for (int i = 0; i < tf; i++)
{
Tmedia.buildMO(folder, i);
Ptotale.buildMO(folder, i);
TmROM.col(c) = Tmedia.modelOutput;
PtotROM.col(c) = Ptotale.modelOutput;
c++;
}
"./ITHACAoutput");
"./ITHACAoutput");
Then, the sensitivity analysis performed using the LRSensitivity utilities provided by the library. Here, the sampling points are defined and the ROM is re-initialized:
folder = {"./ITHACAoutput/Sensitivity/ModelOutput/"};
int Nparameters = prova.Pnumber;
std::vector<std::string> pdflist = {"normal", "normal", "normal"};
int samplingPoints = 1000;
analisi.trainingRange = arange;
Eigen::MatrixXd Mp(Nparameters, 2);
Mp(0, 0) = nu0;
Mp(0, 1) = nu0 * 0.1 / 3;
Mp(1, 0) = betatot0;
Mp(1, 1) = betatot0 * 0.1 / 3;
Mp(2, 0) = dlam30;
Mp(2, 1) = dlam30 * 0.1 / 3;
analisi.buildSamplingSet(pdflist, Mp);
analisi.getXstats();
int counter = 0;
tstart = std::time(0);
ridotto.clearFields();
Then, the ROM is solved in a loop over all sampled parameters for sensitivity purpose, and the solution is reconstructed:
for (int j = 0; j < samplingPoints; j++)
{
ridotto.nu = analisi.MatX(j, 0);
ridotto.btot = analisi.MatX(j, 1);
ridotto.b1 = prova.r1 * ridotto.btot;
ridotto.b2 = prova.r2 * ridotto.btot;
ridotto.b3 = prova.r3 * ridotto.btot;
ridotto.b4 = prova.r4 * ridotto.btot;
ridotto.b5 = prova.r5 * ridotto.btot;
ridotto.b6 = prova.r6 * ridotto.btot;
ridotto.b7 = prova.r7 * ridotto.btot;
ridotto.b8 = prova.r8 * ridotto.btot;
ridotto.dl3 = analisi.MatX(j, 2);
mu_on(0) = ridotto.nu;
mu_on(1) = ridotto.btot;
mu_on(2) = ridotto.dl3;
folder.append(std::to_string(counter));
ridotto.solveOnline(vel_now, temp_now, mu_on, ref_start);
ridotto.reconstructAP(folder, 1000);
folder = {"./ITHACAoutput/Sensitivity/ModelOutput/"};
ridotto.clearFields();
counter++;
}
Finally, the figures of merit, timings info, and LRSensitivity analysis objects are collected:
tend = std::time(0);
int Ntot = samplingPoints;
double deltat_online = difftime(tend, tstart);
std::ofstream diff_ton("./ITHACAoutput/t_onlineSA_" + name(Ntot) + "=" + name(
deltat_online));
Tmlocal TmediaSA(argc, argv, Ntot);
Plocal PtotSA(argc, argv, Ntot);
TmediaSA.buildMO(folder);
PtotSA.buildMO(folder);
analisi.M = autoPtr<FofM>(& TmediaSA);
analisi.load_output();
analisi.getYstat();
Info << "Mean value and variance of the output:" << endl;
Info << analisi.Ey << "\t" << analisi.Vy << endl;
Info << "-------------" << endl;
analisi.getBetas();
Info << "analisi regression coefficients:" << endl;
Info << analisi.betas << endl;
Info << "-------------" << endl;
analisi.assessQuality();
Info << "quality: " << analisi.QI << endl;
Eigen::MatrixXd saveyout(samplingPoints, 1);
Eigen::MatrixXd saveymodel(samplingPoints, 1);
saveyout.col(0) = analisi.y;
saveymodel.col(0) = analisi.ylin;
Eigen::MatrixXd coeffsT(4, 3);
coeffsT.setZero();
coeffsT(0, 0) = analisi.Ey;
coeffsT(0, 1) = analisi.Vy;
coeffsT(0, 2) = analisi.QI;
coeffsT.row(1) = analisi.EX;
coeffsT.row(2) = analisi.VX;
coeffsT.row(3) = analisi.betas;
analisi.M = autoPtr<FofM>(& PtotSA);
analisi.load_output();
analisi.getYstat();
Info << "Mean value and variance of the output:" << endl;
Info << analisi.Ey << "\t" << analisi.Vy << endl;
Info << "-------------" << endl;
analisi.getBetas();
Info << "analisi regression coefficients:" << endl;
Info << analisi.betas << endl;
Info << "-------------" << endl;
analisi.assessQuality();
Info << "quality: " << analisi.QI << endl;
Eigen::MatrixXd saveyoutP(samplingPoints, 1);
Eigen::MatrixXd saveymodelP(samplingPoints, 1);
saveyoutP.col(0) = analisi.y;
saveymodelP.col(0) = analisi.ylin;
Eigen::MatrixXd coeffsP(4, 3);
coeffsP.setZero();
coeffsP(0, 0) = analisi.Ey;
coeffsP(0, 1) = analisi.Vy;
coeffsP(0, 2) = analisi.QI;
coeffsP.row(1) = analisi.EX;
coeffsP.row(2) = analisi.VX;
coeffsP.row(3) = analisi.betas;
return 0;
The plain code
The plain code is available here.