src/examples/bubble.c
Bubble rising in a large tank
We wish to study the behaviour of a single bubble rising “in a large tank” i.e. far from any boundaries.
We use the centered Navier–Stokes solver and log performance statistics.
#include "navierstokes/centered.h"
#include "navierstokes/perfs.h"
We have two phases e.g. air and water. For large viscosity and density ratios, the harmonic mean for the viscosity tends to work better than the default arithmetic mean. We “overload” the default by defining the mu() macro before including the code for two phases.
#define mu(f) (1./(clamp(f,0,1)*(1./mu1  1./mu2) + 1./mu2))
#include "twophase.h"
We also need surface tension, and in 3D only we will use the ${\lambda}_{2}$ criterion of Jeong and Hussain, 1995 to display the vortices using Basilisk View.
#include "tension.h"
#if dimension == 3
# include "lambda2.h"
#endif
#include "view.h"
We can control the maximum runtime.
#include "maxruntime.h"
The density ratio is 1000 and the dynamic viscosity ratio 100.
#define RHOR 1000.
#define MUR 100.
We try to replicate the results of CanoLozano et al, 2016 (obtained with Gerris). Aside from the ratios above, there are two independent parameters which can be described by the Galilei number $$G{a}^{2}=\frac{g{D}^{3}}{{\nu}^{2}}$$ with $g$ the acceleration of gravity, $D$ the diameter of the bubble and $\nu $ the kinematic viscosity of the outer fluid; and the Bond/Eötvös number $$Bo=\frac{\rho g{D}^{2}}{\sigma}$$ with $\rho $ the density of the outer fluid and $\sigma $ the surface tension coefficient.
We consider two bubbles studied by CanoLozano et al, 2016.
#if BUBBLE19
// Bubble 19 of CanoLozano et al, P.R.Fluids, 2016
# define Ga 100.8
# define Bo 4.
# define MAXTIME 82
#else
// Bubble 26 of CanoLozano et al, P.R.Fluids, 2016
# define Ga 100.25
# define Bo 10.
# define MAXTIME 110
#endif
We choose as length unit the diameter of the bubble. The domain is ${120}^{3}$. Z0 is the initial position of the bubble relative to the bottom wall. The acceleration of gravity is set to unity, which gives a characteristic rise velocity also of order unity, which gives a maximum time for the simulation comparable to the domain size.
#define WIDTH 120.0
#define Z0 3.5
int LEVEL = 12;
The main function can take two optional parameters: the maximum level of adaptive refinement (as well as an optional maximum runtime).
int main (int argc, char * argv[]) {
maxruntime (&argc, argv);
if (argc > 1)
LEVEL = atoi (argv[1]);
We set the domain geometry and initial refinement.
size (WIDTH);
origin (L0/2, 0, L0/2);
init_grid (128);
We set the physical parameters: densities, viscosities and surface tension.
rho1 = 1.;
rho2 = 1./RHOR;
mu1 = 1./Ga;
mu2 = 1./(MUR*Ga);
f.σ = 1./Bo;
We reduce the tolerance on the divergence of the flow. This is important to minimise mass conservation errors for these simulations which are very long.
TOLERANCE = 1e4;
run();
}
For the initial conditions, we first try to restore the simulation from a previous “restart”, if this fails we refine the mesh locally to the maximum level, in a sphere of diameter 1.5 around the bubble. We then initialise the volume fraction for a bubble initially at (0,Z0,0) of diameter unity.
event init (t = 0) {
if (!restore (file = "restart")) {
refine (sq(x) + sq(y  Z0) + sq(z)  sq(0.75) < 0 && level < LEVEL);
fraction (f, sq(x) + sq(y  Z0) + sq(z)  sq(.5));
}
}
We add the acceleration of gravity (unity) in the downward (y) direction.
event acceleration (i++) {
face vector av = a;
foreach_face(y)
av.y[] = 1.;
}
We adapt the mesh by controlling the error on the volume fraction and velocity field.
event adapt (i++) {
double uemax = 1e2;
adapt_wavelet ({f,u}, (double[]){0.01,uemax,uemax,uemax}, LEVEL, 5);
}
Outputs
Every ten timesteps, we output the time, volume, position, and velocity of the bubble.
event logfile (i += 10) {
double xb = 0., yb = 0., zb = 0., sb = 0.;
double vbx = 0., vby = 0., vbz = 0.;
foreach(reduction(+:xb) reduction(+:yb) reduction(+:zb)
reduction(+:vbx) reduction(+:vby) reduction(+:vbz)
reduction(+:sb)) {
double dv = (1.  f[])*dv();
xb += x*dv;
yb += y*dv;
zb += z*dv;
vbx += u.x[]*dv;
vby += u.y[]*dv;
vbz += u.z[]*dv;
sb += dv;
}
fprintf (ferr,
"%.8f %.8f %.8f %.8f %.8f %.8f %.8f %.8f\n",
t, sb,
xb/sb, yb/sb, zb/sb,
vbx/sb, vby/sb, vbz/sb);
fflush (ferr);
}
Every time unit, we output a full snapshot of the simulation, to be able to restart and for visualisation. In three dimensions, we compute the value of the ${\lambda}_{2}$ field which will be used for visualisation of vortices, as well as the streamwise vorticity ${\omega}_{y}={\partial}_{x}{u}_{z}{\partial}_{z}{u}_{x}$.
event snapshot (t = 1; t <= MAXTIME; t++)
{
scalar l2[], omegay[];
#if dimension == 3
lambda2 (u, l2);
foreach()
omegay[] = (u.z[1]  u.z[1]  u.x[0,0,1] + u.x[0,0,1])/(2.*Δ);
boundary ({omegay});
#endif
char name[80];
sprintf (name, "dump%03d", (int) t);
dump (file = name);
}
We make a movie of the trailing wake and bubble shape.
event movie (t = 21; t <= MAXTIME; t += 0.25)
{
#if BUBBLE19
view (fov = 5.0278, quat = {0.132839,0.513023,0.0748175,0.844727},
tx = 0.00149469, ty = 0.355489, width = 300, height = 800);
For $50<t<60$ we do a tracking shot from the initial camera position (above) to a new position, following and zooming on the bubble.
travelling (50, 60, fov = 2.07254, tx = 0.00524944, ty = 0.513744);
For $60<t<82$ we just follow the bubble.
travelling (60, 82, tx = 0.00898601, ty = 0.703841);
#else // BUBBLE26
The viewing parameters are different for bubble 26.
view (fov = 5.0278, quat = {0.0487657,0.654023,0.0506598,0.7532},
tx = 0.00353642, ty = 0.302285, width = 300, height = 800);
travelling (50, 60, fov = 2.07254, tx = 0.0022909, ty = 0.402237);
travelling (60, 110, tx = 0.00644264, ty = 0.741016);
#endif
We use a different color for the bubble interface and for the isosurface.
clear();
draw_vof ("f", fc = {0.13,0.47,0.77});
#if dimension == 3
scalar l2[];
lambda2 (u, l2);
isosurface ("l2", 0.0002);
#endif
save ("bubble.mp4");
}
Parallel runs
These simulations are expensive (in 3D), mostly because of the timestep restriction due to surface tension and of the long evolution required to reach an established quasistationary regime. The results presented below were obtained using 96 cores on occigen at CINES in 12 hours for each bubble. To run the simulation in 3D use
local% qcc source grid=octree D_MPI=1 bubble.c
local% scp _bubble.c occigen.cines.fr:
and see the isotropic turbulence example for details.
Results
The evolution of the bubble Reynols number for bubble 19 and 26 can be compared to Figures 20.c and 20.f respectively of CanoLozano et al, 2016. The results are close, although the transition to established regime seems to be faster in Basilisk. The final Reynolds numbers are comparable (within a few percent) but the amplitude of oscillation seems to be somewhat larger in Basilisk. Note that the Basilisk results presented here use a different resolution for the bubble and its wake than the Gerris results of CanoLozano et al, 2016.
The trajectories of the center of gravity of the bubble reveal the two regimes.
On the movies below, the bubble interface is in dark blue and the white surface is the ${\lambda}_{2}$ isosurface showing vortical structures.

