sandbox/ecipriano/src/navier-stokes/velocity-extrapolation.h

    Velocity Extrapolation

    We want to extrapolate the one-field velocity \mathbf{u}, in order to obtain a liquid velocity \mathbf{u}_l and a gas phase velocity \mathbf{u}_g, which are both continuous and divergence-free at the interface. The extrapolations are perfomed using Aslam’s PDE based approach. After the extrapolation step, the velocity-potential.h approach can be used to force the divergence of the velocity to be zero. This method was proposed by Palmore et al., 2019.

    #include "aslam.h"
    #include "redistance.h"

    Field Allocation

    We create the fields corresponding to the liquid and gas phase face velocities.

    face vector ufext1[], ufext2[];
    scalar ps1[], ps2[];
    mgstats mgpdiv1, mgpdiv2;
    extern scalar f;
    int nl1 = 1, nl2 = 1;

    Helper Functions

    We define a function that converts the vof fraction to the level set field.

    void vof_to_ls (scalar f, scalar ls) {
      double deltamin = L0/(1 << grid->maxdepth);
      foreach()
        ls[] = -(2.*f[] - 1.)*deltamin*0.75;
    #if TREE
      restriction({ls});
    #endif
      redistance (ls, imax = 3);
    }

    We define a function that performs a projection step to correct the divergence of the velocity field.

    trace
    mgstats project_div1 (face vector ufs, scalar ps,
         (const) face vector alpha = unityf,
         double dt = 1.,
         int nrelax = 4)
    {
      scalar div[];
      foreach() {
        ps[] = 0.;
        div[] = 0.;
        foreach_dimension()
          div[] += ufext1.x[1] - ufext1.x[];
        div[] /= dt*Delta;
      }
    
    #ifdef PS_IMPLICIT_SOURCE
      scalar marker[];
      mapregion (marker, f, inverse=true, interface=true, nl=2);
      foreach()
        marker[] *= 1./sq(dt);
    
      mgstats mgp = poisson (ps, div, alpha, lambda=marker,
          tolerance = TOLERANCE/sq(dt), nrelax = nrelax);
    #else
      mgstats mgp = poisson (ps, div, alpha,
          tolerance = TOLERANCE/sq(dt), nrelax = nrelax);
    #endif
    
      foreach_face()
        ufs.x[] = -dt*alpha.x[]*face_gradient_x (ps, 0);
    
      return mgp;
    }
    
    trace
    mgstats project_div2 (face vector ufs, scalar ps,
         (const) face vector alpha = unityf,
         double dt = 1.,
         int nrelax = 4)
    {
      scalar div[];
      foreach() {
        ps[] = 0.;
        div[] = 0.;
        foreach_dimension()
          div[] += ufext2.x[1] - ufext2.x[];
        div[] /= dt*Delta;
      }
    
    #ifdef PS_IMPLICIT_SOURCE
      scalar marker[];
      mapregion (marker, f, inverse=false, interface=true, nl=2);
      foreach()
        marker[] *= 1./sq(dt);
    
      mgstats mgp = poisson (ps, div, alpha, lambda=marker,
          tolerance = TOLERANCE/sq(dt), nrelax = nrelax);
    #else
      mgstats mgp = poisson (ps, div, alpha,
          tolerance = TOLERANCE/sq(dt), nrelax = nrelax);
    #endif
    
      foreach_face()
        ufs.x[] = -dt*alpha.x[]*face_gradient_x (ps, 0);
    
      return mgp;
    }

    Extrapolations

    At the end of the solution of the Navier-Stokes equations, we apply the extrapolation procedure.

    vector uext1[], uext2[];
    
    event end_timestep (i++)
    {

    We need to start from the face one-field velocity \mathbf{u}_f and to return the two extended velocities on the faces, in order to be used for the advection steps. However, the extrapolation procedure must be applied on cell-centered fields. Therefore, we interpolate from face to cell.

      //vector uext1[], uext2[];
      foreach() {
        foreach_dimension() {
          uext1.x[] = 0.5*(uf.x[1] + uf.x[])*f[];
          uext2.x[] = 0.5*(uf.x[1] + uf.x[])*(1. - f[]);
        }
      }

    We reconstruct a signed distance field from the volume fraction, since the extrapolations need interface normals defined over the whole domain of extrapolation.

      scalar f1[], f2[], ls1[], ls2[];
      foreach() {
        f1[] = (f[] > 1.e-10) ? f[] : 0.;
        f2[] = (1. - f[] > 1.e-10) ? (1. - f[]) : 0.;
      }
      vof_to_ls (f1, ls1);
    
      foreach()
        ls2[] = -ls1[];

    We apply the constant extrapolations on the two velocities.

      double dtext = 0.5*L0/(1 << grid->maxdepth);
      //constant_extrapolation (uext1.x, ls1, dtext, 10, c=f1, nl=2);
      //constant_extrapolation (uext1.y, ls1, dtext, 10, c=f1, nl=2);
      constant_extrapolation (uext1.x, ls1, dtext, 10, c=f1, nl=nl1);
      constant_extrapolation (uext1.y, ls1, dtext, 10, c=f1, nl=nl1);
      constant_extrapolation (uext2.x, ls2, dtext, 10, c=f2, nl=nl2);
      constant_extrapolation (uext2.y, ls2, dtext, 10, c=f2, nl=nl2);

    Finally, we reconstruct the face velocities from the extrapolated colocated velocities by linear interpolation.

      foreach_face() {
        ufext1.x[] = 0.5*(uext1.x[] + uext1.x[-1]);
        ufext2.x[] = 0.5*(uext2.x[] + uext2.x[-1]);
      }

    The extrpolation procedure does not guarantee the final velocity to be divergence-free. Therefore, we cancel errors on the velocity divergence by solving an additional Poisson equation: \displaystyle \nabla \cdot \left( \alpha \nabla \phi \right) = \nabla \cdot u_f^E \\ which is used to correct the extrapolated velocity according to: \displaystyle \mathbf{u}_f^E += \nabla \phi

      face vector ufs1[], ufs2[];
    
      mgpdiv1 = project_div1 (ufs1, ps1, alpha, dt, mgpdiv1.nrelax);
      mgpdiv2 = project_div2 (ufs2, ps2, alpha, dt, mgpdiv2.nrelax);
    
      foreach_face() {
        ufext1.x[] -= ufs1.x[];
        ufext2.x[] -= ufs2.x[];
      }
    }

    References

    [palmore2019volume]

    John Palmore Jr and Olivier Desjardins. A volume of fluid framework for interface-resolved simulations of vaporizing liquid-gas flows. Journal of Computational Physics, 399:108954, 2019.