thermal.h
📄 View in API Reference (Doxygen) · View on basilisk.fr
Requires: poisson.h · compressible/two-phase.h
Test cases (1): shrinking
Thermal effects extension for the compressible two-phase solver
This extension of the two-phase compressible solver is described in detail in Saade et al, 2023.
This solves the two-phase compressible Navier-Stokes equations including the total energy equation.
an advection equation for the volume fraction $f$
The term in blue in the energy equation is the heat flux which is added by this extension. The other terms are treated by the two-phase compressible solver. The heat flux is given by Fourier's relation as
where $\kappa_i$ is the thermal conductivity and $T$ is the temperature field.
The temperature and pressure fields are then coupled through the two equations (8 and 10 in Saade et al, 2023)
with $c_p$ the specific heat capacity and $\beta$ the thermal expansion coefficient.
with $\gamma$ the ratio of specific heats.
The system is closed by specifying an Equation Of State (EOS) relating $p$, $\rho$ and $T$.
In addition to the primary variables of the compressible two-phase solver we have the temperature field...
scalar T[];
... and the thermal conductivities and specific heat capacities.
double kappa1 = 0., kappa2 = 0.;
double cp1 = 0., cp2 = 0.;
These functions are provided by the Equation Of State. See for example the Noble-Able Stiffened-Gas Equation Of State.
extern double average_temperature (Point point, double p);
extern double thermal_expansion (Point point);
Phase-averaged thermal conductivity
By default the arithmetic average of the two phase conductivities is used to compute the face-centered thermal conductivity.
const face vector kappa0[] = {0,0,0};
(const) face vector kappa = kappa0;
#ifndef kappa
#define kappa(f) (clamp(f,0.,1.)*(kappa1 - kappa2) + kappa2)
#endif
If the thermal conductivities are not-zero, we need to allocate a new field.
event defaults (i = 0)
{
if (kappa1 || kappa2)
kappa = new face vector;
}
Coupled Poisson-Helmholtz equations for temperature and pressure
The robustness of the solver relies on solving equations (8) and (10) in a strongly-coupled manner with the multigrid solver. After temporal discretisation this leads to the following coupled Poisson--Helmholtz system for the temperature $T^{n+1}$ and pressure $p^{n+1}$ (see eqs. 25 and 26 in Saade et al, 2023)
We allocate fields to store the provisionary temperature $T^n$ and the coefficients.
scalar Ts[], lambda1[], lambda2[], lambda4[];
We then define the corresponding relaxation and residual functions which will be used by the multigrid solver.
#include "poisson.h" [api]
struct Thermal {
(const) face vector alpha;
(const) scalar lambda;
double tolerance;
int nrelax, minlevel;
scalar * res;
};
static void relax_thermal (scalar * al, scalar * bl, int l, void * data)
{
scalar T = al[0], p = al[1], rhsT = bl[0], rhsp = bl[1];
struct Thermal * tp = (struct Thermal *) data;
(const) face vector alpha = tp->alpha;
(const) scalar lambda = tp->lambda;
#if JACOBI
scalar cT[], cp[];
#else
scalar cT = T, cp = p;
#endif
foreach_level_or_leaf (l) {
double numT = sq(Delta)*(lambda2[]*p[] - rhsT[]), denT = - lambda1[]*sq(Delta);
double nump = - sq(Delta)*rhsp[], denp = - lambda[]*sq(Delta);
foreach_dimension() {
numT += kappa.x[1]*T[1] + kappa.x[]*T[-1];
denT += kappa.x[1] + kappa.x[];
nump += alpha.x[1]*p[1] + alpha.x[]*p[-1];
nump += lambda4[]*(kappa.x[1]*(T[1] - T[]) - kappa.x[]*(T[] - T[-1]));
denp += alpha.x[1] + alpha.x[];
}
cT[] = numT/denT;
cp[] = nump/denp;
}
#if JACOBI
foreach_level_or_leaf (l) {
T[] = (T[] + 2.*cT[])/3.;
p[] = (p[] + 2.*cp[])/3.;
}
#endif
}
static double residual_thermal (scalar * al, scalar * bl, scalar * resl, void * data)
{
scalar T = al[0], p = al[1], rhsT = bl[0], rhsp = bl[1], resT = resl[0], resp = resl[1];
struct Thermal * tp = (struct Thermal *) data;
(const) face vector alpha = tp->alpha;
(const) scalar lambda = tp->lambda;
double maxres = 0.;
#if TREE
/* conservative coarse/fine discretisation (2nd order) */
face vector gradT[], gradp[];
foreach_face() {
gradT.x[] = kappa.x[]*face_gradient_x (T, 0);
gradp.x[] = alpha.x[]*face_gradient_x (p, 0);
}
foreach (reduction(max:maxres)) {
resT[] = rhsT[] - lambda1[]*T[] - lambda2[]*p[];
resp[] = rhsp[] - lambda[]*p[];
foreach_dimension() {
resT[] -= (gradT.x[1] - gradT.x[])/Delta;
resp[] -= (gradp.x[1] - gradp.x[])/Delta;
resp[] -= lambda4[]*(gradT.x[1] - gradT.x[])/Delta;
}
#else // !TREE
/* "naive" discretisation (only 1st order on trees) */
foreach (reduction(max:maxres)) {
resT[] = rhsT[] - lambda1[]*T[] - lambda2[]*p[];
resp[] = rhsp[] - lambda[]*p[];
foreach_dimension() {
resT[] += (kappa.x[0]*face_gradient_x (T, 0) -
kappa.x[1]*face_gradient_x (T, 1))/Delta;
resp[] += (alpha.x[0]*face_gradient_x (p, 0) -
alpha.x[1]*face_gradient_x (p, 1))/Delta;
resp[] += lambda4[]*(kappa.x[0]*face_gradient_x (T, 0) -
kappa.x[1]*face_gradient_x (T, 1))/Delta;
}
#endif // !TREE
double maxr = max(fabs(resT[]), fabs(resp[]));
if (maxr > maxres)
maxres = maxr;
}
return maxres;
}
This is the interface for the coupled solver.
mgstats poisson_thermal (scalar p, scalar rhs,
(const) face vector alpha,
(const) scalar lambda,
double tolerance = 0.,
int nrelax = 4,
int minlevel = 0,
scalar * res = NULL)
{
double defaultol = TOLERANCE;
if (tolerance)
TOLERANCE = tolerance;
restriction ({kappa, alpha, lambda1, lambda2, lambda, lambda4});
struct Thermal tp = { alpha, lambda, tolerance, nrelax, minlevel, res };
mgstats s = mg_solve ({T,p}, {Ts,rhs}, residual_thermal, relax_thermal,
&tp, nrelax, res, minlevel = max(1, minlevel));
if (tolerance)
TOLERANCE = defaultol;
return s;
}
Finally, we overload the *poisson()* function called by [two-phase.h]() with our new function.
#define poisson(...) poisson_thermal(__VA_ARGS__)
#include "compressible/two-phase.h" [api]
#undef poisson
Computation of the provisional temperature and Poisson--Helmholtz parameters
This is done before the projection, which will call the coupled Poisson--Helmholtz solver.
event acceleration (i++)
{
foreach() {
Ts[] = average_temperature (point, ps[]);
We compute $\lambda_1$, $\lambda_2$, $\lambda_4$ and r.h.s. for the temperature which will be used by the coupled Poisson--Helmholtz solver. The $\lambda$ parameter and r.h.s. for the pressure are computed by the two-phase compressible solver.
double rhocp = cp1*frho1[] + cp2*frho2[];
lambda1[] = - cm[]*rhocp/dt;
double beta = thermal_expansion (point);
lambda2[] = cm[]*beta*Ts[]/dt;
lambda4[] = beta/rhocp/dt;
scalar rhsT = Ts;
rhsT[] = lambda1[]*Ts[] + lambda2[]*ps[];
}
We also compute the face-averaged thermal conductivity.
if (kappa1 || kappa2)
foreach_face() {
double ff = (f[] + f[-1])/2.;
kappa.x[] = fm.x[]*kappa(ff);
}
}
Heat flux contribution to the total energy
This adds the $-\nabla\cdot\mathbf{q}_i$ term of the energy equation.
event end_timestep (i++)
{
if (kappa1 || kappa2) {
face vector gradTv = kappa;
foreach_face()
gradTv.x[] = kappa.x[]*face_gradient_x (T,0);
foreach () {
double energy = 0.;
foreach_dimension()
energy += gradTv.x[1] - gradTv.x[];
energy *= dt/(Delta*cm[]);
double fc = clamp(f[],0,1);
fE1[] += energy*fc;
fE2[] += energy*(1. - fc);
}
}
}
References
~~~bib @hal{saade2023, hal-03950917} ~~~