sandbox/ghigo/src/test-stl/wind-turbine-flow.c
Incompressible flow past a fixed windturbine
#include "grid/octree.h"
#include "../myembed.h"
#include "../mycentered.h"
#include "distance.h"
#include "lambda2.h"
#include "view.h"
#include "../myperfs.h"
Reference solution
#define Re (1000.) // Reynolds number, u*l/nu
#define l (100.) // Characteristic size of the windturbine
#define uref (1.) // Reference velocity
#define tref ((l)/(uref)) // Reference time, tref=l/u
Importing the geometry from an stl file
This function computes the solid and face fractions given a pointer to an STL file, an adaptation criteria (maximum relative error on distance) and a minimum and maximum level.
#define scmin (1.e-14) // Minimun volume and face fraction,
void fraction_from_stl (scalar c, face vector f, FILE * fp, double eps,
int minlevel, int maxlevel)
{
We read the STL file and compute the bounding box of the model.
* p = input_stl (fp);
coord , max;
coord minbounding_box (p, &min, &max);
double maxl = -HUGE;
foreach_dimension()
if (max.x - min.x > maxl)
= max.x - min.x; maxl
We initialize the distance field on the coarse initial mesh and refine it adaptively until the threshold error (on distance) is reached.
scalar d[];
distance (d, p);
#if TREE
while (adapt_wavelet ({d}, (double[]){eps*maxl}, maxlevel, minlevel).nf);
#endif // TREE
We also compute the volume fraction from the distance field. We first construct a vertex field interpolated from the centered field and then call the appropriate VOF functions. We have to be carefull with the orientation of the normals (- sign here).
vertex scalar phi[];
foreach_vertex()
[] = -(d[] + d[-1] + d[0,-1] + d[-1,-1] +
phi[0,0,-1] + d[-1,0,-1] + d[0,-1,-1] + d[-1,-1,-1])/8.;
dboundary ({phi});
fractions (phi, c, f);
fractions_cleanup (c, f,
= (scmin), cmin = (scmin));
smin }
Setup
We need a field for viscosity so that the embedded boundary metric can be taken into account.
face vector muv[];
We also define a reference velocity field.
scalar un[];
We define the mesh adaptation parameters.
#define lmin (5) // Min mesh refinement level (l=5 is 3pt/l)
#define lmax (9) // Max mesh refinement level (l=9 is 50pt/l)
#define cmax (1.e-2*(uref)) // Absolute refinement criteria for the velocity field
int main ()
{
The domain is 1024\times 1024\times 1024.
= 1024.;
L0 (L0);
size (-(L0)/2., -(L0)/2., 0.); // Centered in x and y, z is the altitude origin
We set the maximum timestep.
= 1.e-2*(tref);
DT = 200; NITERMAX
We set the tolerance of the Poisson solver.
= 1.e-4;
TOLERANCE = 1.e-4*(uref); TOLERANCE_MU
We initialize the grid.
= 1 << (lmin);
N (N);
init_grid
run();
}
Boundary conditions
We use inlet boundary conditions in the y-direction.
.n[bottom] = dirichlet ((uref));
u.t[bottom] = dirichlet (0);
u.r[bottom] = dirichlet (0);
u[bottom] = neumann (0);
p[bottom] = neumann (0);
pf
.n[top] = neumann (0);
u.t[top] = neumann (0);
u.r[top] = neumann (0);
u[top] = dirichlet (0);
p[top] = dirichlet (0); pf
The boundary z=0 is the ground with a no-slip boundary condition.
.n[back] = dirichlet (0);
u.t[back] = dirichlet (0);
u.r[back] = dirichlet (0);
u[back] = neumann (0);
p[back] = neumann (0); pf
We give boundary conditions for the face velocity to “potentially” improve the convergence of the multigrid Poisson solver.
.n[left] = 0;
uf.n[right] = 0;
uf.n[bottom] = (uref);
uf.n[back] = 0;
uf.n[front] = 0; uf
Properties
event properties (i++)
{
foreach_face()
.x[] = (uref)*(l)/(Re)*fm.x[];
muvboundary ((scalar *) {muv});
}
Initial conditions
event init (i = 0)
{
We set the viscosity field in the event properties.
= muv; mu
We use “third-order” face flux interpolation.
#if ORDER2
for (scalar s in {u, p, pf})
.third = false;
s#else
for (scalar s in {u, p, pf})
.third = true;
s#endif // ORDER2
We use a slope-limiter to reduce the errors made in small-cells.
#if SLOPELIMITER
for (scalar s in {u}) {
.gradient = minmod2;
s}
#endif // SLOPELIMITER
#if TREE
When using TREE and in the presence of embedded boundaries, we should also define the gradient of u at the cell center of cut-cells.
#endif // TREE
We initialize the embedded boundary. We read the stl file that contains the network geometry.
FILE * fp = fopen ("wind-turbine-flow.stl", "r");
fraction_from_stl (cs, fs, fp, 1.e-4, (lmin), max (10, (lmax)));
fclose (fp);
#if TREE
When using TREE, we refine the mesh around the embedded boundary. Since we do not have an analytic expression for the level-set function, we perform this operation only once.
({cs}, (double[]) {1.e-30},
adapt_wavelet = (lmax), minlevel = (1));
maxlevel fractions_cleanup (cs, fs,
= (scmin), cmin = (scmin));
smin #endif // TREE
We also define the volume fraction at the previous timestep csm1=cs.
= cs; csm1
We define the no-slip boundary conditions for the velocity.
.n[embed] = dirichlet (0);
u.t[embed] = dirichlet (0);
u.r[embed] = dirichlet (0);
u[embed] = neumann (0);
p
.n[embed] = dirichlet (0);
uf.t[embed] = dirichlet (0);
uf.r[embed] = dirichlet (0);
uf[embed] = neumann (0); pf
We initialize the velocity.
We finally initialize the reference velocity field.
foreach()
[] = u.y[];
un}
Embedded boundaries
Adaptive mesh refinement
#if TREE
event adapt (i++)
{
({cs,u}, (double[]) {1.e-2,(cmax),(cmax),(cmax)},
adapt_wavelet = (lmax), minlevel = (1)); maxlevel
We do not reset the embedded fractions to avoid interpolation errors on the geometry as we do not have an analytic expression for the level-set function.
fractions_cleanup (cs, fs,
= (scmin), cmin = (scmin));
smin }
#endif // TREE
Outputs
event logfile (i++; t <= 100.*(tref))
{
We look for a stationary solution.
double du = change (u.y, un);
stats nx = statsf (u.x), ny = statsf(u.y), nz = statsf (u.z), np = statsf(p);
fprintf (stderr, "%d %g %g %d %d %d %d %d %d %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %ld %g\n",
, t/(tref), dt/(tref),
i.i, mgp.nrelax, mgp.minlevel,
mgp.i, mgu.nrelax, mgu.minlevel,
mgu.resb, mgp.resa,
mgp.resb, mgu.resa,
mgu.sum/(nx.volume + SEPS), nx.min, nx.max,
nx.sum/(ny.volume + SEPS), ny.min, ny.max,
ny.sum/(nz.volume + SEPS), nz.min, nz.max,
nz.sum/(np.volume + SEPS), np.min, np.max,
np,
du->tn, perf.t
grid);
fflush (stderr);
}
Snapshots
event snapshots (t = end)
{
view (fov = 16,
= {0.575, -0.191, -0.261, 0.752},
quat = -0.2,
ty = {0.3,0.4,0.6},
bg = 800, height = 800);
width
clear();
draw_vof ("cs", "fs", lw = 0.5);
save ("vof.png");
draw_vof ("cs", "fs", lw = 0.5);
squares ("u.x", n= {1,0,0}, map = cool_warm);
save ("ux.png");
draw_vof ("cs", "fs", lw = 0.5);
squares ("u.y", n= {1,0,0}, map = cool_warm);
save ("uy.png");
draw_vof ("cs", "fs", lw = 0.5);
squares ("u.y", n= {0,1,0}, map = cool_warm);
save ("uy-2.png");
draw_vof ("cs", "fs", lw = 0.5);
squares ("p", n= {1,0,0});
save ("p.png");
scalar l2[];
lambda2 (u, l2);
boundary ({l2});
draw_vof ("cs", "fs", lw = 0.5);
cells (n= {1,0,0});
squares ("u.y", n= {1,0,0}, map = cool_warm);
isosurface ("l2", -0.002);
save ("l2.png");
}
event animations (i += 10)
{
view (fov = 16,
= {0.575, -0.191, -0.261, 0.752},
quat = -0.2,
ty = {0.3,0.4,0.6},
bg = 800, height = 800);
width
clear();
cells (n= {1,0,0});
save ("mesh.mp4");
draw_vof ("cs", "fs", lw = 0.5);
squares ("u.x", n= {1,0,0}, map = cool_warm);
save ("ux.mp4");
draw_vof ("cs", "fs", lw = 0.5);
squares ("u.y", n= {1,0,0}, map = cool_warm);
save ("uy.mp4");
draw_vof ("cs", "fs", lw = 0.5);
squares ("u.y", n= {0,1,0}, map = cool_warm);
save ("uy-2.mp4");
draw_vof ("cs", "fs", lw = 0.5);
squares ("p", n= {1,0,0});
save ("p.mp4");
scalar l2[];
lambda2 (u, l2);
boundary ({l2});
draw_vof ("cs", "fs", lw = 0.5);
cells (n= {1,0,0});
squares ("u.y", n= {1,0,0}, map = cool_warm);
isosurface ("l2", -0.002);
save ("l2.mp4");
}
Results
\lambda_2 criteria