sandbox/oystelan/waveflume_example/simple_waveflume.c
Simple flume with wavemaker
This little example which shows how to implement a simple waveflume in Basilisk, where a wave is propagated into the domain from the left boundary. To keep it simple, linear wave theory is used. The example may however easily be replace by more advanced theories by replacing the functions velox, veloy and wave_elev. The wave elevation at inflow is measured using the waveprobe functionality added with “waveprobes.h”.
Oystein Lande 2018
We start by including the building blocks:
#include <sys/stat.h>
#include "grid/quadtree.h"
#include "adapt_wavelet_leave_interface.h"
#include "utils.h"
#include "navier-stokes/centered.h"
#include "two-phase.h"
#include "navier-stokes/conserving.h"
#include "waveprobes.h"
#include "output_vtu_foreach.h"
#include "reduced.h"
#include "tension.h"
#include "view.h"For simplicity, all input parameters defining the simulation is defined in the struct below
struct SIM_properties {
double tmax; // simulation length
double dtmax;
double size[4]; // wave flume dimensions, [x_min, x_max, y_min, y_max]
double waterlevel; // elevation of stillwater level, relative to domain definition
double gravity; // m/s^2 (default 9.81)
double rho1; // density of water
double rho2; // density of air
int refine_init; // refinment level at t = 0
int adapt_refine_max; // maximum level of refinement
int adapt_refine_min; // minimum level of refinement
int adapt_utol;
double A; // wave linear amplitude
double w; // wave frequency (rad/s)
double k; // wave number
double waterdepth; // waterdepth
};
struct SIM_properties simdata = {
.tmax = 20.0,
.dtmax = 0.5,
.size = {-200.,200.,-90.0,30.0},
.waterlevel = 0.0,
.gravity =9.81,
.rho1 = 1025,
.rho2 = 1.225,
.refine_init = 9,
.adapt_refine_max = 9,
.adapt_refine_min = 4,
.adapt_utol = 1.0,
.A = 0.25,
.w = 1.0472,
.k = 0.11179,
.waterdepth = 90.
};
int vtucount = 0;Functions
We define some useful functions which will be called further down.
Sets the domain size and origin according to the specified values in simdata
void set_domain_size(){
double lx = simdata.size[1]-simdata.size[0];
double lz = simdata.size[3]-simdata.size[2];
fprintf(stderr,"data:%.2f\n",simdata.size[1]);
init_grid (1 << (simdata.refine_init));
if (lx >= lz){
size (lx);
origin (simdata.size[0], simdata.size[2]); // move origin
//mask (y > dp.size[5] ? top : none);
}
else {
size (lz);
origin (simdata.size[0], simdata.size[2]); // move origin
//mask (x > dp.size[1] ? right : none);
}
}maskes away parts of the domain to make it the right size
void mask_domain(){
double lx = simdata.size[1]-simdata.size[0];
double lz = simdata.size[3]-simdata.size[2];
if (lx >= lz){
mask (y > simdata.size[3] ? top : none);
}
else {
mask (x > simdata.size[1] ? right : none);
}
}fills the basin and sets kinematics to 0 in entire basin
void set_kinematics(){
// initialize basin with wave
scalar phi[];
foreach_vertex() {
phi[] = -y + simdata.waterlevel;
}
fractions (phi, f);
fprintf(stderr,"Initializing basin velocities... rest...\n");
foreach()
foreach_dimension()
u.x[] = 0.0;
boundary((scalar*){f,u});
}Nice to have print function
void mg_print (mgstats mg)
{
if (mg.i > 0 && mg.resa > 0.)
fprintf (stderr, "# %d %g %g %g\n", mg.i, mg.resb, mg.resa,
exp (log (mg.resb/mg.resa)/mg.i));
}horizontal particle velocity, linear wave theory for finite depth
double velox(double x,double y,double t){
return clamp(t/4.,0.,1.)*simdata.A*simdata.w*(cosh(simdata.k*(y+simdata.waterdepth))/sinh(simdata.k*simdata.waterdepth))*cos(simdata.k*x - simdata.w*t);
}Vertical particle velocity, linear wave theory for finite depth
double veloy(double x,double y,double t){
return clamp(t/4.,0.,1.)*simdata.A*simdata.w*(sinh(simdata.k*(y+simdata.waterdepth))/sinh(simdata.k*simdata.waterdepth))*sin(simdata.k*x - simdata.w*t);
}wave elevation, linear wave theory
double wave_elev(double x, double t){
return clamp(t/4.,0.,1.)*simdata.A*cos(simdata.k*x - simdata.w*t);
}Simple coarse function for calculating volume fraction
double wave_Vfrac(double x, double y, double t, double del){
double eta = wave_elev(x,t);
//cout << wwelev << endl;
if (eta < y - (del / 2.)) {
return 0.0;
}
else if (eta > y + (del / 2.)) {
return 1.0;
}
else {
// Calculate volume fraction for the given cell with size del and position y
return (eta - (y - (del / 2.))) / del;
}
}write unstructured vtu files
void save_vtu ( int nf, int j)
{
char name[80];
FILE * fp ;
nf > 0 ? sprintf(name, "RES_VTK/res_n%3.3d_%4.4d.vtu",pid(),j) : sprintf(name, "RES_VTK/res_%4.4d.vtu",j);
fp = fopen(name, "w"); output_vtu_bin_foreach ((scalar *) {f,p}, (vector *) {u}, N, fp, false); fclose (fp);
}Setting boundaries
// Left boundary (wave inflow)
u.n[left] = f[]*dirichlet(velox(x,y,t)) + (1.-f[])*neumann(0);
u.t[left] = f[]*dirichlet(veloy(x,y,t)) + (1.-f[])*neumann(0);
f[left] = wave_Vfrac(x,y,t,Delta);
// Top boundary
u.n[top] = neumann(0);
p[top] = dirichlet(u.y[]*abs(u.y[])*rho(f[])*0.5); // this is a neat little trick to avoid escalating back-circulating flows in the top boundary.
// bottom boundary
p[bottom] = neumann(-a.y[]*fm.y[]*rho(f[])); // Think this is already default, but just to be sure...Main loop
int main() {
TOLERANCE = 1E-8;
mkdir("./RES_VTK",0755); // make a directory to store the resulting VTU files
//Set domain properties
set_domain_size();
dtmax = simdata.dtmax;
rho1 = simdata.rho1, rho2 = simdata.rho2;
mu1 = 0., mu2 = 0.;
G.y = -simdata.gravity;
Z.y = 1.;
run();
}initialize basin at rest
event init (t = 0) {
// Mask domain to fit specified size
mask_domain();
// Set flume surface at rest
set_kinematics();
}Events
This is a useful function to dump some simulation data during runtime, but strictly not neccessary
#if 1
event logfile (i++) {
stats s = statsf (f);
scalar l[];
foreach()
l[] = (sqrt(sq(u.x[]) + sq(u.y[])));
norm n = normf (l);
fprintf (stderr, "time: %g i: %d dt: %g statsF: %g %g %g", t, i, dt, s.sum, s.min, s.max - 1.);
fprintf (stderr, ", Urms: %g Umax: %g, speed: %g cellcount: %ld\n", n.rms, n.max, perf.speed, grid->tn);
mg_print (mgp);
//mg_print (mgpf);
mg_print (mgu);
fflush (stderr);
}
#endifWaveprobes
This function uses the height function to calculate the surface elevation at a given point x (or x,y in 3D)
#if 1
event waveprobe (t+=0.02;t<=simdata.tmax) {
heights (f, h);
static FILE * fp0 = fopen("waveprobe0.dat", "w");
double ycoords[2] = {-10.,10.}; // define a vertical line of points
double yMax0 = wprobe(-199.9,ycoords,20);
double yMax1 = wave_elev(-199.9,t);
// update file
fprintf(fp0, "%g %g %g\n",t, yMax0,yMax1);
fflush(fp0);
}
#endifdump a vtu file (which can be viewed directly in paraview), for a given timeinterval
#if 1
event logfilevtu (t=0.0;t<=simdata.tmax;t+=0.04) {
save_vtu(0,vtucount);
vtucount += 1;
}
#endifmesh adaptation
Using a sligtly modified version of adapt_wavelet, which restricts the mesh to be at max level around the fluid interface
#if 1
event adapt(i++){
adapt_wavelet_leave_interface((scalar *){u},{f},(double[]){simdata.adapt_utol,simdata.adapt_utol}, simdata.adapt_refine_max, simdata.adapt_refine_min,1);
}
#endif#Results The simulation takes about a min on a resonable desktop computer.
