sandbox/Antoonvh/nsrk.h

    A 2D finite-diference co-located Navier-Stokes-equation solver

    For the approximate solution to:

    \displaystyle \frac{\partial \mathbf{u}}{\partial t} + \left(\mathbf{u} \cdot \mathbf{\nabla}\right)\mathbf{u} = -\mathbf{\nabla} p + \nu \nabla^2 \mathbf{u},

    with the constraint that,

    \displaystyle \mathbf{\nabla} \cdot \mathbf{u} = 0.

    Method

    A Low-storage Runge-Kutta scheme is used to advance the centered velocity field u. Two additional scalar fields are defined that represent the pressure p and another field for extra projections p2, which is not a pressure. Both are a solution to a Poisson equation.

    #include "lsrk.h"
    #include "poisson.h"
    mgstats mgp, mgp2;
    int it_project = 5; 
    vector u[];
    scalar p[], p2[];
    double nu;
    
    event defaults (i = 0) 
      CFL = 1.3;
    
    mgstats projecter (vector v, scalar d, double Tol); // A prototype
    
    event init (t = 0) 
      projecter (u, p2, TOLERANCE); //Project the user-initialized field
    
    event call_timestep (t = 0) 
      event ("timestep");

    The timestep size is limited by the CFL condition and a Peclet condition, the latter scales to be more stringent with decreasing \Delta.

    \displaystyle \frac{\nu \mathtt{dt}}{\Delta^2} \leq \mathcal{P}e

    double Pe = 0.4;
    event timestep (i++, last) {
      double dtm = HUGE;
      foreach() {
        foreach_dimension() {
          if (fabs(u.x[]) > 0)
    	if (Delta/fabs(u.x[]) < dtm)
    	  dtm = Delta/fabs(u.x[]);
        }
      }
      dtm *= CFL;
      if (nu) {
        double Deltamin = L0/(1 << grid->maxdepth);
        dtm = min(dtm, Pe*sq(Deltamin)/nu);
      }
      dt = dtnext (min(DT, dtm));
    }

    The non-linear advection term

    For the advection term , 3rd-order accurate upwind differencing is used.

    #define ducx(uc) (u.x[] > 0 ? (2*uc[1]     + 3*uc[] -6*uc[-1]     + uc[-2])/6.     : \
    		  (-2.*uc[-1]     - 3.*uc[] + 6.*uc[1]     - uc[2])/6.)
    #define ducy(uc) (u.y[] > 0 ? (2*uc[0,1]   + 3*uc[] -6*uc[0,-1]   + uc[0,-2])/6.   : \
    		  (-2.*uc[0,-1]   - 3.*uc[] + 6.*uc[0,1]   - uc[0,2])/6.)
    #define ducz(uc) (u.z[] > 0 ? (2*uc[0,0,1] + 3*uc[] -6*uc[0,0,-1] + uc[0,0,-2])/6. : \
    		  (-2.*uc[0,0,-1] - 3.*uc[] + 6.*uc[0,0,1] - uc[0,0,2])/6.)
    
    void advect (vector du) {
      foreach()
        foreach_dimension()
          du.x[] = -(u.x[]*ducx(u.x) + u.y[]*ducy(u.x))/Delta;
    }

    Diffusion term

    2nd-order accurate diffusion via the centered Lapacian.

    void diffuse (vector du) {
      foreach() 
        foreach_dimension() 
          du.x[] += nu*(u.x[0,1] + u.x[1,0] + u.x[0,-1] + u.x[-1,0] - 4.*u.x[])/sq(Delta);
    }

    Pressure-gradient term

    The pressure is found as a solution to the Poisson equation:

    \displaystyle \nabla^2 p = \left(\frac{\partial u_x}{\partial x}\right)^2 + 2\frac{\partial u_x}{\partial y} \frac{\partial u_y}{\partial x} + \left(\frac{\partial u_y}{\partial y}\right)^2

    The right-hand side is computed using upwinding. The tendency due to the pressure gradient is added to du.

    mgstats pressure_term (vector du, scalar p, double Tol) {
      scalar rhs[];
      foreach() {   
        rhs[] = -(sq(ducx(u.x)) +
    	       2*ducy(u.x)*ducx(u.y) +
    	      sq(ducy(u.y)))/sq(Delta);
      }
      mgstats mg = poisson (p, rhs, tolerance = Tol);
      foreach() 
        foreach_dimension()
          du.x[] -= (p[1] - p[-1])/(2*Delta);
      return mg;
    }

    The Runge-Kutta step

    The functions above are called during the stages of the RK time integrator.

    void NS_step (scalar * ul, scalar * dul) {
      vector du = {dul[0], dul[1]};
      advect (du);
      if (nu) 
        diffuse (du);
      mgp = pressure_term (du, p, TOLERANCE/sq(dt));
    }

    Time integration

    Runge-Kutta Time integration is used. Because our methods are not exactly solenoidal, an additional projection of the solution field is performed every it_project-th iteration.

    mgstats projecter (vector v, scalar d, double Tol) {
      scalar div[];
      foreach() {
        div[] = 0;
        foreach_dimension()
          div[] += (v.x[1] - v.x[-1])/(2*Delta);
      }
      mgstats mg = poisson (d, div, tolerance = Tol);
      foreach() 
        foreach_dimension()
          v.x[] -= (d[1] - d[-1])/(2*Delta);
      boundary ((scalar*){v});
      return mg;
    }
    
    event advance (i++) {
      A_Time_Step ((scalar*){u}, dt, NS_step);
      boundary ((scalar*){u});
      if (i%it_project == 0)
        mgp2 = projecter (u, p2, TOLERANCE/sq(dt));
    }
    
    #if TREE
    event adapt (i++, last);
    #endif

    Tests