Implementation of a Navier-Stokes/Cahn-Hilliard model

Objective of this 5th tutorial

The objective is to implement in LBM_Saclay, the Navier-Stokes/Cahn-Hilliard model. The starting point is the kernel CH implemented in the first tutorial. It will be extended here with the Navier-Stokes (NS) equations and renamed NSCH.

Mathematical model & LBM scheme

  • The model summarized in Summary of Navier-Stokes/phase-field model composed of incompressible Navier-Stokes equations Eqs. (503) - (507) coupled with a Cahn-Hilliard equation Eq. (508). Two forces appear in that model: 1) the capillary force defined by Eq. (500) \(\boldsymbol{F}_c=\mu_{\phi}\boldsymbol{\nabla}\phi\) and 2) the gravity force defined by \(\boldsymbol{F}_g=\varrho\boldsymbol{g}\).

  • The equilibrium distribution function for incompressible Navier-Stokes is the version 2 presented in LBM scheme for incompressble NS. The scheme works on a dimensionless pressure \(p^{\star}_h\). It needs to add a viscous force \(\boldsymbol{F}_v\) (Eq. (210)) and a pressure force \(\boldsymbol{F}_p\) (Eq. (209)) to be consistent with the continuous model.

  • Expression of viscous force in LBM

LBM formulation of \(\boldsymbol{F}_v\)
(264)\[\boldsymbol{F}_{v}=-\frac{\nu}{c_{s}^{2}\delta t}\sum_{i}\boldsymbol{c}_{i}(\boldsymbol{c}_{i}\cdot\boldsymbol{\nabla}\varrho)f_{i}^{\star}\]

where

(265)\[f_{i}^{\star}=(\boldsymbol{M}^{-1}\boldsymbol{S}\boldsymbol{M}(\boldsymbol{f}-\boldsymbol{f}^{eq}))_{i}\]

For each component

\[\begin{split}F_{v,x}=-\frac{\nu}{c_{s}^{2}\delta t}\sum_{i=0}^{N_{pop}}c_{ix}\left[c_{ix}(\partial_{x}\varrho)+c_{iy}(\partial_{y}\varrho)+c_{iz}(\partial_{z}\varrho)\right]f_{i}^{\star}\\ F_{v,y}=-\frac{\nu}{c_{s}^{2}\delta t}\sum_{i=0}^{N_{pop}}c_{iy}\left[c_{ix}(\partial_{x}\varrho)+c_{iy}(\partial_{y}\varrho)+c_{iz}(\partial_{z}\varrho)\right]f_{i}^{\star}\\ F_{v,z}=-\frac{\nu}{c_{s}^{2}\delta t}\sum_{i=0}^{N_{pop}}c_{iz}\left[c_{ix}(\partial_{x}\varrho)+c_{iy}(\partial_{y}\varrho)+c_{iz}(\partial_{z}\varrho)\right]f_{i}^{\star}\end{split}\]

Objective

In this tutorial, we will see how to

  • add a new equation (like tuto 2)

  • add a setup_collider function relative to NS equations

  • add external force terms \(\boldsymbol{F}_c\) and \(\boldsymbol{F}_g\)

  • add force terms \(\boldsymbol{F}_v\) and \(\boldsymbol{F}_p\) for consistency

1. Create a new kernel NSCH

Create a new kernel NSCH

You can refer to Tuto 1 & 2 where all commands have been presented in detail:

  1. Copy your kernel CH and rename it NSCH_Tutorial in directory LBM_Saclay_Rech-Dev/src/kernels/

  2. Go to your new folder NSCH_Tutorial and replace all files with extension _CH by _NSCH

  3. Edit your files and replace _CH with _NSCH

  4. In file Setup.h replace the problem name CH by NSCH

  5. Create your Makefile with option -DPROBLEM=NSCH_Tutorial and compile

2. Add a new equation in your kernel NSCH

The mathematical model is currently composed of one Cahn-Hilliard equation. 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 tagNS for EquationTag2

Solution
EquationTag1 tagC;
EquationTag2 tagNS;
  • 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 second one will be filled below.

Function make_boundary

The boundary conditions are applied only for Cahn-Hilliard equation

// phi 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(tagNS, faceId, IJK, ipop, 0.0);
}
  • Add a new block if - else if for BOUNDARY_EQUATION_2 and tagNS with keyword BOUNDARY_PRESSURE

Solution
if (Base::params.boundary_types[BOUNDARY_EQUATION_2][faceId] == BC_ANTI_BOUNCE_BACK) {
  real_t boundary_value = this->params.boundary_values[BOUNDARY_PRESSURE][faceId];
  for (int ipop = 0; ipop < npop; ++ipop) {
     this->compute_boundary_antibounceback(tagNS, 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(tagNS, faceId, IJK, ipop, 0.0);
  }
}

3. Modifications inside each file of NSCH

Declaration of new fields

In enum ComponentIndex, only the indices relative to Cahn-Hilliard model are declared:

enum ComponentIndex {
  IU     , /*!< X velocity / momentum index */
  IV     , /*!< Y velocity / momentum index */
  IW     , /*!< Z velocity / momentum index */
  IC     , /*!< Phase field index */
  IMU    , /*!< Chemical potential */
  ILAPLAC,     /*!< Laplacien of Phase field */
  COMPONENT_SIZE /*!< invalid index, just counting number of fields */
};
  • Complete the list with hydrodynamics indices density \(\varrho\), hydrodynamic pressure \(p_h\), gradient \(\boldsymbol{\nabla}\phi\), gradient \(\boldsymbol{\nabla}\varrho\) and total force \(\boldsymbol{F}_{tot}\).

Solution
enum ComponentIndex {
  IU     , /*!< X velocity / momentum index */
  IV     , /*!< Y velocity / momentum index */
  IW     , /*!< Z velocity / momentum index */
  IPHI   , /*!< Phase field index */
  IMU    , /*!< Chemical potential */
  ILAPLAC,     /*!< Laplacien of Phase field */
  ID,
  IP,
  IDPHIDX,
  IDPHIDY,
  IDPHIDZ,
  IDRHODX,
  IDRHODY,
  IDRHODZ,
  IFX,           /*!< Force Navier Stokes X */
  IFY,           /*!< Force Navier Stokes Y */
  IFZ,           /*!< Force Navier Stokes Z */
  COMPONENT_SIZE /*!< invalid index, just counting number of fields */
};

Add new outputs

In struct index2names only the strings for Cahn-Hilliard model are written:

map[IC ] = "comp";
map[IU ] = "vx";
map[IV ] = "vy";
map[IW ] = "vz";
map[IMU] = "mu";
  • Add a new outputs for density and pressure (and more if wanted).

Solution
map[IPHI] = "phi";
map[IU  ] = "vx";
map[IV  ] = "vy";
map[IW  ] = "vz";
map[IMU ] = "mu";
map[ID  ] = "rho";
map[IP  ] = "pressure";

Read and declare Navier-Stokes parameters in ModelParams

The Navier-Stokes equations involve several parameters: the kinematic viscosity \(\nu_1\) of fluid 1 (respectively \(\nu_2\) of fluid 2), the density \(\rho_1\) (\(\rho_2\)). The surface tension \(\sigma\) has already been read as input paramater for Cahn-Hilliard equation. Also read the components of gravity \(\boldsymbol{g}=(g_x,g_y,g_z)\) (gx, gy, gz)

  • Read and declare the Navier-Stokes parameters in ModelParams

Solution

Read parameters in input file:

rho0 = configMap.getFloat("params", "rho0", 1.0);
rho1 = configMap.getFloat("params", "rho1", 1.0);
nu0  = configMap.getFloat("params", "nu0" , 1.0);
nu1  = configMap.getFloat("params", "nu1" , 1.0);
gx   = configMap.getFloat("params", "gx"  , 0.0);
gy   = configMap.getFloat("params", "gy"  , 0.0);
gz   = configMap.getFloat("params", "gz"  , 0.0);
  • Declare them as real_t at the end of ModelParams

Solution
real_t rho0, rho1, nu0, nu1, gx, gy, gz;
  • From Tuto 1, three initial conditions are currently implemented

if (initTypeStr == "random") initType = RANDOM_SPINODAL;
else if (initTypeStr == "nucleation") initType = RANDOM_NUCLEATION;
else if (initTypeStr == "serpentine") initType = SERPENTINE;

Add a new else if condition for vertical (see Tuto 2)

Solution
if (initTypeStr == "random") initType = RANDOM_SPINODAL;
else if (initTypeStr == "nucleation") initType = RANDOM_NUCLEATION;
else if (initTypeStr == "serpentine") initType = SERPENTINE;
else if (initTypeStr == "vertical") initType = PHASE_FIELD_INIT_VERTICAL;

Add specific functions for Navier-Stokes

  • Add a new harmonic function tau_NS for relaxation rate \(\tau_{NS}\)

Solution tau_NS
// =======================================================
// relaxation coef for LBM scheme
//
KOKKOS_INLINE_FUNCTION
real_t tau_NS(const LBMState &lbmState) const {
  const real_t phi = lbmState[IPHI];
  const real_t nu  = nu0 * nu1 / (((1.0 - phi) * nu1) + ((phi)*nu0));
  real_t tau = 0.5 + (e2 * nu * dt / SQR(dx));
  return (tau);
}
  • Add a new function interpol_rho for linear interpolation of density \(\varrho=\rho_1\phi+\rho_2(1-\phi)\)

Solution interpol_rho
// =======================================================
// Interpolation for rho
KOKKOS_INLINE_FUNCTION real_t interpol_rho (real_t phi) const {
  return rho0*phi + rho1*(1.0 - phi);
}
  • Add a new function force_P for pressure force \(\boldsymbol{F}_p=-p_h/\varrho(\phi)\boldsymbol{\nabla}\varrho\)

Solution force_P
// ===============================================================================================================
// Pressure term for NS. Correction term because of p* formulation inside the eq dist function
// ===============================================================================================================
template <int dim>
KOKKOS_INLINE_FUNCTION RVect<dim> force_P (const LBMState &lbmState) const {
  RVect<dim> gradrho;
  gradrho[IX] = lbmState[IDRHODX];
  gradrho[IY] = lbmState[IDRHODY];
  if (dim == 3) {
     gradrho[IZ] = lbmState[IDRHODZ];
  }
  const real_t scal = lbmState[IP] / lbmState[ID];
  RVect<dim> term;
  term[IX] = -scal * gradrho[IX];
  term[IY] = -scal * gradrho[IY];
  if (dim == 3) {
     term[IZ] = -scal * gradrho[IZ];
  }
  return term;
}
  • Add a new function force_G for gravity force \(\boldsymbol{F}_g=\varrho\boldsymbol{g}\)

Solution force_G
// ===============================================================================================================
// Body term for NS: gravity
//
template <int dim>
KOKKOS_INLINE_FUNCTION RVect<dim> force_G(const LBMState &lbmState) const {
  RVect<dim> term;
  term[IX] = gx * lbmState[ID];
  term[IY] = gy * lbmState[ID];
  if (dim == 3) {
     term[IZ] = gz * lbmState[ID];
  }
  return term;
}
  • Add a new function force_TS for surface tension force \(\mu_{\phi}\boldsymbol{\nabla}\phi\)

Solution force_TS
// ===============================================================================================================
// Surface tension liquid/gas for NS
//
template <int dim>
KOKKOS_INLINE_FUNCTION RVect<dim> force_TS(const LBMState &lbmState) const {
  const real_t tension = sigma * 1.5 / W * (g_prime(lbmState[IPHI]) - SQR(W) * lbmState[ILAPLAC]);
  RVect<dim> term;
  term[IX] = tension * lbmState[IDPHIDX];
  term[IY] = tension * lbmState[IDPHIDY];
  if (dim == 3) {
     term[IZ] = tension * lbmState[IDPHIDZ];
  }
  return term;
}

Function setup_collider with BGK

  • From stage 2, the function setup_collider with BGK_Collider is currently empty. It must be filled to simulate Navier-Stokes equations.

Solution
KOKKOS_INLINE_FUNCTION
void setup_collider(EquationTag2 tag, const IVect<dim> &IJK, BGK_Collider &collider) const
{
  // Paramètres pour la simulation
  const real_t dx = Model.dx;
  const real_t dt = Model.dt;
  const real_t c = dx / dt;
  const real_t cs2 = SQR(c) / Model.e2;
  // Stockage des anciennes grandeurs macroscopiques
  LBMState lbmState;
  Base::template setupLBMState2<LBMState, COMPONENT_SIZE>(IJK, lbmState);

  // Calcul du tau de collision
  collider.tau = Model.tau_NS(lbmState);
  • Equilibrium distribution function Eq. (202) without variable change

  // Equilibrium without force term
  FState GAMMA;
  if (dim == 2) {
     real_t scalUU    = SQR(lbmState[IU]) + SQR(lbmState[IV]);
     for (int ipop    = 0; ipop < npop; ++ipop) {
        real_t scalUC = c * Base::compute_scal(ipop, lbmState[IU], lbmState[IV]);
        GAMMA[ipop]   = scalUC / cs2 + 0.5 * SQR(scalUC) / SQR(cs2) - 0.5 * scalUU / cs2;
        real_t feqbar = w[ipop] * (lbmState[IP] / (cs2 * lbmState[ID]) + GAMMA[ipop]);
        collider.feq[ipop] = feqbar;
        collider.f[ipop]   = Base::get_f_val(tag, IJK, ipop);
     }
  • Computation of force terms \(\boldsymbol{F}_c\), \(\boldsymbol{F}_g\) and \(\boldsymbol{F}_p\) defined in file Models_NSCH.h

     // Computation of force terms defined in models
     RVect<dim> ForceTS = Model.force_TS<dim>(lbmState);
     RVect<dim> ForceG  = Model.force_G<dim>(lbmState);
     RVect<dim> ForceP  = Model.force_P<dim>(lbmState);
  • Computation of viscous force \(F_{v,x}\) and \(F_{v,y}\) Eq. (264) & Eq. (265). For BGK collision only one relaxation rate is involved. It appears in factor coeffV.

     // Computation of viscous force
     RVect<dim> ForceV;
     const real_t nu = Model.nu0 * Model.nu1 / (((1.0 - lbmState[IPHI]) * Model.nu1) + ((lbmState[IPHI]) * Model.nu0));
     real_t coeffV = - nu / (cs2 * dt * collider.tau);
     ForceV[IX] = 0.0;
     ForceV[IY] = 0.0;
     for (int alpha = 0; alpha < npop; ++alpha) {
        ForceV[IX] += E[alpha][IX] * E[alpha][IX] * lbmState[IDRHODX] * (collider.f[alpha] - collider.feq[alpha]) +
                      E[alpha][IX] * E[alpha][IY] * lbmState[IDRHODY] * (collider.f[alpha] - collider.feq[alpha]);
        ForceV[IY] += E[alpha][IY] * E[alpha][IX] * lbmState[IDRHODX] * (collider.f[alpha] - collider.feq[alpha]) +
                      E[alpha][IY] * E[alpha][IY] * lbmState[IDRHODY] * (collider.f[alpha] - collider.feq[alpha]);
     }
     ForceV[IX] = coeffV * ForceV[IX];
     ForceV[IY] = coeffV * ForceV[IY];
  • Computation of total force \(\boldsymbol{F}_{tot}\) and save in array with appropriate indices IFX and IFY

     // Calcul et enregistrement du terme source total
     RVect<dim> ForceTot;
     ForceTot[IX] = ForceG[IX] + ForceP[IX] + ForceTS[IX] + ForceV[IX];
     ForceTot[IY] = ForceG[IY] + ForceP[IY] + ForceTS[IY] + ForceV[IY];

     this->set_lbm_val(IJK, IFX, ForceTot[IX]);
     this->set_lbm_val(IJK, IFY, ForceTot[IY]);
  • Correction of equilibrium (variable change) with total force term ForceTot[IX] & ForceTot[IY]

     // Add force term in equilibrium
     for (int ipop = 0; ipop < npop; ++ipop) {
        collider.S0[ipop]  = dx * w[ipop] / (lbmState[ID] * cs2) * Base::compute_scal(ipop, ForceTot[IX], ForceTot[IY]);
        collider.feq[ipop] = collider.feq[ipop] - 0.5 * (collider.S0[ipop]);
     }
  }
} // end of setup_collider for composition equation

Function init_macro

  • Write (or copy-paste from tuto 2) 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.phi0(xphi);
}
rho = Model.interpol_rho(c);
  • Save in appropriate arrays

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

Function update_macro

  • Update necessary fields

Solution

The function starts with necessary parameters for computing the coefficient \(c_s^2\)

// get local coordinates
real_t x, y;
this->get_coordinates(IJK, x, y);
const real_t dx  = this->params.dx;
const real_t dt  = this->params.dt;
const real_t cs2 = SQR(dx / dt) / Model.e2;

Next the moments of distribution functions are computed for \(\phi\), \(p^{\star}\), \(v_x\) and \(v_y\)

// compute moments of distribution equations
real_t moment_phi = 0.0;
real_t moment_P   = 0.0;
real_t moment_VX  = 0.0;
real_t moment_VY  = 0.0;
for (int ipop = 0; ipop < npop; ++ipop) {
  moment_phi += Base::get_f_val(tagC, IJK, ipop);
  moment_P   += Base::get_f_val(tagNS, IJK, ipop);
  moment_VX  += Base::get_f_val(tagNS, IJK, ipop) * E[ipop][IX];
  moment_VY  += Base::get_f_val(tagNS, IJK, ipop) * E[ipop][IY];
}

// Update phi
const real_t phi  = moment_phi;

Computation of \(\mu\)

// Update mu
LBMState lbmStatePrev;
Base::setupLBMState(IJK, lbmStatePrev);
const real_t mu = Model.M2(tagC, lbmStatePrev);

Computation of \(p_h\) from \(p^{\star}\)

// Update pressure p_h
const real_t rho = Model.interpol_rho(phi) ;
const real_t P   = moment_P * (rho*cs2) ;

Computation of \(u_x\) and \(u_y\) from \(v_x\) and \(v_y\) with force term correction \((\delta t/2)\boldsymbol{F}_{tot}/\varrho\)

// Update VX and VY
const real_t ForceNSX = lbmStatePrev[IFX];
const real_t ForceNSY = lbmStatePrev[IFY];
real_t VX = moment_VX + 0.5 * dt * ForceNSX / rho;
real_t VY = moment_VY + 0.5 * dt * ForceNSY / rho;

if (Model.initType == SERPENTINE) {
  const real_t pi = M_PI;
  VX    = - pi*Model.U0 * cos(pi*((x/Model.L)-0.5)) * sin(pi*((y/Model.L)-0.5));
  VY    =   pi*Model.U0 * sin(pi*((x/Model.L)-0.5)) * cos(pi*((y/Model.L)-0.5));
}

Save in array with appropriate indices

// update macro fields
this->set_lbm_val(IJK, IPHI , phi);
this->set_lbm_val(IJK, ID   , rho);
this->set_lbm_val(IJK, IP   , P  );
this->set_lbm_val(IJK, IU   , VX );
this->set_lbm_val(IJK, IV   , VY );
this->set_lbm_val(IJK, IMU  , mu );

Function update_macro_grad

Currently the function contains the laplacian computation for chemical potential.

KOKKOS_INLINE_FUNCTION
void update_macro_grad(IVect<dim> IJK) const {
  real_t laplace_c = Base::compute_laplacian(IJK, IC, BOUNDARY_EQUATION_1);
  Base::set_lbm_val(IJK, ILAPLAC, laplace_c);
}
  • Add computation of gradients

Solution
KOKKOS_INLINE_FUNCTION
void update_macro_grad(IVect<dim> IJK) const {
  real_t laplace_c = Base::compute_laplacian(IJK, IC, BOUNDARY_EQUATION_1);
  Base::set_lbm_val(IJK, ILAPLAC, laplace_c);

  RVect<dim> gradPhi;
  this->compute_gradient(gradPhi, IJK, IPHI, BOUNDARY_EQUATION_1);
  this->set_lbm_val(IJK, IDPHIDX, gradPhi[IX]);
  this->set_lbm_val(IJK, IDPHIDY, gradPhi[IY]);
  if (dim == 3) this->set_lbm_val(IJK, IDPHIDZ, gradPhi[IZ]);

  RVect<dim> gradRho;
  this->compute_gradient(gradRho, IJK, ID, BOUNDARY_EQUATION_2);
  this->set_lbm_val(IJK, IDRHODX, gradRho[IX]);
  this->set_lbm_val(IJK, IDRHODY, gradRho[IY]);
  if (dim == 3) this->set_lbm_val(IJK, IDRHODZ, gradRho[IZ]);
}

Add the keywords of initial conditions

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

enum InitCondition{
  PHASE_FIELD_INIT_UNDEFINED,
  ADE_GAUSSIAN,
  RANDOM_SPINODAL,
  RANDOM_NUCLEATION,
  SERPENTINE
};
  • Add the new keyword for initial condition PHASE_FIELD_INIT_VERTICAL

Solution
enum InitCondition{
   PHASE_FIELD_INIT_UNDEFINED,
   ADE_GAUSSIAN,
   RANDOM_SPINODAL,
   RANDOM_NUCLEATION,
   SERPENTINE,
   PHASE_FIELD_INIT_VERTICAL
};

4. Verification of your implementation

The test case of validation is the double-Poiseuille flow. The input file is Test-Tuto04_Double_Poiseuille_BGK.ini in the directory run_training_lbm/Tutorial05_NSCH.

Run and post-process your results

  • Go to the appropriate folder and run LBM_Saclay

$ cd LBM_Saclay_Rech-Dev/run_training_lbm/Tutorial05_NSCH
$ ../../build_NSCH/src/LBM_saclay Test-Tuto04_Double_Poiseuille_BGK.ini
  • Once the computation is complete, extract from your final .vti file one profile \(u_y\) as a function of \(x\) with paraview 5.12

$ path_to_pvpython/pvpython Extract-Profile_BGK_PV512.pvpy

The output is a .csv file called Profil_Double-Poiseuille_BGK.csv.

  • Compare with the analytical solution written in the python script Profils_NSCH-BGK_Analytical.py

$ python Profils_NSCH-BGK_Analytical.py

You must obtain Fig Fig. 49

../../_images/Fig_Tuto5_DoublePoiseuille.png

Fig. 49 Comparison between LBM and analytical solution of double-Poiseuille flow

Section author: Alain Cartalade