sandbox/Antoonvh/diurnalLES.c
Large-eddy simulation of a diunral cycle
This page discusses the setup and inplementation for the large-eddy
simulation (LES) of a idealized diunral cycle of the dry atmospheric
boundary layer (ABL). We use an adaptive octree grid, the Navier-Stokes
solver, a tracer and the subgrid-scale (SGS) model for turbulent mixing
of Vreman (2004). Furthermore we will view
our results.
This setup enherites quite some features of the single-column model (SCM), that was run for the same case.
#include "grid/octree.h" //<- Uncomment for *the* 3D simulation
#include "navier-stokes/centered.h"
#include "tracer.h"
#include "diffusion.h"
#include "SGS.h"
#include "view.h"
A macro is defined to find b_{surf} (see SCM page)
#define BSURF ((b[0,1] - b[]*c[level])/(1. - c[level]))
The buoyancy field (b) is an active tracer field.
scalar b[], * tracers = {b};
To ensure no spurious fluxes at the top and bottom boundaries, we set a boundary condition for the eddy viscosity.
The bottom buoyancy flux is computed as the sum of G and Q^* (see the SCM page)
#define Gbflx (Lambda*(beq - BSURF))
#define Qn (max (B0*sin(2.*M_PI*t/T), B1))
A damping-layer tendency can be defined for a quantity
s
.
double S = 0.1;
#define damping(s) (-S*min( sq(y - (L0*0.35)), 1.)*(y >(L0*0.35))*(s))
The values for the dimensionless parameters are listed below;
double Pi1 = -6.;
double Pi2 = 2160.;
double Pi3 = 10.;
double Pi4 = 5366.;
double Pi5 = 0.5;
double Pi6 = 5000.;
double Pi7 = 1.;
double L0Lc = 3.; // L0/Lc
Two dimensional parameters give rise to “lattice units” or simulation units. We choose a normalized length scale for L_c and a cycle time-scale of 24*60=1440 units, i.e. inspired by the (dimensionless) number of minutes in a 24 hour period.
double Lc = 1.; // A normalized length scale.
double T = 1440.; // = 24*60 time units in a cycle
double beq = 0.; // Equal to $theta_{ref}$
double k = 0.4; // Von Karmann constant
double lut[19], c[19];// Loop-up tables
Adaptive refinement parameters
These values should not affect the dynamics. However they do affect the absolute values of all physical fields and how they are represented in the simulation. Hence the refinement criteria should be defined consistently.
double be, ue; // The criteria for buoyancy and the velocities
double uemin, bemin; // Minimum for u and b
double fracu = 2.; // Fraction of the convective velocity scale
double fracb = 1.; // Fraction of the convective buoyancy scale
int maxlevel = 8;
These global variables are usefull and their values depend on the values provided above and are calculated in the “main” function.
double B0, B1, Nb, Lambda, Ugeo, f, zom, zoh, zi;
The buoyancy will be linked to the acceleration via the
av
face vector field.
face vector av[];
int main(){
For the pressure field a third-order-accurate prolongation and
refinment attribute for grid-alligned variations are set. For the
momentum, we choose conservative refinement and the gradients in the
buoyancy field are computed with the generalized mindmod2
slope limiter, this helps with advection of sharp buoyancy fronts.
.prolongation = p.refine = refine_linear;
pforeach_dimension()
.x.refine = refine_linear;
u.gradient = minmod; b
Next we compute the physical parameters from the dimensionless groups, L_c and T (see the SCM page).
= Pi2/T;
Nb = Pi3/T;
f = sq(Lc*Nb)*M_PI/(2.*T);
B0 = B0/Pi1;
B1 = sqrt(B0*T)/Pi4;
Lambda = Pi5*pow(B0*Lc, 1./3.);
Ugeo = Lc / Pi6;
zom = zom * Pi7;
zoh = L0Lc*Lc; L0
Aditionally, we express the minimum refinment criertia values as \zeta_{u,\mathrm{min}}= U_c/10 and \zeta_{b,\mathrm{min}}= b_c / 5. The boundary conditions at the surface are consistent with no-slip, and we enforce the initialized buoyancy value at the top.
= pow(B0*Lc, 1./3.)/10.;
uemin = pow(sq(B0)/Lc, 1./3.)/5.;
bemin = uemin;
ue = bemin;
be = 0;
Y0 = -L0/2;
X0 = Lc;
zi periodic (left);
=av;
a.t[bottom] = dirichlet (0.);
u[top] = dirichlet (y*sq(Nb));
b#if (dimension == 3)
.r[bottom] = dirichlet (0.);
uperiodic (back);
= X0;
Z0 #endif
For future reference, we save some parameter values of this
run()
to a file called systemparameters
.
FILE * fpstart = fopen ("systemparameters","w");
fprintf (fpstart, "Pi1=%g\nPi2=%g \nPi4=%g \nPi6=%g \nPi7=%g\n " \
"Nb=%g \nB0=%g \nB1=%g \nLambda=%g Ug =%g \nml = %d\n", \
, Pi2, Pi4, Pi6, Pi7, Nb, B0, B1, Lambda, Ugeo, maxlevel);
Pi1fprintf (fpstart,"zi = %g and %g \nbcq=%g\n bs = %g \nsqNb=%g\n" , \
, pow((2.*B0*T/(M_PI*sq(Nb))), 0.5), pow((2*B0*T*sq(Nb)/M_PI), 0.5),
Lc/Lambda, sq(Nb));
B1fflush (fpstart);
fclose (fpstart);
= 1 << 4;
N run();
}
Initialization
This event takes care of the initialization:
event init(t=0){
Since we are not bothered to initialize a pressure, the solver will
have to find it in a short while. As such a small initial timestep
(DT
) and tolerance on the residuals for the Poisson
problems are used.
= 10E-5;
TOLERANCE = 10E-3; //Small values DT
If possible, we restore for a dumpfile. The name of file can be changed manually.
if (!restore("dumpt=390")){ //e.g.
Else we refine the grid close to the surface and initialize mean profiles.
refine (level < (maxlevel - 1) && y < (Lc/10.));
refine (level < maxlevel && y < (Lc/20.));
foreach(){
[] = sq(Nb)*y + (noise()*be/5.);
b.y[] = noise()*ue/10.;
u.z[] = Ugeo;
u}
}
boundary (all);
[0] = 0.; lut
Look-up tables
We also make a lookup table for the aerodynamic drag coefficient (C_D) at the wall,
u_{*,i}^2 = C_D * u_i^2,
Using the law of the wall
u = \frac{u_*}{k} \mathrm{log}\left( \frac{z}{z_0} \right) .
And a look-up table for the logaritmic extrapolation coefficients of the buoyancy profile. (see the SCM page)
for (int j = 1; j <= maxlevel; j++){
double d = (L0/((double)(1 << j)))/zoh;
double d2 = (L0/((double)(1 << j)))/zom;
[j] = (log(4.*d) - 1.)/(log(d) - 1.);
c[j] = sq(k/log(d2));
lut}
}
event tracer_diffusion (i++){
The heatflux tendencies at the surface and within the buoyancy damping layer are time integrated using a rather unsophisticated forward-Euler method. Noting that since \Pi_2 >> 1, the evolution of the surface buoyancy flux is slow (time scale T) compared to the evolution of the flow at the discretization time scale (via CFL-criterion).
foreach(){
[] += dt*damping (b[] - sq(Nb)*y);
bif (y< Delta)
[] += dt*(Gbflx + Qn)/Delta; // <-- This is the surface buoyancy flux
b}
}
Momentum forcing
Here we implement
- The acceleration of gravity is implemented with a buoyancy
formulation.
- The so-called “sponge layer”.
- The acceleration due to background rotation
- The tendencies due to the surface shear stress, based on a law of the wall.
event acceleration (i++){
Notice how in the associated work, the coordinates referred to as
x,y,z are substitued in the simulation
by z,x,y
and as such; \{u,v,w\}
\rightarrow \{u.z ,u.x, u.z
\}. respectively. This curiosity is rooted in
the fact that Basilisk’s y
-coordinate is associated with
the bottom
and top
boundary, and a
corresponding arbitrary choice.
foreach_face(x) //v
.x[] += f*(Ugeo - ((u.z[] + u.z[-1])/2.)) + damping((u.x[] + u.x[-1])/2.);
avforeach_face(z) //u
.z[] += f*((u.x[] + u.x[0,0,-1])/2.) + damping(((u.z[] + u.z[0,0,-1])/2. - Ugeo));
avforeach_face(y) //w
.y[]+=(gr.y*(b[]+b[0,-1])/2.) + damping((u.y[]+u.y[0,-1])/2.);
avforeach(){
if (y + Y0 < Delta){
.x[] -= dt*sign(u.x[])*lut[level]*sq(u.x[])/Delta;
u.z[] -= dt*sign(u.z[])*lut[level]*sq(u.z[])/Delta;
u}
}
}
## Adaptivity
Due to the large degree of scale separation over the course of the simulation run, it seems attractive to use an adatpive grid approach. The grid adaptation strategy is based on a multi-resolution/wavelet analysis of the solution structure at the grid level. This may be viewed as a “feature detection algorithm”.
double Lc, Uc;
event adapt (i++){
The small TOLERANCE
is relaxed over time.
= min (TOLERANCE*1.01, 1E-3); TOLERANCE
Since the simulation evolves over time, we use a dynamic meassure to express the refinement criteron (adaptive adaptation). For that purpose the convective velocity-fluctuation scale (\mathcal{U}_c) and the convective buoyancy-fluctuation scale: b_c according to:
\mathcal{U}_c = \left( \mathrm{L}_c B \right) ^{1/3},
b_c = \left(\frac{B}{L_c}\right)^{1/3},
with \mathcal{L}_c the height of the well-mixed layer according to,
\mathcal{L}_c^2 = \frac{2}{N^2}\int \langle b\rangle -N^2z \mathrm{d}z.
This assumes a positive B, for the stable part of the simulation we default to the aforementioned minimal values for the refinement criteria.
double uen = 0.;
double ben = 0.;
double H = 0;
double mxdbdz = 0;
double bint = 0;
foreach (reduction(max:mxdbdz) reduction(+:H) reduction(+:bint)){
+= (b[] - (sq(Nb)*y))*dv();
bint if ((b[0,1] - b[])/(Delta) > mxdbdz)
= (b[0,1] - b[])/(Delta);
mxdbdz if (y<Delta){
double m = sq(Delta);
+= (Qn + Gbflx) * m;
H }
}
= min (DT*1.02, 1./(Nb*((double)(1<<5))));
DT = 0.001;
Lc if (H > 0 && bint > 0){
= (1./(L0*pow(sq(Nb),0.5)))*pow(2*bint, 0.5);
Lc = pow((H/sq(L0))*Lc, 1./3.);
Uc = max (Uc/fracu, uemin);
uen double Bs = pow(sq(H/(sq(L0)))/Lc, 1./3.);
= max (Bs/fracb, bemin);
ben } else {
= uemin;
uen = bemin;
ben }
To actual refinement criterion has a small history.
= (ue + uen)/2.;
ue = (be + ben)/2.;
be fprintf (stderr, "ue = %g be = %g DT = %g, Lc = %g, 1/sqN*8 = %g\n",
, be, DT, Lc, 1./(sqrt(mxdbdz)*((double)(1<<3)))); ue
An if
statement ensures that the grid is not adapted to
be too coarse in the first few physical minutes before the atmospheric
dynamics occur…
if (t > 60./((double)(maxlevel - 4)))
((scalar*){b, u}, (double[]){be, ue, ue, ue}, maxlevel);
adapt_wavelet fflush (stdout);
}
The last event
The simulation stops once t = T.
event end (t = T);
Output
The main output features:
- Simulation dump files for post processing and simulation
restarts.
- Data from a virtual observation tower.
- Vertical profiles (i.e. horizontal averages) of the solution
fields.
- A movie.
- Time-serie data of domain-integrated and/or surface-integrated quantities.
For a cleaner appearance of this page, it was chosen to separate
the
burden of defining the case setup from the output routines between
different files. As such, the output routines are presented in a
header file (click the link for details):
#include "diagnostics.h"