sandbox/ggennari/phase_change/phase_change_pure_species.h

    Transport of a species concentration field with phase change

    This module can be used to compute the concentration field of a soluble species in a two-phase system with mass transfer; the volume change due to the exchange of mass between the phases is taken into account.

    Here we implement the two-scalar method of Fleckenstein and Bothe,2015, where two transport equations are solved for each species. However, we consider pure disperse (gas) phases only (i.e. no mixtures), where the concentration field is uniform (and known). Therefore, we need to solve only one transport equation in the continuous (liquid) phase: \displaystyle \partial_t c_c + \mathbf{u}\cdot\nabla c_c = \nabla\cdot\left(D\nabla c_c\right) - \frac{\dot{m}}{M}\delta_\Sigma where:

    • c_c is the phase-average concentration field in the continuous (liquid) phase
    • D is the diffusion coefficient in the continuous phase
    • \dot{m} is the mass transfer rate
    • M is the molar mass
    • \delta_\Sigma is the interfacial delta function

    We assume the primary phase to be the continuous one (i.e. f=1 in the liquid domain). The cell-average concentration (tr_c) is related to the phase average one via: tr_c=fc_c.

    The interface is considered to be at the equilibrium (i.e. saturated) and the concentration jump across the interface is given by Henry’s Law: \displaystyle (c_c)_\Sigma=\frac{(c_d)_\Sigma}{He} where c_d is the (known) concentration in the disperse region and He is Henry’s constant.

    For the details of the following implementation, the reader is referred to Gennari et al.,2022

    #include "mass_refine_prolongation.h"
    #include "../fracface.h"
    #include "gradients.h"

    We define the attributes for the concentration field.

    attribute {
      double D; //diffusion coefficient in the liquid phase
      double mol_mass; //molar mass
      double cd; //uniform concentration in the gas phase
      double He; //Henry's coeff
      bool diff; //we can switch off (on) the diffusion term if diff is false (true)
    }

    F_ERR is the accepted error over f to avoid division by zero; it is also used to identify interfacial cells.

    #ifndef F_ERR
    #define F_ERR 1e-10 //tolerance on the interfacial cells
    #endif

    We need a scalar m to store the redistributed mass transfer field. The list species contains the (only) transferable tracer and m_list is used to store the interfacial (not redistributed) mass transfer term \dot{m}. We have the option to switch on the volume change at time tt.

    scalar m[]; //overall (redistributed) mass transfer term
    extern scalar * species; //list containing the only transferable species
    extern scalar * m_list; //mass transfer in the interfacial cells
    
    double tt = 0.; //used to start the volume change at t = tt

    Defaults

    //We set the refine/prolongation/restriction operators for Tree grids
    event defaults (i = 0)
    {
    #if TREE
      for (scalar m_i in m_list) {
        m_i.c = f;
        m_i.refine = m_i.prolongation = mass_refine;
        m_i.restriction = no_restriction;
        m_i.dirty = true;
      }
    
      m.refine = m.prolongation = refine_injection;
      m.dirty = true;
    #endif
    }

    Tracer advection

    The advection of the soluble tracer is performed in the vof event (along with the interface). Here we take into account the contribution of the mass transfer \dot{m} on the species concentration (last term on the RHS of the transport equation) and volume of fluid. We also compute the redistributed mass transfer m.

    event tracer_advection (i++)
    {
      scalar m_i;

    MASS_TRANSFER_RATE is used to set a constant \dot{m} (only useful for debugging purposes).

    #ifndef MASS_TRANSFER_RATE
      scalar tr_c;
      for (tr_c, m_i in species, m_list) {
        bool third = false; //we use the scheme of Fleck. and Bothe (VOF avg.)

    We recover the phase-average concentration field c_c.

        //tr_c -> c_c
        foreach()
          tr_c[] = f[] > F_ERR ? tr_c[]/f[] : 0.;
        boundary({tr_c});

    To compute the gradient of concentration normal to the interface we need the face fraction of the vof (liquid) field.

        face vector d[]; //d is the face fraction of f
        face_fraction (f, d);

    Here we compute the mass transfer rate: \displaystyle \dot{m} = -\frac{MD}{1-\rho_{c_c}/\rho_c}\frac{\partial c_c}{\partial \mathbf{n}_\Sigma} where \rho_{c_c} is the local density of the soluble species on the liquid side of the interface and \rho_c is the liquid density. Note the for large density ratios, the term 1-\rho_{c_c}/\rho_c \approx 0.

        //compute m_true and set m to zero
        foreach() {
          m_i[] = 0.; //set individual mass transfer term = 0
          m[] = 0.; //set global (redistributed) mass transfer term = 0
          if (f[] > F_ERR && f[] < 1. - F_ERR) {
    	coord n = interface_normal(point, f);
    #if dimension == 2
    	coord p = {0.,0.};
    #else //dimension ==3
    	coord p = {0.,0.,0.};
    #endif
    	double alpha = plane_alpha (f[], n);
    	plane_area_center (n, alpha, &p);
    	normalize (&n); // |n| = 1
    	double bc = tr_c.cd/tr_c.He; //Henry's Law
    	m_i[] = -concentration_gradient (point, tr_c, f, d, n, p, bc, third)*tr_c.mol_mass*tr_c.D; //Fix me. Add 1-\rho_{c_c}/\rho_c term
          }
        }

    We recover the cell-average concentration tr_c.

        //c_c -> tr_c
        foreach() {
          tr_c[] *= f[];
        }
      }

    If we use a constant mass transfer rate, we set \dot{m} = MASS\_TRANSFER\_RATE.

    #else //MASS_TRANSFER_RATE
      
      for (m_i in m_list) {
        foreach() {
          m_i[] = 0.; //set individual mass transfer term = 0
          m[] = 0.; //set global (redistributed) mass transfer term = 0
          if (f[] > F_ERR && f[] < 1. - F_ERR)
    	m_i[] = MASS_TRANSFER_RATE;
        }
      }
      
    #endif

    The incompressible VOF scheme implemented in Basilisk (vof.h) ensures mass conservation provided the velocity field is divergence-free everywhere (always true for incompressible flows without mass transfer). When phase-change occurs, the velocity field at the interface is generally not divergence-free (\nabla\cdot\mathbf{u} \neq 0). Here we propose an algorithm to redistribute the original interfacial mass transfer rate \dot{m} into the neighbouring pure gas cells, in order to have a divergence-free velocity field for the cells that contain the continuous phase (both pure liquid and interfacial cells).

    We first store in the field avg the number of neighbouring pure gas cells (in a 3\times3 (3\times3\times3) stencil in 2D (3D)) for each interfacial cell. Note that we do this when we want to take into account the volume change, i.e. for t>tt.

      scalar avg[];
      avg.c = f;
    
    #if TREE
      avg.refine = avg.prolongation = refinement_avg;
      avg.restriction = no_restriction;
      avg.dirty = true;
    #endif
    
      if (t > tt) {
        //Compute avg
        foreach() {
          avg[] = 0.;
          if (f[] > F_ERR && f[] < 1. - F_ERR) {
    	int count = 0;
    	foreach_neighbor(1) {
    	  if (f[] < F_ERR)
    	    count ++;
    	}
    	avg[] = count;
          }
        }

    Here we compute the redistributed mass transfer term m. The idea is to replace the projection step: \displaystyle \nabla\cdot(\alpha\nabla p) = \frac{\nabla\cdot\mathbf{u}_f}{\Delta t} - \frac{\dot{m}}{\Delta t}\left(\frac{1}{\rho_d} - \frac{1}{\rho_c}\right)\delta_\Sigma with: \displaystyle \nabla\cdot(\alpha\nabla p) = \frac{\nabla\cdot\mathbf{u}_f}{\Delta t} - \frac{m}{\Delta t} The field m is \neq 0 only for pure gas cells close to the interface and takes into account the density jump \left(\frac{1}{\rho_d} - \frac{1}{\rho_c}\right) and the Dirac function (approximated as \delta_\Sigma = A_\Sigma/V).

        //Compute m
        foreach() {
          if (f[] < F_ERR) {
    	double val = 0.;
    	foreach_neighbor(1) {
    	  if (f[] > F_ERR && f[] < 1. - F_ERR && avg[] > 0) {
    	    coord n = interface_normal(point, f), p;
    	    double alpha = plane_alpha (f[], n);
    	    double area = plane_area_center (n, alpha, &p);
    	    for (scalar m_i in m_list) {
    #if AXI
    	      val += cm[]*m_i[]*(area*(y + p.y*Delta)*(1./rho2 - 1./rho1)/(Delta*y)/avg[]);
    #else
    	      val += m_i[]*(area*(1./rho2 - 1./rho1)/Delta/avg[]);
    #endif
    	    }
    	  }
    	}
    	m[] = val;
          }
          else {
    	m[] = 0.;
          }
        }
      }

    The change in volume due to the mass transfer is computed. We assume a rigid displacement of the interface along the normal direction \mathbf{n}_\Sigma: \displaystyle \mathbf{h} = -\frac{\dot{m}}{\rho_c}\frac{\Delta t}{\Delta}\mathbf{n}_\Sigma The change in intercept after the displacement is: \displaystyle \Delta \alpha = -\frac{\dot{m}}{\rho_c}\frac{\Delta t}{\Delta}\sqrt{n_{\Sigma x}^2 + n_{\Sigma y}^2 + n_{\Sigma z}^2}

    //Update f according to mass transfer \dot{m}
      foreach() {
        if (f[] > F_ERR && f[] < 1. - F_ERR) {
          coord n = interface_normal(point, f);
          double alpha = plane_alpha (f[], n);
          double val = 0.;
          for (scalar m_i in m_list)
    	val += m_i[]; //total mass transfer
          double delta_alpha = -val*dt*sqrt(sq(n.x) + sq(n.y) + sq(n.z))/rho1/Delta; //total interface shift (in terms of alpha). You must be sure that |nx|+|ny|+|nz|=1. 
          //This is not true when contact.h is used. Fix me.
          
          if (t > tt) {
          	double ff = plane_volume (n, alpha + delta_alpha); //total interface shift (in terms of volume fraction)
          	if (ff > F_ERR && ff < 1. - F_ERR)
          	  f[] = ff;
          	f[] = clamp(f[], 0., 1.);
          }

    The concentration in the liquid domain is updated according to \dot{m}.

          coord p;
          double area = plane_area_center (n, alpha, &p);
          scalar tr_c, m_i;
          for (tr_c, m_i in species, m_list) {
    	
    #if AXI
    	tr_c[] -= m_i[]*dt*area*(y + p.y*Delta)/(Delta*y)/tr_c.mol_mass;	
    #else
    	tr_c[] -= m_i[]*dt*area/Delta/tr_c.mol_mass;
    	
    #endif
          }
        }
      }
      
      //Set tr_c = 0 in pure gas cells (according to F_ERR)
      foreach() {
        for (scalar tr_c in species) {
          tr_c[] *= (f[] > F_ERR);
        }
      }
    }

    Tracer diffusion

    In this event we perform the integration of the diffusion term. The concentration field c_c must be confined to the liquid side of the interface. It is important to prevent any diffusion across the interface, as this would represent an artificial mass transfer (since \dot{m} has already been taken into account in the event before). Here we use the same approach proposed by Quentin Magdelaine in mixtures.h. Details of the numerical implementation can be found in Magdelaine, 2019.

    The FV scheme for diffusion with an immersed no flux condition reads: \displaystyle f\, \frac{\partial c_c}{\partial t} = \frac{1}{V}\, \sum_\text{cell faces}{f_f\, D\, \frac{\partial c_c}{\partial \mathbf{n}}\, A} where f_f is the face fraction of the VOF field.

    event tracer_diffusion (i++) 
    { 
      for (scalar tr_c in species) {
        scalar theta[]; //volume correction (it takes into account the effective volume of species)
        face vector d[];
    
    #if TREE
        theta.refine = theta.prolongation = fraction_refine;
        theta.dirty = true;
    #endif
    
        foreach() {
          theta[] = cm[]*max(f[], F_ERR);
        }
        
        face_fraction (f, d);
        foreach_face() {
          d.x[] *= fm.x[]*tr_c.D;
        }
        boundary((scalar *){d});
        
        //tr_c -> c_c
        foreach() {
          tr_c[] = f[] > F_ERR ? tr_c[]/f[] : 0.;
        }
        
        if (tr_c.diff) {
          diffusion (tr_c, dt, D = d, theta = theta);
        }
        
        //c_c -> tr_c and set tr_c = 0 in pure gas cells
        foreach() {
          tr_c[] *= f[]*(f[] > F_ERR);
        }
      }
    }

    References

    [gennari2022]

    Gabriele Gennari, Richard Jefferson-Loveday, and Stephen J. Pickering. A phase-change model for diffusion-driven mass transfer problems in incompressible two-phase flows. Chemical Engineering Science, 259:117791, 2022. [ DOI | http ]

    [magdelaine2019]

    Quentin Magdelaine-Guillot de Suduiraut. Hydrodynamics of liquid films hétérogènes. PhD thesis, Sorbonne university ’e, 2019.

    [fleckenstein2015]

    Stefan Fleckenstein and Dieter Bothe. A volume-of-fluid-based numerical method for multi-component mass transfer with local volume changes. Journal of Computational Physics, 301:35–58, 2015. [ DOI | http ]