src/redistance.h

    Redistancing of a distance field

    The original implementation was by Alexandre Limare used in particular in Limare et al., 2022.

    This file implements redistancing of a distance field with subcell correction, see the work of Russo & Smereka, 2000 with corrections by Min & Gibou, 2007 and by Min, 2010.

    Let \phi be a function close to a signed function that has been perturbed by numerical diffusion (more precisely, a non-zero tangential velocity). By iterating on the eikonal equation \displaystyle \left\{\begin{array}{ll} \phi_t + \text{sign}(\phi^{0}) \left(\left| \nabla \phi\right| - 1 \right) = 0\\ \phi(x,0) = \phi^0(x) \end{array} \right. we can correct or redistance \phi to make it a signed function.

    We use a Godunov Hamiltonian approximation for \left| \nabla \phi\right| \displaystyle \left| \nabla \phi \right|_{ij} = H_G(D_x^+\phi_{ij}, D_x^-\phi_{ij}, D_y^+\phi_{ij}, D_y^-\phi_{ij}) where D^\pm\phi_{ij} denotes the one-sided ENO difference finite difference in the x- direction \displaystyle D_x^+ = \dfrac{\phi_{i+1,j}-\phi_{i,j}}{\Delta} - \dfrac{\Delta}{2}\text{minmod}(D_{xx}\phi_{ij}, D_{xx}\phi_{i+1,j}) \displaystyle D_x^- = \dfrac{\phi_{i,j}-\phi_{i-1,j}}{\Delta} + \dfrac{\Delta}{2}\text{minmod}(D_{xx}\phi_{ij}, D_{xx}\phi_{i+1,j}) here D_{xx}\phi_{ij} = (\phi_{i-1,j} - 2\phi{ij} + \phi_{i+1,j})/\Delta^2.

    The minmod function is zero when the two arguments have different signs, and takes the argument with smaller absolute value when the two have the same sign.

    The Godunov Hamiltonian H_G is given as \displaystyle H_G(a,b,c,d) = \left\{ \begin{array}{ll} \sqrt{\text{max}((a^-)^2,(b^+)^2 + (c^-)^2,(d^+)^2)} \text { when } \text{sign}(\phi^0_{ij}) \geq 0\\ \sqrt{\text{max}((a^+)^2,(b^-)^2 + (c^+)^2,(d^-)^2)} \text { when } \text{sign}(\phi^0_{ij}) < 0 \end{array} \right. with \displaystyle x^+ = \text{max}(0, x)\\ x^- = \text{min}(0, x)\\

    We use a minmod limiter.

    static inline double minmod3 (double a, double b)
    {
      if (a == b || a*b <= 0.)
        return 0.;
      return fabs(a) < fabs(b) ? a : b;
    }
    
    #define BGHOSTS 2

    Time derivative

    This function fills dphi with \partial_t\phi .

    static
    void dphidt (scalar phi, scalar dphi, scalar phi0, double cfl)
    {
      foreach() {
        double dt = cfl*Delta;

    We first calculate the inputs of the Hamiltonian \displaystyle D^+\phi_{ij} = \dfrac{\phi_{i+1,j} - \phi_{ij}}{\Delta x} - \dfrac{\Delta} {2}\text{minmod}(D_{xx}\phi_{ij},D_{xx}\phi_{i+1,j}) \displaystyle D^-\phi_{ij} = \dfrac{\phi_{i,j} - \phi_{i-1,j}}{\Delta x} - \dfrac{\Delta} {2}\text{minmod}(D_{xx}\phi_{ij},D_{xx}\phi{i-1,j}) where \displaystyle D_{xx}\phi_{ij} = \dfrac{\phi_{i-1,j} - 2\phi_{ij} + \phi_{i+1,j}}{\Delta x^2}

        coord gra[2];
        for (int i = 0, j = 1; i < 2; j = 1 - 2*++i)
          foreach_dimension() {
    	double s1 = (phi[2*j] + phi[] - 2.*phi[j])/Delta; 
    	double s2 = (phi[1] + phi[-1] - 2.*phi[])/Delta;
    	gra[i].x = j*((phi[j] - phi[])/Delta - minmod3(s1, s2)/2.);
          }

    We check for interfacial cells.

        bool interfacial = false;
        foreach_dimension()
          if (phi0[-1]*phi0[] < 0 || phi0[1]*phi0[] < 0)
    	interfacial = true;
        
        dphi[] = - sign2(phi0[]);
        
        if (interfacial) {

    Near the interface, i.e. for cells where \displaystyle \phi^0_i\phi^0_{i+1} \leq 0 \text{ or } \phi^0_i\phi^0_{i-1} \leq 0

    The scheme must stay truly upwind, meaning that the movement of the 0 level-set of the function must be as small as possible. Therefore the upwind numerical scheme is modified to \displaystyle D_x^+ = \dfrac{0-\phi_{ij}}{\Delta x^+} - \dfrac{\Delta x^+}{2} \text{minmod}(D_ {xx}\phi_{ij},D_{xx}\phi_{i+1,j}) \text{ if } \phi_{ij}\phi_{i+1,j} < 0 \displaystyle D_x^- = \dfrac{\phi_{ij}-0}{\Delta x^-} + \dfrac{\Delta x^-}{2} \text{minmod}(D_ {xx}\phi_{ij},D_{xx}\phi_{i-1,j}) \text{ if } \phi_{ij}\phi_{i+1,j} < 0 which is the correction by Min & Gibou 2007.

          double size = HUGE;
          foreach_dimension()
    	for (int i = 0, j = 1; i < 2; j = 1 - 2*++i)
    	  if (phi0[]*phi0[j] < 0.) {

    We compute the subcell fix near the interface.

    \displaystyle \Delta x^+ = \left\{ \begin{array}{ll} \Delta x \cdot \left( \dfrac{\phi^0_{i,j}-\phi^0_{i+1,j} - \text{sgn}(\phi^0_{i,j}-\phi^0_{i+1,j})\sqrt{D}}{}\right) \text{ if } \left| \phi^0_{xx}\right| >\epsilon \ \ \Delta x \cdot \dfrac{\phi^0_{ij}}{\phi^0_{i,j}-\phi^0_{i+1,j}} \text{ else.}\ \ \end{array} \right. with \displaystyle \phi_{xx}^0 = \text{minmod}(\phi^0_{i-1,j}-2\phi^0_{ij}+\phi^0_{i+1,j}, \phi^0_{i,j}-2\phi^0_{i+1j}+\phi^0_{i+2,j}) \ \ D = \left( \phi^0_{xx}/2 - \phi_{ij}^0 - \phi_{i+1,j} \right)^2 - 4\phi_{ij}^0\phi_{i+1,j}^0 For the \Delta x^- calculation, replace all the + subscript by -, this is dealt with properly with the j parameter below.

    	    double dx = Delta;
    	    double phixx = minmod3 (phi0[2*j] + phi0[] - 2.*phi0[j],
    				    phi0[1] + phi0[-1] - 2.*phi0[]);
    	    if (fabs(phixx) > 1./HUGE) {
    	      double D = sq(phixx/2. - phi0[] - phi0[j]) - 4.*phi0[]*phi0[j];
    	      dx *= 1/2. + (phi0[] - phi0[j] - sign2(phi0[] - phi0[j])*sqrt(D))/phixx;
    	    }
    	    else
    	      dx *= phi0[]/(phi0[] - phi0[j]);
    	    
    	    if (dx != 0.) {
    	      double sxx1 = phi[2 - 4*i] + phi[] - 2.*phi[1 - 2*i];
    	      double sxx2 = phi[1] + phi[-1] - 2.*phi[];
    	      gra[i].x = (2*i - 1)*(phi[]/dx + dx*minmod3(sxx1, sxx2)/(2.*sq(Delta)));
    	    }
    	    else 
    	      gra[i].x = 0.;
    	    size = min(size, dx);
    	  }
          dphi[] *= min(dt, fabs(size)/2.)/dt;
        }

    The Godunov Hamiltonian is \displaystyle H_G(a,b,c,d) = \left\{ \begin{array}{ll} \sqrt{\text{max}((a^-)^2,(b^+)^2 + (c^-)^2,(d^+)^2)} \text { when } \text{sign}(\phi^0_{ij}) \geq 0\\ \sqrt{\text{max}((a^+)^2,(b^-)^2 + (c^+)^2,(d^-)^2)} \text { when } \text{sign}(\phi^0_{ij}) < 0 \end{array} \right.

        double H_G = 0;
        if (phi0[] > 0)
          foreach_dimension() {
    	double a = min(0., gra[0].x); 
    	double b = max(0., gra[1].x);
    	H_G += max(sq(a), sq(b));
          }
        else
          foreach_dimension() {
    	double a = max(0., gra[0].x);
    	double b = min(0., gra[1].x);
    	H_G += max(sq(a), sq(b));
          }
        
        dphi[] *= sqrt(H_G) - 1.;
      }
    }

    The redistance() function

    trace
    int redistance (scalar phi,
    		int imax = 1,       // The maximum number of iterations
    		double cfl = 0.5,   // The CFL number
    		int order = 3,      // The order of time integration
    		double eps = 1e-6,  // The maximum error on $|\nabla\phi| - 1$
    		double band = HUGE, // The width of the band in which to compute the error
    		scalar resf = {-1}) // The residual $|\nabla\phi| - 1$
    {

    We create phi0[], a copy of the initial level-set function before the iterations.

      scalar phi0[];
      foreach()
        phi0[] = phi[] ;

    Time integration iteration loop.

      for (int i = 1; i <= imax; i++) {
        double maxres = 0.;

    We use either a RK2 scheme…

        if (order == 2) {
          scalar tmp1[];
          dphidt (phi, tmp1, phi0, cfl/2.);
          foreach()
    	tmp1[] = phi[] + Delta*cfl/2.*tmp1[];
          scalar tmp2[];
          dphidt (tmp1, tmp2, phi0, cfl);
          foreach (reduction(max:maxres)) {
    	double res = tmp2[];
    	if (resf.i >= 0) resf[] = res;
    	if (fabs (res) > maxres && fabs(phi0[]) < band*Delta) maxres = fabs (res);
    	phi[] += Delta*cfl*tmp2[];
          }
        }

    … or a RK3 compact scheme from Shu and Osher, 1988.

        else {
          scalar tmp1[];
          dphidt (phi, tmp1, phi0, cfl);
          foreach()
    	tmp1[] = phi[] + Delta*cfl*tmp1[];
          scalar tmp2[];
          dphidt (tmp1, tmp2, phi0, cfl);
          foreach()
    	tmp1[] = (3.*phi[] + tmp1[] + Delta*cfl*tmp2[])/4.;
          dphidt (tmp1, tmp2, phi0, cfl);
          foreach (reduction(max:maxres)) {
    	double res = 2./3.*((phi[] - tmp1[])/(Delta*cfl) - tmp2[]);
    	if (resf.i >= 0) resf[] = res;
    	if (fabs (res) > maxres && fabs(phi0[]) < band*Delta) maxres = fabs (res);
    	phi[] = (phi[] + 2.*(tmp1[] + Delta*cfl*tmp2[]))/3.;
          }
        }
        
        if (maxres < eps)
          return i;
      }
      return imax;
    }

    References

    [limare2022]

    Alexandre Limare, Stéphane Popinet, Christophe Josserand, Zhonghan Xue, and Arthur R. Ghigo. A hybrid level-set / embedded boundary method applied to solidification-melt problems. Journal of Computational Physics, December 2022. [ DOI | http | .pdf ]

    [min2010]

    Chohong Min. On reinitializing level set functions. Journal of Computational Physics, 229:2764–2772, 2010. [ DOI ]

    [min2007]

    Chohong Min and Frédéric Gibou. A second order accurate level set method on non-graded adaptive cartesian grids. Journal of Computational Physics, 225:300–321, 2007. [ DOI ]

    [russo2000]

    Giovanni Russo and Peter Smereka. A remark on computing distance functions. Journal of Computational Physics, 163(1):51–67, 2000.

    [shu1988]

    Chi-Wang Shu and Stanley Osher. Efficient implementation of essentially non-oscillatory shock-capturing schemes. Journal of Computational Physics, 77:439–471, 1988. [ DOI ]

    Usage

    Tests