src/integral.h
Integral formulation for surface tension
See Al Saud et al., 2018 and Popinet & Zaleski, 1999 for details.
The surface tension field \sigma will be associated to each levelset tracer. This is done easily by adding the following field attributes.
extern scalar * tracers;
attribute {
  scalar sigmaf;
}Surface tension is a source term in the right-hand-side of the evolution equation for the velocity of the centered Navier–Stokes solver i.e. it is an acceleration. If necessary, we allocate a new vector field to store it.
event defaults (i = 0) {
  if (is_constant(a.x)) {
    a = new face vector;
    foreach_face() {
      a.x[] = 0.;
      dimensional (a.x[] == Delta/sq(DT));
    }
  }
}Stability condition
The surface tension scheme is time-explicit so the maximum timestep is the oscillation period of the smallest capillary wave. T = \sqrt{\frac{\rho_{m}\Delta_{min}^3}{\pi\sigma}} with \rho_m=(\rho_1+\rho_2)/2. and \rho_1, \rho_2 the densities on either side of the interface.
event stability (i++)
{
  double sigma = 0.;
  for (scalar d in tracers)
    if (is_constant (d.sigmaf))
      sigma += constant (d.sigmaf);
  double sigmamax = sigma;We first compute the minimum and maximum values of \alpha/f_m = 1/\rho, as well as \Delta_{min}.
  double amin = HUGE, amax = -HUGE, dmin = HUGE;
  foreach_face (reduction(min:amin) reduction(max:amax) reduction(min:dmin)
		reduction(max:sigmamax))
    if (fm.x[] > 0.) {
      if (alpha.x[]/fm.x[] > amax) amax = alpha.x[]/fm.x[];
      if (alpha.x[]/fm.x[] < amin) amin = alpha.x[]/fm.x[];
      if (Delta < dmin) dmin = Delta;The maximum timestep is set using the sum of surface tension coefficients.
      double sigmai = sigma;
      for (scalar d in tracers)
	if (!is_constant (d.sigmaf) && fabs(d[]) < 2.*Delta) {
	  scalar sigmaf = d.sigmaf;
	  sigmai += sigmaf[];
	}
      if (sigmai > sigmamax)
	sigmamax = sigmai;
    }
  double rhom = (1./amin + 1./amax)/2.;
  if (sigmamax) {
    double dt = sqrt (rhom*cube(dmin)/(pi*sigmamax));
    if (dt < dtmax)
      dtmax = dt;
  }
}Curvature
This function computes the curvature from the levelset function d using: \kappa = \frac{d^2_xd_{yy} - 2d_xd_yd_{xy} + d^2_yd_{xx}}{|\nabla d|^3}
#define CURVATURE 1 // set to 1 (resp. 2) to use curvature (resp. linear) interpolation of curvature
#if CURVATURE
static inline double distance_curvature (Point point, scalar d)
{
  double dx = (d[1] - d[-1])/2.;
  double dy = (d[0,1] - d[0,-1])/2.;
  double dxx = d[1] - 2.*d[] + d[-1];
  double dyy = d[0,1] - 2.*d[] + d[0,-1];
  double dxy = (d[1,1] - d[-1,1] - d[1,-1] + d[-1,-1])/4.;
  double dn = sqrt(sq(dx) + sq(dy)) + 1e-30;
  return (sq(dx)*dyy - 2.*dx*dy*dxy + sq(dy)*dxx)/cube(dn)/Delta;
}
#endif // CURVATURESurface tension term
The calculation of the acceleration is done by this event, overloaded from its definition in the centered Navier–Stokes solver.
#if AXI
# include "fractions.h"
#endif
event acceleration (i++)
{We check whether surface tension is associated with any levelset function d.
  for (scalar d in tracers)
    if (d.sigmaf.i) {
      (const) scalar sigma = d.sigmaf;
      
#if CURVATURE == 2We first compute the curvature.
      scalar kappa[];
      foreach()
	kappa[] = distance_curvature (point, d);
#endif // CURVATURE == 2We allocate the surface tension stress tensor “manually”, because the symmetries of the default tensor allocation in Basilisk are not what we want (this is messy).
      scalar Sxx[], Sxy[], Syy[], Syx[];
      tensor S; S.x.x = Sxx, S.x.y = Sxy, S.y.y = Syy, S.y.x = Syx;We compute the diagonal components of the tensor.
      foreach()
	foreach_dimension() {
	  S.y.y[] = 0.;
	  for (int i = -1; i <= 1; i += 2)
	    if (d[]*(d[] + d[i]) < 0.) {
	      double xi = d[]/(d[] - d[i]);
	      double nx = ((d[1] - d[-1])/2. +
			   xi*i*(d[-1] - 2.*d[] + d[1]))/Delta;
	      double sigmai = sigma[] + xi*(sigma[i] - sigma[]);
#if CURVATURE
#if CURVATURE == 2 // does not make much difference
              double ki = kappa[] + xi*(kappa[i] - kappa[]);
#else
	      double ki = distance_curvature (point, d);
#endif
	      S.y.y[] += sigmai*(fabs(nx)/Delta - sign(d[])*ki*(0.5 - xi));
#else // CURVATURE == 0Here we use the pressure jump instead of the curvature. See Appendix A of Al Saud et al., 2018. The noise induced by pressure jumps can be problematic for some cases however, for example for Marangoni translation at small capillary numbers Ca.
	      S.y.y[] += sigmai*fabs(nx)/Delta + (p[] - p[i])*(0.5 - xi);
#endif // CURVATURE == 0
	    }
        }We compute the off-diagonal components of the tensor.
      foreach_vertex()
	foreach_dimension()
	  if ((d[] + d[0,-1])*(d[-1] + d[-1,-1]) > 0.)
	    S.x.y[] = 0.;
	  else {
	    double xi = (d[-1] + d[-1,-1])/(d[-1] + d[-1,-1] - d[] - d[0,-1]);
	    double ny = (xi*(d[] - d[-1] + d[-1,-1] - d[0,-1]) +
			 d[-1] - d[-1,-1])/Delta;
	    double sigmai = (sigma[-1] + sigma[-1,-1] +
			     xi*(sigma[] + sigma[0,-1] - sigma[-1] -  sigma[-1,-1]))/2.;
	    S.x.y[] = - sigmai*sign(d[] + d[0,-1])*ny/Delta;
	  }Finally, we add the divergence of the surface tension tensor to the acceleration and the axisymmetric term if necessary.
      face vector av = a;
      foreach_face() {
	av.x[] += alpha.x[]/(fm.x[] + SEPS)*(S.x.x[] - S.x.x[-1] + S.x.y[0,1] - S.x.y[])/Delta;Axisymmetric term
The axisymmetric acceleration is computed using the volumetric surface tension formulation as \frac{1}{\rho}\sigma\kappa_\theta\mathbf{n}\delta_s with the principal axisymmetric curvature given by \kappa_\theta = \frac{n^r}{r} and using the approximation \mathbf{n}\delta_s\approx\mathbf{\nabla}f with f the volume fraction.
#if AXI
	coord n = {
	  (d[] - d[-1])/Delta,
	  (d[0,1] + d[-1,1] - d[0,-1] - d[-1,-1])/(4.*Delta)
	};
	extern scalar f;
	av.x[] -= alpha.x[]/sq(fm.x[] + SEPS)*(sigma[] + sigma[-1])/2.*n.y*(f[] - f[-1])/Delta;
#endif // AXI
      }
    }
}References
| [alsaud2018] | 
 Moataz O. Abu-Al-Saud, Stéphane Popinet, and Hamdi A. Tchelepi. A conservative and well-balanced surface tension model. Journal of Computational Physics, 371:896–913, February 2018. [ DOI | http | .pdf ]  | 
| [popinet1999] | 
 S. Popinet and S. Zaleski. A front-tracking algorithm for accurate representation of surface tension. International Journal for Numerical Methods in Fluids, 30(6):775–793, 1999.  | 
