# Starting flow around a cylinder

This is a canonical case of complex boundary layer separation, inspired by the experiments of Bouard & Coutanceau, 1980. Notable early numerical simulations include the results of Koumoutsakos and Leonard, 1995, hereafter K & L, which will be used in the comparisons below.

We will solve the Navier–Stokes equations and add the cylinder using an embedded boundary.

The “double projection” method is necessary to avoid noise in the pressure field which would pollute the drag force results.

``````#include "embed.h"
#include "navier-stokes/centered.h"
#include "navier-stokes/double-projection.h"
#include "navier-stokes/perfs.h"
#include "view.h"
``````

High-resolution is needed to resolve the boundary layers properly. Mohaghegh et al., 2017 propose to use a maximum resolution of order $D/10/\sqrt{Re}$, with $Re$ the Reynolds number and $D$ the cylinder diameter. The cylinder diameter will be set to unity, and the domain size to 18, so that the corresponding levels of refinement are approximately 12 and 16 for $Re=1000$ and $Re=9500$, respectively.

``````int maxlevel = 12;  // 15/16 for Re = 9500, 12 for Re = 1000
double Re = 1000;   // or 9500
double cmax = 3e-3; // 1e-3 for Re = 9500, 3e-3 for Re = 1000``````

We need a field for viscosity, so that the embedded boundary metric can be taken into account.

``face vector muv[];``

We set a unit velocity inflow on the left and an outflow on the right.

``````u.n[left] = dirichlet(1);

u.n[right] = neumann(0);
p[right] = dirichlet(0);
pf[right] = dirichlet(0);``````

Command line arguments can be used to change the default parameters.

``````int main (int argc, char * argv[])
{
if (argc > 1)
maxlevel = atoi (argv[1]);
if (argc > 2)
Re = atof (argv[2]);
if (argc > 3)
cmax = atof (argv[3]);``````

The domain is $18×18$ and only half the cylinder is modelled.

``````  size (18);
origin (- L0/2.);``````

We set the viscosity field and tune the Poisson solver.

``````  μ = muv;
TOLERANCE = 1e-4;
NITERMIN = 2;

run();
}``````

The viscosity field needs to take the embedded boundary metric into account. Given that the diameter and velocity are set to one, the viscosity is just $1/Re$.

``````event properties (i++) {
foreach_face()
muv.x[] = fm.x[]/Re;
boundary ((scalar *){muv});
}``````

## Initial conditions

``````event init (t = 0)
{``````

We can restart from a dump file.

``  if (!restore ("restart")) {``

Otherwise, we first create a mesh initially refined only around the cylinder.

``    refine (level <= maxlevel*(1. - sqrt(fabs(sq(x) + sq(y) - sq(0.5)))/2.));``

Then initialize the embedded boundary with the unit-diameter cylinder.

``````    vertex scalar φ[];
foreach_vertex()
φ[] = sq(x) + sq(y) - sq(0.5);
fractions (φ, cs, fs);

foreach()
u.x[] = cs[]; // fixme: with 1 this results in sub-optimal adaptation
}
else { // restart``````

When we restart, we still need to restore the face fraction field fs, since it is not dumped.

``````    vertex scalar φ[];
foreach_vertex()
φ[] = sq(x) + sq(y) - sq(0.5);
fractions (φ, cs, fs);
}``````

The boundary condition is zero velocity on the embedded boundary.

``````  u.n[embed] = dirichlet_embed(0);
u.t[embed] = dirichlet_embed(0);
}``````

## Positions of the separation points

We would like to track the positions with time of the separation points on the surface of the cylinder, as done by K & L, 1995.

We first define a function which computes the vorticity at the surface of the cylinder and returns an array of $\left(\theta ,\omega \right)$ pairs, with $\theta$ the angular coordinate and $\omega$ the corresponding value of vorticity.

``````typedef struct {
double θ, ω;
} ThetaOmega;

int compar_theta (const void * a, const void * b)
{
const ThetaOmega * p1 = a, * p2 = b;
return p1->θ > p2->θ ? 1 : -1;
}

ThetaOmega * theta_omega()
{
Array * a = array_new();
foreach_leaf()
if (cs[] > 0. && cs[] < 1.) {
coord n, b;
embed_geometry (point, &b, &n);
x += b.x*Δ, y += b.y*Δ;

ThetaOmega t;
t.ω = embed_vorticity (point, u, b, n);
t.θ = atan2(y, x);
array_append (a, &t, sizeof (ThetaOmega));
}
qsort (a->p, a->len/sizeof(ThetaOmega), sizeof(ThetaOmega), compar_theta);
ThetaOmega t = {nodata, nodata};
array_append (a, &t, sizeof (ThetaOmega));
ThetaOmega * p = a->p;
free (a);
return p;
}``````

The zeros of the function approximated by the $\left(\theta ,\omega \right)$ array are then recorded, together with the corresponding time, in the file pointed to by fp.

``````void omega_zero (FILE * fp)
{
// fixme: this function will not work with MPI
ThetaOmega * a = theta_omega();
for (ThetaOmega * o = a; (o + 1)->θ != nodata; o++) {
ThetaOmega * o1 = o + 1;
if (o1->ω*o->ω < 0.)
fprintf (fp, "%g %g\n", t,
o->θ + o->ω*(o1->θ - o->θ)/
(o->ω - o1->ω));
}
free (a);
fflush (fp);
}``````

## Log files

The log file contains the time, timestep, pressure and viscous forces components exerted by the fluid on the cylinder.

The “omega” file contains the locations where the surface vorticity vanishes.

``````event logfile (i += 10)
{
coord Fp, Fmu;
embed_force (p, u, μ, &Fp, &Fmu);
fprintf (ferr, "%d %g %g %g %g %g %g\n", i, t, dt, Fp.x, Fp.y, Fmu.x, Fmu.y);

static FILE * fp = fopen ("omega", "w");
omega_zero (fp);
}``````

## Images and animations

We display the vorticity field and the corresponding adaptive mesh.

``````void display_omega (int width, int height)
{
view (fov = 1.68, tx = -0.0251023,
ty = 1e-12, // fixme: this is necessary to re-center the view
width = width, height = height);
draw_vof ("cs", filled = -1, fc = {1,1,1});
draw_vof ("cs");
squares ("omega", min = -12, max = 12, linear = 1, map = cool_warm);
mirror ({-1}) {
draw_vof ("cs");
squares (color = "level", min = 6, max = maxlevel);
}
}``````

Still images are created at times matching those in various papers.

``````event snapshots (t = 1.; t <= 3.; t += 1.)
{
scalar ω[];
vorticity (u, ω);

if (t == 1. || t == 3.) {
// Fig. 4.
view (fov = 0.470916, tx = -0.0197934, ty = -0.0258659,
width = 524, height = 480);
draw_vof ("cs", filled = -1, fc = {1,1,1});
draw_vof ("cs", filled = 0, lw = 1);
double max = Re == 9500 ? 40 : 12;
squares ("omega", min = -max, max = +max, linear = 1, map = cool_warm);
char name[80];
sprintf (name, "zoom-%g.png", t);
save (name);

draw_vof ("cs", lw = 2);
squares ("level");
sprintf (name, "cells-%g.png", t);
save (name);
}

if (t == 3.) {
// Fig. 3.
display_omega (640, 480);
save ("omega-3.png");
}

p.nodump = false;
char name [80];
sprintf (name, "dump-%g", t);
dump (name);
}``````

An animation is created. The iteration interval is adjusted depending on the maximum spatial resolution.

``````event movie (i += 10*(1 << (maxlevel - 12)))
{
scalar ω[];
vorticity (u, ω);
display_omega (1280, 960);
save ("omega.mp4");
}``````

## Surface vorticity profiles

We are also interested in the details on the surface of the cylinder, in particular surface vorticity.

``````void cpout (FILE * fp)
{
foreach_leaf()
if (cs[] > 0. && cs[] < 1.) {
coord b, n;
double area = embed_geometry (point, &b, &n);
x += b.x*Δ, y += b.y*Δ;

fprintf (fp, "%g %g %g %g %g %g\n",
x, // 1
y, // 2
atan2(y, x), // 3
embed_interpolate (point, p, b), // 4
area*Δ, // 5
embed_vorticity (point, u, b, n)); // 6
}
}

event surface_profiles (t = {0.5,0.9,1.5,2.5})
{
char name[80];
sprintf (name, "cp-%g", t);
FILE * fp = fopen (name, "w");
cpout (fp);
fclose (fp);
}

#if 0
event snapshot (i += 10) {
scalar ω[];
vorticity (u, ω);
p.nodump = false;
dump();

FILE * fp = fopen ("cp", "w");
cpout (fp);
fclose (fp);
}
#endif``````

The mesh is adapted according to the embedded boundary and velocity field.

``````event adapt (i++) {
}``````

## Results

### Re = 1000

Animation of the vorticity field and adaptive mesh, Re = 1000.

The final state at $tU/D=3$ can be compared with figure 3 (top row) of Mohahegh et al. 2017.

Note that the points of Figure 4 of K. & L. 1995 do not seem to match the data in Fig. 5a and 5b of the same paper, which explains the disagreement in the figure below. This agreement should be much better as can be seen on the more detailed surface vorticity figures below.

### Re = 9500

Animation of the vorticity field and adaptive mesh, Re = 9500, 16 levels.

The final state at $tU/D=3$ can be compared with figure 3 (bottom row) of Mohahegh et al. 2017.

## References

 [bouard1980] Roger Bouard and Madeleine Coutanceau. The early stage of development of the wake behind an impulsively started cylinder for 40 < Re < 104. Journal of Fluid Mechanics, 101(3):583-607, 1980. [koumoutsakos1995] Petros Koumoutsakos and A Leonard. High-resolution simulations of the flow around an impulsively started cylinder using vortex methods. Journal of Fluid Mechanics, 296:1-38, 1995. [mohaghegh2017] Fazlolah Mohaghegh and HS Udaykumar. Comparison of sharp and smoothed interface methods for simulation of particulate flows ii: Inertial and added mass effects. Computers & Fluids, 143:103-119, 2017.