src/test/porous1.c

Stokes flow through a complex porous medium, randomly refined

This is similar to porous.c but tougher since the mesh refinement is now completely arbitrary (i.e. independent from the solution). This further tests robustness of the treatment of arbitrary embedded boundaries, with arbitrary levels of refinement.

Randomly refined mesh and embedded boundary

Randomly refined mesh and embedded boundary

#include "embed.h"
#include "navier-stokes/centered.h"
#include "view.h"

int maxlevel = 5;

The porous medium is the same as in porous.c.

void porous (scalar cs, face vector fs)
{
  int ns = 800;
  double xc[ns], yc[ns], R[ns];
  srand (0);
  for (int i = 0; i < ns; i++)
    xc[i] = 0.5*noise(), yc[i] = 0.5*noise(), R[i] = 0.01 + 0.02*fabs(noise());

Once we have defined the random centers and radii, we can compute the levelset function ϕ representing the embedded boundary.

  vertex scalar φ[];
  foreach_vertex() {
    φ[] = HUGE;

Since the medium is periodic, we need to take into account all the disk images using periodic symmetries.

    for (double xp = -L0; xp <= L0; xp += L0)
      for (double yp = -L0; yp <= L0; yp += L0)
	for (int i = 0; i < ns; i++)
	  φ[] = intersection (φ[], (sq(x + xp - xc[i]) +
					sq(y + yp - yc[i]) - sq(R[i])));
    φ[] = -φ[];
  }
  boundary ({φ});

  fractions (φ, cs, fs);
  fractions_cleanup (cs, fs);
}

The domain is the periodic unit square centered on the origin.

int main()
{
  origin (-0.5, -0.5);
  periodic (right);
  periodic (top);

We turn off the advection term. The choice of the maximum timestep and of the tolerance on the Poisson and viscous solves is not trivial. This was adjusted by trial and error to minimize (possibly) splitting errors and optimize convergence speed.

  stokes = true;
  DT = 2e-5;
#if 1
  TOLERANCE = HUGE;
  NITERMIN = 2;
#else
  TOLERANCE = 1e-3;
  NITERMIN = 2;
#endif
  N = 1 << maxlevel;

  run();
}

scalar un[];

event init (t = 0)
{

The mesh is refined randomly along the embedded boundary.

  porous (cs, fs);
  refine (cs[] && cs[] < 1. &&
	  level < 6 && (int)(100.*rand()/(double)RAND_MAX) == 0);
  porous (cs, fs);
  refine (cs[] && cs[] < 1. &&
	  level < 7 && (int)(200.*rand()/(double)RAND_MAX) == 0);
  porous (cs, fs);
  refine (cs[] && cs[] < 1. &&
	  level < 8 && (int)(400.*rand()/(double)RAND_MAX) == 0);

We define the porous embedded geometry.

  porous (cs, fs);
  dump();

  FILE * fp = fopen ("inter", "w");
  output_cells (fp);
  output_facets (cs, fp, fs);
  foreach()
    fprintf (fp, "cs %g %g %g\n", x, y, cs[]);
  foreach_face()
    fprintf (fp, "fs %g %g %g\n", x, y, fs.x[]);
  fclose (fp);

The gravity vector is aligned with the channel and viscosity is unity.

  const face vector g[] = {1.,0.};
  a = g;
  μ = fm;

The boundary condition is zero velocity on the embedded boundary.

  u.n[embed] = dirichlet(0);
  u.t[embed] = dirichlet(0);

We initialize the reference velocity.

  foreach()
    un[] = u.x[];
}

We check for convergence of the solution.

event logfile (i++; i <= 400)
{
  double avg = normf(u.x).avg, du = change (u.x, un)/(avg + SEPS);
  fprintf (ferr, "%d %d %d %d %d %d %d %d %.3g %.3g %.3g %.3g %.3g\n",
	   maxlevel, i,
	   mgp.i, mgp.nrelax, mgp.minlevel,
	   mgu.i, mgu.nrelax, mgu.minlevel,
	   du, mgp.resa*dt, mgu.resa, statsf(u.x).sum, normf(p).max);
}
Convergence history

Convergence history

See also