sandbox/farsoiya/marangoni_surfactant/surfactant-transport.h

    //#include "LS_reinit.h"
    #include "redistance2.h"
    #include "diffusion.h"
    #include "tracer.h"
    
    
    bool advect_diff_phase_field = 0;
    int reinit_skip_steps = 100;
    bool surfactant = 1;
    
    
    scalar c1[], gamma2[],d2[], pfield[]; //volumetric surfactant conc, area surfactant conc, signed distance, phase-field respectively
    
    scalar * tracers = {c1,d2,pfield};
    
    double zeta = 1. [*]; // Velocity scale parameter >= max|u| for CFL/boundedness criteria
    //Epsilon is the interface thickness parameter > 0.5 delta x. Cannot avoid macro here because I need the smallest delta x at all times
    #define EPSILON ((L0/(1 << grid->maxdepth))*0.75) 
    double D_s = 0.01 [*];  //Surface Diffusivity of surfactant
    double varepsilon = 1.e-6; // \varepsilon in paper 
    double sharpening_coefficient = 2. ; // a in Jain 2023 
    
    
    struct HDiffusion {
      face vector D;
      face vector beta;
    };
    
    static void h_relax (scalar * al, scalar * bl, int l, void * data)
    {
      scalar a = al[0], b = bl[0];
      struct HDiffusion * p = (struct HDiffusion *) data;
      face vector D = p->D, beta = p->beta;
    
      scalar c = a;
      foreach_level_or_leaf (l) {
        double n = - sq(Delta)*b[], d = cm[]/dt*sq(Delta);
        foreach_dimension() {  
          n += D.x[1]*a[1] + D.x[]*a[-1] +
    	Delta*(beta.x[1]*a[1] - beta.x[]*a[-1])/2.;
          d += D.x[1] + D.x[] - Delta*(beta.x[1] - beta.x[])/2.;
        }
        c[] = n/d;
      }
    }
    
    static double h_residual (scalar * al, scalar * bl, scalar * resl, void * data)
    {
      scalar a = al[0], b = bl[0], res = resl[0];
      struct HDiffusion * p = (struct HDiffusion *) data;
      face vector D = p->D, beta = p->beta;
      double maxres = 0.;
    #if TREE
      /* conservative coarse/fine discretisation (2nd order) */
      face vector g[];
      foreach_face()
        g.x[] = D.x[]*face_gradient_x (a, 0) + beta.x[]*face_value (a, 0);
      foreach (reduction(max:maxres)) {
        res[] = b[] + cm[]/dt*a[];
        foreach_dimension()
          res[] -= (g.x[1] - g.x[])/Delta;
        if (fabs (res[]) > maxres)
          maxres = fabs (res[]);
      }
    #else // !TREE
      /* "naive" discretisation (only 1st order on trees) */
      foreach (reduction(max:maxres)) {
        res[] = b[] + cm[]/dt*a[];
        foreach_dimension()
          res[] -= (D.x[1]*face_gradient_x (a, 1) -
    		D.x[0]*face_gradient_x (a, 0) +
    		beta.x[1]*face_value (a, 1) -
    		beta.x[0]*face_value (a, 0))/Delta;  	  
        if (fabs (res[]) > maxres)
          maxres = fabs (res[]);
      }
    #endif // !TREE    
      return maxres;
    }
    
    event stability(i++){
    	
     double maxvel = 1.e-6 [*];
    
      if (surfactant ){
        foreach_face(reduction(max:maxvel))
        if (u.x[] != 0.) {
          // double positivity_criteria = 2.*D_s/(fabs(u.x[]) + D_s*sharpening_coefficient/2./epsilon); Delta/fabs(u.x[]);
          // assert(Delta < positivity_criteria);
              double deltas = (pfield[]*(1. - pfield[]))/EPSILON;
    
          if (deltas > 0.01){
            if (fabs(u.x[]) > maxvel) maxvel = fabs(u.x[]);
          }
        }
        //smallest grid size
        
        double deltaxmin = 1. [*] ;
        deltaxmin = L0/(1 << grid->maxdepth) ;
        
      //   double positivity_criteria = 2.*D_s/(maxvel + D_s*sharpening_coefficient/2./EPSILON);
    //    if (deltaxmin > positivity_criteria){
    //	    printf("Warning: positivity criteria not fullfilled, increase resolution, dxmin = %e pc = %e level = %d",deltaxmin,positivity_criteria,grid->maxdepth);
    //    }
         // assert(deltaxmin < positivity_criteria);
        zeta = 1.1*maxvel; //zeta must be more than maxvel
        // assert(zeta > maxvel);
        double dt = dtmax;
      // time restriction for phase-field transport
      if (advect_diff_phase_field){
         dt = 0.75*sq(deltaxmin)/zeta/EPSILON;
          if (dt < dtmax)
            dtmax = dt;
      }
        // time restriction for surfactant-transport  
        dt = 0.75*sq(deltaxmin)/4./D_s;
        #if dimension == 3
          dt =  0.75*sq(deltaxmin)/6./D_s;
        #endif
          // if (dt < dtmax)
          //   dtmax = dt;
      }
    
    }
    int counter = 0;
    
    //small values pfield anywhere else in domain cause
    //c1 to leak hence clamp2 keeps it in check
    //mass conservation of c1 isn't affected by this
    //vof takes care of fluid mass
    //conservation of pfield 
    double clamp2 (double a){
      if (a < 1.e-6)
        return 0.;
      else if (a > 1. - 1.e-6)
        return 1.;
      else
        return a;
    }
      
     
    //"properties" event is called by other routines too, we named it differently so that it is not called
    // more than once in a time step because redistancing is an expensive step
    event properties2 (i++)
    { 
      // If not redistancing at each step phase field must be transported
      if (reinit_skip_steps > 1)
       advect_diff_phase_field = 1;
       else
       advect_diff_phase_field = 0;
      // Avoid redistancing at the 0th step because d2 is initialized accurately at cheap cost
      // Skip redistancing and let the phase field advect and diffuse to save the cost
      if (counter >=0  && counter%reinit_skip_steps == 0){
    	scalar d2temp[];
        double deltamin = L0/(1 << grid->maxdepth);
        foreach()
          d2temp[] = (2.*f[] - 1.)*deltamin*0.75;
        #if TREE
          restriction({d2temp});
        #endif
        // LS_reinit (d2, dt = 0.5*L0/(1 << grid->maxdepth),
        //     it_max = 0.5*(1 << grid->maxdepth));
          redistance (d2temp, imax = 0.5*(1 << grid->maxdepth));
         printf("\n %d redistancing",i); fflush(stdout);
    	double d2max = statsf(d2temp).max;
    	double d2min = statsf(d2temp).min;
    	bool signed_distance_faulty = 0;	
    	if (d2max > 6. || d2min < -6.){
    
    		signed_distance_faulty = 1; //something went wrong in computing distance
    		counter = counter - 2; //try again after two computational steps
    	}	 	
    	if (!signed_distance_faulty){
    		foreach(){
    			d2[] = d2temp[];
    			pfield[] = 0.5*(1. - tanh((d2[])/2./EPSILON));
    			pfield[] = clamp2(pfield[]);
    		}
    		boundary({pfield});
    	} 
      }
    
      counter++;
    
    }
    event tracer_diffusion (i++)
    {
    if (surfactant){
    
      scalar psi[];
      face vector cflux[],geta[];
           
      face vector  beta[];
      scalar r[];
      //~ scalar pfield[];
      face vector D[];
    if (advect_diff_phase_field){
      foreach(){
        //~ pfield[] = 0.5*(1. - tanh((d[])/2./epsilon));
            pfield[] = clamp2(pfield[]);
    
        psi[] = EPSILON*log((pfield[] + varepsilon)/(1. - pfield[] + varepsilon));
      }
      boundary({psi});
    
        foreach_face(){      
          cflux.x[] = 0.;
          double psif = (psi[] + psi[-1])/2.;
          
          double gradpsi = (psi[] - psi[-1])/Delta;
          if (fabs(gradpsi) > varepsilon){
            #if dimension == 2
    
             double psiup = 0.25*(psi[] + psi[-1] + psi[0,1] + psi[-1,1]);
             double psidown = 0.25*(psi[] + psi[-1] + psi[0,-1] + psi[-1,-1]);
              double mag_grad_psi = sqrt(sq(psi[] - psi[-1]) + sq(psiup - psidown))/Delta;
            #endif
    
            #if dimension == 3
             
             double psiup = 0.25*(psi[0,0,0] + psi[-1,0,0] + psi[0,1,0] + psi[-1,1,0]);
             double psidown = 0.25*(psi[0,0,0] + psi[-1,0,0] + psi[0,-1,0] + psi[-1,-1,0]);
             
              double psifront = 0.25*(psi[0,0,0] + psi[-1,0,0] + psi[0,0,1] + psi[-1,0,1]);
             double psiback = 0.25*(psi[0,0,0] + psi[-1,0,0] + psi[0,0,-1] + psi[-1,0,-1]);
    
              double mag_grad_psi = sqrt(sq(psi[0,0,0] - psi[-1,0,0]) + sq(psiup - psidown) + sq(psifront - psiback))/Delta;
    
            #endif
    
            
            cflux.x[] = fm.x[]*(1. - sq(tanh(psif/2./EPSILON)))*gradpsi/mag_grad_psi;
          }
    
          D.x[] = fm.x[]*zeta*EPSILON;
          beta.x[] = 0.;
          
        }
    
        foreach(){
          #if dimension == 2
            r[] = - cm[]*pfield[]/dt + 0.25*zeta*(cflux.x[1] - cflux.x[] + (cflux.y[0,1] - cflux.y[]))/Delta;
          #endif
    
          #if dimension == 3
            r[] = - cm[]*pfield[]/dt + 0.25*zeta*(cflux.x[1,0,0] - cflux.x[0,0,0] + cflux.y[0,1,0] - cflux.y[0,0,0] + cflux.z[0,0,1] - cflux.z[0,0,0]  )/Delta;
    
          #endif
        }
    
    
       
       
        restriction ({D,beta, cm});
    
        struct HDiffusion q1;
        q1.D = D;
        q1.beta = beta;
        
        mg_solve ({pfield}, {r}, h_residual, h_relax, &q1);
       
    }
    
    
      
        foreach() {
          r[] = - cm[]*c1[]/dt;
        }
    
         foreach(){
    	    pfield[] = clamp2(pfield[]);
    
          psi[] = EPSILON*log((clamp(pfield[],0.,1.) + varepsilon)
                  /(1. - clamp(pfield[],0.,1.) + varepsilon));
        }
    
     
      foreach_face() {
    
        double phif = (pfield[] + pfield[-1])/2.;
        D.x[] = fm.x[]*D_s;
        double gradpsi = (psi[] - psi[-1])/Delta;
        beta.x[] = 0.;
        
            if (fabs(gradpsi) > varepsilon){
    
            #if dimension == 2  
            // The derivative of Tanh is too steep and linear interpolation messes it up
              double psiup = 0.5*(psi[0,1] + psi[-1,1]);
              double psidown = 0.5*( psi[0,-1] + psi[-1,-1]);
              double mag_grad_psi = sqrt(sq(psi[] - psi[-1]) + sq(psiup - psidown)/4.)/Delta;
    
            #endif
    
            #if dimension == 3
    
             double psiup = 0.5*(psi[0,1,0] + psi[-1,1,0]);
             double psidown = 0.5*( psi[0,-1,0] + psi[-1,-1,0]);
    
             double psifront = 0.5*(psi[0,0,1] + psi[-1,0,1]);
             double psiback = 0.5*( psi[0,0,-1] + psi[-1,0,-1]);
    
             double mag_grad_psi = sqrt(sq(psi[] - psi[-1]) + sq(psiup - psidown)/4.  + sq(psifront - psiback)/4.)/Delta;
    
            #endif
            beta.x[] = - D.x[]* sharpening_coefficient*(0.5 - phif)/EPSILON * gradpsi/mag_grad_psi ;
          }
        }
    
        restriction ({D, beta, cm});    
        struct HDiffusion q;
        q.D = D;
        q.beta = beta;
        
        
        mg_solve ({c1}, {r}, h_residual, h_relax, &q);
    
        
    }
    }

    References

    [farsoiya2024vof]

    Palas Kumar Farsoiya, Stephane Popinet, Howard A. Stone, and Luc Deike. A coupled volume of fluid - phase field method for direct numerical simulation of insoluble surfactant-laden interfacial flows and application to rising bubbles. prep, 2024.

    [jain2023model]

    Suhas S Jain. A model for transport of interface-confined scalars and insoluble surfactants in two-phase flows. arXiv preprint arXiv:2311.11076, 2023.