Implementation of Allen-Cahn coupled with temperature for phase change problems

Objective of this 2nd tutorial

The starting point is the kernel ADE_for_AC-Temp_Tutorial which simulates (again) the Advection-Diffusion Equation (ADE) with the lattice Boltzmann method:

(257)\[\frac{\partial c}{\partial t}+\boldsymbol{\nabla}\cdot(\boldsymbol{u}c)=D\boldsymbol{\nabla}^{2}c\]

Mathematical model

Partial Derivative Equations

In this tutorial we will implement a model to simulate a phase change problem composed of two coupled PDEs, a first one for the evolution of dimensionless temperature:

(258)\[\frac{\partial\theta}{\partial t}=D\boldsymbol{\nabla}^{2}\theta-\frac{\partial\phi}{\partial t}\]

where \(\theta\) is a dimensionless temperature, \(D\) is the diffusivity parameter and \(\phi\) the phase-field. The second equation is for the evolution of phase-field:

(259)\[\frac{\partial\phi}{\partial t}=M_{\phi}\boldsymbol{\nabla}^{2}\phi-\frac{16M_{\phi}}{W^{2}}\phi(1-\phi)(1-2\phi)-\frac{4D}{\mathscr{A}W^{2}}(\theta_{I}-\theta)\phi(1-\phi)\]

where \(M_{\phi}\) is the mobility parameter, \(W\) is the interface thickness, \(\mathscr{A}=10/48\) is known parameter and \(\theta_I\) the temperature at interface.

Boundary and initial conditions

The model will be validated with an analytical solution of the 1D Stefan problem which is obtained for Dirichlet boundary conditions.

For temperature equation
  • The boundary conditions for the temperature equation are:

\[\begin{split}\theta(0,t)&=&\theta_w\\ \theta(L,t)&=&\theta_{\infty}\end{split}\]

where the index \(w\) means wall and \(L\) is the domain length.

  • The initial condition is a constant temperature

\[\theta(\boldsymbol{x},0)=\theta_{\infty}\]
For phase-field equation
  • The boundary conditions for the phase-field equation are:

\[\begin{split}\phi(0,t)&=&0\\ \phi(L,t)&=&1\end{split}\]
  • The initial condition is a hyperbolic tangent solution at \(x_0=0\)

\[\phi(\boldsymbol{x},0)=\frac{1}{2}\left[1+\text{tanh}\left(\frac{2(x-x_0)}{W}\right)\right]\]

Objective

In this tutorial we will see how to

  • add a new equation and all related stages (setup_collider, update, boundary conditions, etc.)

  • add source terms of Eqs. () and ().

The verification will be carried out with one analytical solution of the Stefan problem (see section 4). We will

  • compare the outputs of LBM_Saclay with an analytical solution

  • see the impact of linear and harmonic interpolation of diffusivity \(D(\phi)\)

1. Create a new kernel ACT for Allen-Cahn/Temperature

Make the new kernel ACT in a new folder Allen-Cahn-Temp_Tutorial

That stage 1. is the same than the first tutorial on Cahn-Hilliard.

  • Copy the folder ADE_for_AC-Temp_Tutorial and rename it Allen-Cahn-Temp_Tutorial

  • Modify _ADE (for Advection-Diffusion Equation) by _ACT (for Allen-Cahn/Temperature).

Details of commands

Copy the ADE kernel template and rename all files with new extension _ACT

  • Copy and rename the folder ADE_for_AC-Temp_Tutorial which is contained in kernels/Templates_for_Tutorials.

Commands
$ cd LBM_Saclay_Rech-Dev/src/kernels
$ cp -r Templates_for_Tutorials/ADE_for_CH-Tutorial ./Allen-Cahn-Temp_Tutorial

Remarks

  • Your new developments will be performed in the folder Cahn-Hilliard_Tutorial

  • The folder name ADE_for_AC-Temp_Tutorial will be used for compilation

  • All files have the extension _ADE for Advection Diffusion Equation. Rename them with _ACT for Allen-Cahn Temperature.

Commands
$ cd Cahn-Hilliard_Tutorial

$ mv Index_ADE.h Index_ACT.h
$ mv LBMScheme_ADE.h LBMScheme_ACT.h
$ mv Models_ADE.h Models_ACT.h
$ mv Problem_ADE.h Problem_ACT.h

Edit them and change strings _ADE with _ACT

  1. Change strings _ADE with _ACT

    • Open all files in your favorite editor (e.g. geany or codium).

    • Search in all files the strings _ADE and replace them with _ACT (Ctrl H with geany).

  2. Change the problem name: in file Setup.h modify the problem name ADE with ACT

    register_problem<Problem, ModelParams::Tag2Quadra>("ADE", "base");
    
    Modification with ACT
    register_problem<Problem, ModelParams::Tag2Quadra>("ACT", "base");
    

    Remark

    Your new problem will be named ACT inside your .ini input file i.e. in section [lbm] your problem must be:

    [lbm]
    problem=ACT
    

Compile your new kernel

  • Generate your makefile (only once)

Commands
$ cd LBM_Saclay_Rech-Dev
$ mkdir build_ACT
$ cd build_ACT
$ cmake -DKokkos_ENABLE_OPENMP=ON -DPROBLEM=Allen-Cahn-Temp_Tutorial ..
  • Compile

Command
$ make -j 22

Run with input files of ADE

  • Execute your new kernel ACT of LBM_Saclay either with the simple diffusion of gaussian or with the advected gaussian in the folder run_training_lbm/Tutorial_Stefan

Commands

Go to the directory

$ cd LBM_Saclay_Rech-Dev/run_training_lbm/Tutorial02_AC-Temp

Run simple diffusion of gaussian

$ ../../build_ACT/src/LBM_saclay TestCase_Gaussian_ADE.ini
  • Check the solutions with paraview.

2. Add a new equation in your kernel ACT

The mathematical model is composed of two coupled PDE. It is necessary to add one supplementary equation.

Problem

The number of equation is currently 1

Base::nbEqs = 1;

const int isize = params.isize;
const int jsize = params.jsize;
const int ksize = params.ksize;
  • Modify it for two equations

Solution
Base::nbEqs = 2;

Function init_f()

The initialization is currently performed only for one equation:

if (params.collisionType1 == BGK) {
     init1eq<EquationTag1, BGKCollider<dim, npop>>();
} else if (params.collisionType1 == MRT) {
     init1eq<EquationTag1, MRTCollider<dim, npop>>();
}
  • Add a new block if - else if for EquationTag2

Solution
if (params.collisionType1 == BGK) {
  init1eq<EquationTag1, BGKCollider<dim, npop>>();
} else if (params.collisionType1 == MRT) {
  init1eq<EquationTag1, MRTCollider<dim, npop>>();
}

if (params.collisionType1 == BGK) {
  init1eq<EquationTag2, BGKCollider<dim, npop>>();
} else if (params.collisionType1 == MRT) {
  init1eq<EquationTag2, MRTCollider<dim, npop>>();
}

Function update_f()

The update is also performed only for one equation:

if (params.collisionType1 == BGK) {
  update1eq<EquationTag1, BGKCollider<dim, npop>>();
} else if (params.collisionType1 == MRT) {
  update1eq<EquationTag1, MRTCollider<dim, npop>>();
}
  • Add a new block if - else if for EquationTag2

Solution
if (params.collisionType1 == BGK) {
  update1eq<EquationTag1, BGKCollider<dim, npop>>();
} else if (params.collisionType1 == MRT) {
  update1eq<EquationTag1, MRTCollider<dim, npop>>();
}

if (params.collisionType1 == BGK) {
  update1eq<EquationTag2, BGKCollider<dim, npop>>();
} else if (params.collisionType1 == MRT) {
  update1eq<EquationTag2, MRTCollider<dim, npop>>();
}

Function make_boundaries()

The update of bounday conditions is also performed for a single equation

this->bcPerMgr.make_boundaries(scheme.f1, BOUNDARY_EQUATION_1);
  • Add a new line for f2 and BOUNDARY_EQUATION_2

Solution
this->bcPerMgr.make_boundaries(scheme.f1, BOUNDARY_EQUATION_1);
this->bcPerMgr.make_boundaries(scheme.f2, BOUNDARY_EQUATION_2);

Function struct LBMScheme

  • Add a new tag tagPHI for EquationTag2

Solution
EquationTag1 tagC;
EquationTag2 tagPHI;
  • Add two empty functions called setup_collider for EquationTag2 with respectively BGK and MRT collisions

Solution
KOKKOS_INLINE_FUNCTION
void setup_collider(EquationTag2 tag, const IVect<dim>& IJK, BGK_Collider& collider) const
{
}

KOKKOS_INLINE_FUNCTION
void setup_collider(EquationTag2 tag, const IVect<dim>& IJK, MRT_Collider& collider) const
{

Remark

Those two functions are empty (but declared). The first one will be filled below.

Function make_boundary

The boundary conditions are applied only for Advection-diffusion equation

// ADE boundary
if (Base::params.boundary_types[BOUNDARY_EQUATION_1][faceId] == BC_ANTI_BOUNCE_BACK) {
  real_t boundary_value = Base::params.boundary_values[BOUNDARY_CONCENTRATION][faceId];

  for (int ipop = 0; ipop < npop; ++ipop)
     this->compute_boundary_antibounceback(tagC, faceId, IJK, ipop, boundary_value);
}
else if (Base::params.boundary_types[BOUNDARY_EQUATION_1][faceId] == BC_ZERO_FLUX) {
  for (int ipop = 0; ipop < npop; ++ipop)
     this->compute_boundary_bounceback(tagC, faceId, IJK, ipop, 0.0);
}
  • Add a new block if - else if for BOUNDARY_EQUATION_2 and tagPHI with keywork BOUNDARY_PHASE_FIELD

Solution
if (Base::params.boundary_types[BOUNDARY_EQUATION_2][faceId] == BC_ANTI_BOUNCE_BACK) {
   real_t boundary_value = Base::params.boundary_values[BOUNDARY_PHASE_FIELD][faceId];
   for (int ipop = 0; ipop < npop; ++ipop)
   this->compute_boundary_antibounceback(tagPHI, faceId, IJK, ipop, boundary_value);
}
else if (Base::params.boundary_types[BOUNDARY_EQUATION_2][faceId] == BC_ZERO_FLUX) {
   for (int ipop = 0; ipop < npop; ++ipop)
   this->compute_boundary_bounceback(tagPHI, faceId, IJK, ipop, 0.0);
}

3. Modifications inside each file of your kernel ACT

Advice

During your implementation, it is strongly recommended to compile regularly to detect any typos. See Advice

Declaration of new fields

In enum ComponentIndex, only the indices for macroscopic fields such as concentration \(c\) (IC) and velocity components \(u_x\) (IU), \(u_y\) (IV) & \(u_z\) (IW) are declared:

enum ComponentIndex {
  IU     , /*!< X velocity / momentum index */
  IV     , /*!< Y velocity / momentum index */
  IW     , /*!< Z velocity / momentum index */
  IC     , /*!< Phase field index */
  COMPONENT_SIZE /*!< invalid index, just counting number of fields */
};
  • Replace IC by ITEMP and add the new indices for chemical potential IPHI and laplacian IDPHIDT.

Solution
enum ComponentIndex {
  IU     , /*!< X velocity / momentum index */
  IV     , /*!< Y velocity / momentum index */
  IW     , /*!< Z velocity / momentum index */
  ITEMP  , /*!< Temperature index */
  IPHI   , /*!< Phase field index */
  IDPHIDT,
  COMPONENT_SIZE /*!< invalid index, just counting number of fields */
};

Add new outputs

In struct index2names only the strings for concentration and velocity components are written:

map[IC ] = "comp";
map[IU ] = "vx";
map[IV ] = "vy";
map[IW ] = "vz";
  • Modify for temperature and add new outputs for \(\phi\) and \(\partial \phi/\partial t\).

Solution
map[ITEMP  ] = "temp";
map[IU     ] = "vx"  ;
map[IV     ] = "vy"  ;
map[IW     ] = "vz"  ;
map[IPHI   ] = "phi" ;
map[IDPHIDT] = "dphidt";

.. admonition:: Remark
   :class: important

   The keywords ``temp``, ``phi``, and ``dphidt`` will be used in the ``.ini`` input file to write those fields.

Add specific functions for temperature equation

  • Add a function Source_TEMP for the source term of temperature equation

Solution Source_TEMP
// =======================================================
// Source term for temperature equation

KOKKOS_INLINE_FUNCTION
real_t Source_TEMP(EquationTag1 tag, const LBMState& lbmState) const
{
  real_t dphidt = lbmState[IDPHIDT];
  return -dphidt;
}
  • Modify the name of collision rate function for temperature equation

Solution tau_TEMP
KOKKOS_INLINE_FUNCTION
real_t tau_TEMP(EquationTag1 tag, const LBMState& lbmState) const
{
  real_t tau = 0.5 + (3.0 * D * dt / (dx * dx));
  return (tau);
}

Warning

Because IC has been replaced by ITEMP in enum ComponentIndex (file Index_ACT.h), don’t forget to find and replace in all functions lbmState[IC] by lbmState[ITEMP].

Add specific functions for phase-field equation

  • Add a new function M0_PHI

Solution function M0_PHI
// =======================================================
// zero order moment for phase field
KOKKOS_INLINE_FUNCTION
real_t M0_PHI(EquationTag2 tag, const LBMState& lbmState) const
{
  return lbmState[IPHI];
}
  • Add a new function M2_PHI

Solution function M2_PHI
// =======================================================
// Second order moment for phase field
KOKKOS_INLINE_FUNCTION
real_t M2_PHI(EquationTag2 tag, const LBMState& lbmState) const
{
  return lbmState[IPHI];
}
  • Add a source term.

Solution Source_PHI
// =======================================================
// Source term for phase field
KOKKOS_INLINE_FUNCTION
real_t Source_PHI (EquationTag2 tag, const LBMState& lbmState) const
{
  real_t phi     = lbmState[IPHI];
  real_t temp    = lbmState[ITEMP];
  real_t tempI   = 0.0;
  real_t Coeff_A = 10.0/48.0;
  real_t S       = - (16.0*mobility/(W*W)) * phi * (1.0-phi) * (1-2*phi)
                   -((4.0*D)/(Coeff_A*W*W))*(tempI-temp)*phi*(1-phi);
  return S;
}
  • Add a new function tau_PHI for collision rate using the mobility \(M_\phi\) of Allen-Cahn equation

Solution tau_PHI
// =======================================================
// relaxation coef for LBM scheme of phase field equation
KOKKOS_INLINE_FUNCTION
real_t tau_PHI(EquationTag2 tag, const LBMState& lbmState) const
{
  real_t tau = 0.5 + (3.0 * (mobility) * dt / (dx * dx));
  return (tau);
}

Warning

Don’t forget to read and declare the parameter mobility in ModelParams.

  • Add a new hyperbolic tangent function phi0 for initial condition

Solution phi0
// Hyperbolic tangent for g1 double-well
KOKKOS_INLINE_FUNCTION real_t phi0(real_t x) const {
  return 0.5*(1.0+tanh(sign * 2.0 * x / W ));
}

Read and declare all parameters in ModelParams

The Allen-Cahn equation involves three paramters: the interface thickness \(W\) and the mobility \(M_\phi\) and the interface temperature \(\theta_I\).

  • Read and declare Allen-Cahn parameters in ModelParams

Solution
mobility = configMap.getFloat("params", "mobility", 1.0);
W        = configMap.getFloat("params", "W"       , 1.0);
tempI    = configMap.getFloat("params", "tempI"   , 0.0);

undercooling = configMap.getFloat("init", "undercooling", 0.0);

Don’t forget to declare them as real_t at the end of ModelParams

real_t mobility, W, tempI, undercooling;

Add condition for initial condition

Currently only one condition exits for intial condition:

if (initTypeStr == "gaussian") initType = ADE_GAUSSIAN;
  • Add the necessary condition for one initialization

Solution
if (initTypeStr == "gaussian") initType = ADE_GAUSSIAN;
else if (initTypeStr == "vertical") initType = PHASE_FIELD_INIT_VERTICAL;

Warning

Don’t forget to declare all keywords PHASE_FIELD_INIT_VERTICAL in file InitConditionsTypes.h

Function setup_collider with BGK for temperature equation

The function setup_collider is written for solving Advection-Diffusion Equation.

const real_t M0     = Model.M0_TEMP(tagC, lbmState);
const RVect<dim> uc = Model.M1_TEMP<dim>(tagC, lbmState);
const real_t M2     = Model.M2_TEMP(tagC, lbmState);
// ipop = 0
collider.f[0]   = Base::get_f_val(tag, IJK, 0);
collider.S0[0]  = 0.0 ;
collider.feq[0] = w[0]*M0;
// ipop > 0
for (int ipop = 1; ipop < npop; ++ipop) {
  collider.f[ipop]   = this->get_f_val(tag, IJK, ipop);
  collider.S0[ipop]  = 0.0 ;
  collider.feq[ipop] = w[ipop] * (M2 + c_cs2 * Base::compute_scal(ipop, uc));
}
  • Modify it for the source term.

Solution
const real_t M0     = Model.M0_TEMP(tagC, lbmState);
const RVect<dim> uc = Model.M1_TEMP<dim>(tagC, lbmState);
const real_t M2     = Model.M2_TEMP(tagC, lbmState);
const real_t Temp_S = Model.Source_TEMP(tagC, lbmState) ;
// compute collision rate
collider.tau = Model.tau_TEMP(tagC, lbmState);
// ipop = 0
collider.f[0]   = Base::get_f_val(tagC, IJK, 0);
collider.S0[0]  = dt * w[0] * Temp_S;
collider.feq[0] = w[0]*M0;
// ipop > 0
for (int ipop = 1; ipop < npop; ++ipop) {
  collider.f[ipop]   = this->get_f_val(tagC, IJK, ipop);
  collider.S0[ipop]  = dt * w[ipop] * Temp_S ;
      collider.feq[ipop] = w[ipop] * (M2 + c_cs2 * Base::compute_scal(ipop, uc));
}

Function setup_collider with BGK for phase-field equation

The function setup_collider is empty.

void setup_collider(EquationTag2 tag, const IVect<dim>& IJK, BGK_Collider& collider) const
{
}
  • Inspired with the temperature equation, write your setup_collider.

Solution
KOKKOS_INLINE_FUNCTION
void setup_collider(EquationTag2 tag, const IVect<dim>& IJK, BGK_Collider& collider) const
{
   const real_t dt = Model.dt;
   const real_t dx = Model.dx;
   const real_t e2 = Model.e2;
   const real_t c = dx / dt;
   const real_t c_cs2 = e2 / c; // Ratio c/cs2
   LBMState lbmState;
   Base::template setupLBMState2<LBMState, COMPONENT_SIZE>(IJK, lbmState);

   const real_t M0     = Model.M0_PHI(tagPHI, lbmState);
   const real_t M2     = Model.M2_PHI(tagPHI, lbmState);
   const real_t phi_S  = Model.Source_PHI(tagPHI, lbmState) ;
   // compute collision rate
   collider.tau = Model.tau_PHI(tagPHI, lbmState);
   // ipop = 0
   collider.f[0]   = Base::get_f_val(tagPHI, IJK, 0);
   collider.S0[0]  = dt * w[0] * phi_S ;
   collider.feq[0] = w[0]*M0;
   // ipop > 0
   for (int ipop = 1; ipop < npop; ++ipop) {
      collider.f[ipop]   = this->get_f_val(tagPHI, IJK, ipop);
      collider.S0[ipop]  = dt * w[ipop] * phi_S ;
      collider.feq[ipop] = w[ipop] * M2;
   }
}

Function init_macro

After the initialization for ADE_GAUSSIAN:

if (Model.initType == ADE_GAUSSIAN) {
  c  = Model.ampl * exp( - ( SQR(x-Model.x0) / (2 * SQR(Model.sigmax)) + SQR(y-Model.y0) / (2 * SQR(Model.sigmay)) )) ;
  vx = Model.initVX;
  vy = Model.initVY;
}
  • Write an initial condition corresponding to keywords PHASE_FIELD_INIT_VERTICAL

Solution for vertical separation
else if (Model.initType == PHASE_FIELD_INIT_VERTICAL) {
  xphi = x - Model.x0;
  c    = Model.undercooling ;
}
real_t phi = Model.phi0(xphi);
  • Save in appropriate arrays

Solution
this->set_lbm_val(IJK, ITEMP, c  );
this->set_lbm_val(IJK, IU   , vx );
this->set_lbm_val(IJK, IV   , vy );
this->set_lbm_val(IJK, IPHI , phi);

Function update_macro

Currently, only the concentration and the velocity are updated:

const real_t c  = moment_c;
const real_t vx = Model.initVX;
const real_t vy = Model.initVY;

this->set_lbm_val(IJK, IC , c );
this->set_lbm_val(IJK, IU , vx);
this->set_lbm_val(IJK, IV , vy);
  • Add for phi

Solution
real_t moment_phi = 0.0;
for (int ipop = 0; ipop < npop; ++ipop) {
  moment_c   += Base::get_f_val(tagC  , IJK, ipop);
  moment_phi += Base::get_f_val(tagPHI, IJK, ipop);
}
const real_t phi  = moment_phi;
  • Add for dphidt

Solution
LBMState lbmStatePrev;
Base::setupLBMState(IJK, lbmStatePrev);
// get time step
const real_t dt = this->params.dt;
const real_t dphidt = (phi - lbmStatePrev[IPHI])/dt;
  • Save in arrays

Solution
this->set_lbm_val(IJK, ITEMP  , c     );
this->set_lbm_val(IJK, IU     , vx    );
this->set_lbm_val(IJK, IV     , vy    );
this->set_lbm_val(IJK, IPHI   , phi   );
this->set_lbm_val(IJK, IDPHIDT, dphidt);

Function update_macro_grad

For that phase change model there is no need to compute additional gradient or laplacian.

Add the keywords of initial conditions

In file InitConditionsTypes.h, two keywords are currently declared:

enum InitCondition{
       PHASE_FIELD_INIT_UNDEFINED,
       ADE_GAUSSIAN
};

Simply add after ADE_GAUSSIAN, the new keyword for Allen-Cahn: PHASE_FIELD_INIT_VERTICAL.

Solution
enum InitCondition{
  PHASE_FIELD_INIT_UNDEFINED,
  ADE_GAUSSIAN,
  PHASE_FIELD_INIT_VERTICAL
};

4. Verifications of your implementation

The LBM implementation is compared with one analytical solution of the Stefan problem. The domain is supposed to be semi-infinite \(]0,L]\) (with \(L\) big) with Dirichlet boundary conditions \(\theta(0,t)=\theta_w\) and \(\theta(L,t)=\theta_{\infty}\). The initial condition is \(\theta(x,0)=\theta_{\infty}\).

Analytical solution

The analytical solution of this problem gives the interface position \(x_i(t)\), the liquid (or solid) temperature \(\theta_{l}(x,t)\) and the gas (or liquid) temperature \(\theta_{g}(x,t)\). Those solutions depend on a parameter \(\xi\) which is given by the transcendental equation.

Interface position

\[x_{i}(t)=2\xi\sqrt{\alpha_{l}t}\]

Liquid temperature

\[\theta_{l}(x,t)=\theta_{w}+(\theta_{I}-\theta_{w})\frac{\mbox{erf}(x/2\sqrt{\alpha_{l}t})}{\mbox{erf}(\xi)}\]

Gas temperature

\[\theta_{g}(x,t)=\theta_{\infty}+(\theta_{I}-\theta_{\infty})\frac{\mbox{erfc}(x/2\sqrt{\alpha_{g}t})}{\mbox{erfc}(\xi\sqrt{\alpha_{l}/\alpha_{g}})}\]

Transcendental equation

\[\frac{e^{-\xi^{2}}}{\mbox{erf}(\xi)}+\left(\frac{\alpha_{g}}{\alpha_{l}}\right)^{1/2}\frac{\theta_{I}-\theta_{\infty}}{\theta_{I}-\theta_{w}}\frac{e^{-\xi^{2}(\alpha_{l}/\alpha_{g})}}{\mbox{erfc}(\xi\sqrt{\alpha_{l}/\alpha_{g}})}=-\frac{\xi\sqrt{\pi}}{\theta_{w}}\]

Input parameters

  • \(\alpha_l\), \(\alpha_g\): respectively diffusity of liquid and Gas

  • \(\theta_w\): temperature at left boundary

  • \(\theta_{\infty}\): temperature at right boundary and initial condition

  • \(\theta_I\): temperature at interface

Fortran code and analytical profile

The analytical solution is implemented in a Fortran code. For validating our LBM implementation, the profiles of temperature for liquid (or gas) and solid (or liquid) with the position \(x\) are given in two output files T_Liq_Chgt_Phase200000.dat and T_Sol_Chgt_Phase200000.dat. Both files are in the folder case01. After \(2\times10^5 \delta t\) the temperature profile is presented on Fig. Fig. 44 for liquid (black) and gas (blue).

Parameters for reference profile

  • \(\theta_w=-0.3\)

  • \(\theta_{\infty}=0.3\)

  • \(\theta_I=0\)

  • \(\alpha_l=\alpha_g=0.1\)

  • \(L=500\): length of computational domain

  • \(\delta x=1\): space step

  • \(\delta t=1\): time step

  • \(x_i(0)=0\): interface position at left boundary

../../_images/Profile-Temp_Analytical_Stefan.png

Fig. 44 Temperature profile at \(t=2\times 10^5 \delta t\).

Edit your .ini input file

  • Set appropriate values of [run] section

Solution
[run]
lbm_name=D2Q9
tEnd=200001
nStepmax=200001
nOutput=20000
nlog=10000000
dt=1.0
adaptative_timestep=false
fMach=0.05
  • Set appropriate values of [mesh] section

Solution
[mesh]
nx=500
ny=10
xmin=0.0
xmax=500.0
ymin=0.0
ymax=10.0
  • Set appropriate values of [lbm] section

Solution
[lbm]
problem=ACT
model=base
e2=3.0
fcount=2
  • Set Dirichlet boundary conditions for [equation1] and [equation2].

Solution
[equation1]
boundary_type_xmin=antibounceback
boundary_type_xmax=antibounceback
boundary_type_ymin=periodic
boundary_type_ymax=periodic
collision=BGK

[equation2]
boundary_type_xmin=antibounceback
boundary_type_xmax=antibounceback
boundary_type_ymin=periodic
boundary_type_ymax=periodic
collision=BGK

[phase_field_boundary]
boundary_value_xmin=0.0
boundary_value_xmax=1.0
boundary_value_ymin=0.0
boundary_value_ymax=0.0

[concentration_boundary]
boundary_value_xmin=-0.3
boundary_value_xmax=0.3
boundary_value_ymin=0.0
boundary_value_ymax=0.0
  • Set the same input parameters and mobility=0.3 with appropriate value of interface thickness

Solution
[params]
D=0.1
W=3.0
mobility=0.3
  • Set initial condition

Solution
[init]
init_type=vertical
x0=0.0
undercooling=0.3
  • Set outputs

Solution
[output]
write_variables=temp,vx,vy,phi
outputPrefix=TestCase_Stefan1
outputVtkAscii=no
vtk_enabled=yes
hdf5_enabled=no

Run LBM_Saclay

  • Run LBM_Saclay with your input datafile

Results

Generate your csv files with Paraview

$ path_to_pvpython/pvpython Profil-Temp.pvpy

where Profil-Temp.pvpy is in

Superpose with Gnuplot

  • A gnuplot script Profil_Comparison_Analy-LBM.gnutex is also available in the folder

$ gnuplot Profil_Comparison_Analy-LBM.gnutex

will generate a new file Profile-Temp-Stefan_LBM-Analytical.tex

$ pdflatex Profile-Temp-Stefan_LBM-Analytical.tex

to make the pdf file. You should obtain Fig. Fig. 45

../../_images/Profile-Temp-Stefan_LBM-Analytical.png

Fig. 45 Comparison between LBM and analytical solution² at \(t=2\times 10^5 \delta t\).

Case \(D_l \neq D_g\)

  • Modify your kernel in order to interpolate the diffusivity in the temperature equation by \(D(\phi)=(1-\phi)D_l+\phi D_g\) where \(D_l\) and \(D_g\) are respectively the thermal diffusivity in liquid and gas. Keep \(D=1\) in the source term of phase-field equation.

Solution
KOKKOS_INLINE_FUNCTION
real_t tau_TEMP(EquationTag1 tag, const LBMState& lbmState) const
{
  real_t phi  = lbmState[IPHI];
  real_t Dphi = Dl*(1.0-phi)+Dg*phi ;
  real_t tau  = 0.5 + (3.0 * Dphi * dt / (dx * dx));
  return (tau);
}

Don’t forget to read and declare Dl and Dg

  • Add necesary values of \(D_l\) and \(D_g\) in your the .ini input file

  • Superpose with analytical solution with \(D_l=0.14\) and \(D_g=0.014\)

  • Use the harmonic interpolation for \(D(\phi)\) for temperature equation and keep \(D=1\) in phase-field equation. Compare with analytical solution.

Solution
KOKKOS_INLINE_FUNCTION
real_t tau_TEMP(EquationTag1 tag, const LBMState& lbmState) const
{
  real_t phi  = lbmState[IPHI];
  real_t Dphi = 1./(phi/Dg+(1.0-phi)/Dl) ;
  real_t tau  = 0.5 + (3.0 * Dphi * dt / (dx * dx));
  return (tau);
}
  • Compare the impact on curves

Solution
../../_images/Profile-Temp-Stefan_LBM-Analytical_HeterogeneousD.png

Fig. 46 Comparison between LBM and analytical solution at \(t=2\times 10^5 \delta t\) for \(D_l=0.14\) and \(D_g=0.014\).

Section author: Alain Cartalade