src/navier-stokes/mac.h

Incompressible Navier–Stokes solver (MAC formulation)

We wish to approximate numerically the incompressible Navier–Stokes equations tu+(uu)=p+(νu) u=0

We will use the generic time loop, a CFL-limited timestep and we will need to solve a Poisson problem.

#include "run.h"
#include "timestep.h"
#include "poisson.h"

The Markers-And-Cells (MAC) formulation was first described in the pioneering paper of Harlow and Welch, 1965. It relies on a face discretisation of the velocity components u.x and u.y, relative to the (centered) pressure p. This guarantees the consistency of the discrete gradient, divergence and Laplacian operators and leads to a stable (mode-free) integration.

scalar p[];
face vector u[];

The only parameter is the viscosity coefficient ν.

The statistics for the (multigrid) solution of the Poisson problem are stored in mgp.

double ν = 0.;
mgstats mgp;

We need to explicitly set the zero normal velocity condition.

u.n[left]   = 0;
u.n[right]  = 0;
u.n[top]    = 0;
u.n[bottom] = 0;

We apply boundary conditions after user initialisation.

event init (i = 0)
{
  boundary ({p,u});
}

Time integration

Advection–Diffusion

In a first step, we compute u* u*undt=S with S the symmetric tensor S=uu+νu=(ux2+2νxuxuxuy+ν(yux+xuy)uy2+2νyuy)

The timestep for this iteration is controlled by the CFL condition (and the timing of upcoming events).

double dtmax;

event set_dtmax (i++,last) dtmax = DT;

event stability (i++,last) {
  dt = dtnext (timestep (u, dtmax));
}

event advance (i++,last)
{

We allocate a local symmetric tensor field. To be able to compute the divergence of the tensor at the face locations, we need to compute the diagonal components at the center of cells and the off-diagonal component at the vertices.

Staggering of \mathbf{u} and \mathbf{S}

Staggering of u and S

  symmetric tensor S[]; // fixme: this does not work on trees

We average the velocity components at the center to compute the diagonal components.

  foreach()
    foreach_dimension()
      S.x.x[] = - sq(u.x[] + u.x[1,0])/4. + 2.*ν*(u.x[1,0] - u.x[])/Δ;

We average horizontally and vertically to compute the off-diagonal component at the vertices.

  foreach_vertex()
    S.x.y[] = 
      - (u.x[] + u.x[0,-1])*(u.y[] + u.y[-1,0])/4. +
      ν*(u.x[] - u.x[0,-1] + u.y[] - u.y[-1,0])/Δ;

We only need to apply boundary conditions to the diagonal components.

  boundary ({S.x.x, S.y.y});

Finally we compute u*=un+dtS

  foreach_face()
    u.x[] += dt*(S.x.x[] - S.x.x[-1,0] + S.x.y[0,1] - S.x.y[])/Δ;
}

Projection

In a second step we compute un+1=u*Δtp with the condition un+1=0 This gives the Poisson equation for the pressure (p)=u*Δt

event projection (i++,last)
{
  boundary ((scalar *){u});
  const face vector unity[] = {1.,1.};
  mgp = project (u, p, unity, dt, mgp.nrelax);
}

Usage

Tests