simulationCases/JumpingBubbles.c
Jumping Bubbles Simulation
A computational fluid dynamics simulation of two bubbles coalescing and jumping off a substrate using Basilisk C. This simulation employs an adaptive octree grid for spatial discretization and models two-phase flow with surface tension effects.
Overview
The simulation captures the complex physics of bubble coalescence and subsequent jumping behavior. It reads bubble geometry from an STL file and tracks the gas-liquid interface evolution using the Volume-of-Fluid (VOF) method, providing high-fidelity results for studying bubble dynamics on surfaces.
Changelog
Version 1.5 (January 5, 2025)
- Extended support for arbitrary contact angle implementation
- Enhanced contact angle boundary condition handling
Author Information
- Author: Vatsal
- Version: 1.5
- Date: January 5, 2025
#include "grid/octree.h"
#include "navier-stokes/centered.h"
//#define FILTERED
#include "contact-fixed.h"
#include "two-phase.h"
#include "navier-stokes/conserving.h"
#include "tension.h"
#if !_MPI
#include "distance.h"
#endif
#include "reduced.h"
Configuration Parameters
Grid Resolution Control
MINlevel
: Minimum refinement level for the coarsest grid resolutionMAXlevel
: Maximum refinement level controlling the finest grid resolution
Time Control Parameters
tsnap
: Time interval between solution snapshots (default: 1e-2)tsnap2
: Time interval for log file outputs (default: 1e-4)
Error Tolerances
fErr
: Error tolerance for VOF field adaptation (default: 1e-3)KErr
: Error tolerance for curvature field adaptation (default: 1e-4)VelErr
: Error tolerance for velocity field adaptation (default: 1e-4)
Physical Properties
Mu21
: Viscosity ratio of gas phase to liquid phase (default: 1e-3)Rho21
: Density ratio of gas phase to liquid phase (default: 1e-3)Ldomain
: Characteristic length of the simulation domain (default: 4)
#define MINlevel 264
#define tsnap (1e-2)65
#define tsnap2 (1e-4)66
#define fErr (1e-3)67
#define KErr (1e-4)68
#define VelErr (1e-4)69
#define hErr (1e-3)70
#define Mu21 (1.00e-3)71
#define Rho21 (1.00e-3)72
#define Ldomain 47374
Boundary Conditions
The simulation implements specific boundary conditions at the substrate (bottom boundary): - Tangential velocity component: No-slip condition (u.t = 0) - Radial velocity component: No-penetration condition (u.r = 0) - Face velocities: Enforced to ensure mass conservation - Contact angle: Specified through the contact angle boundary condition
.t[bottom] = dirichlet(0.);
u.r[bottom] = dirichlet(0.);
u.t[bottom] = dirichlet(0.);
uf.r[bottom] = dirichlet(0.); uf
Contact Angle Implementation
The contact angle is imposed through a height function vector field that specifies the interface orientation at the substrate boundary.
double theta0, patchR;
[];
vector h.t[bottom] = contact_angle(theta0 * pi / 180.);
h.r[bottom] = contact_angle(theta0 * pi / 180.); h
Global Variables
Simulation Parameters
tmax
: Maximum simulation timeOh
: Ohnesorge number (η_l/√(ρ_l·γ·R_equiv))MAXlevel
: Maximum refinement leveltheta0
: Contact angle in degreespatchR
: Normalized contact patch radius (R_cont/R_equiv)Bo
: Bond number (ρ_l·g·R_equiv²/γ)
double tmax, Oh, Bo;
int MAXlevel;
char nameOut[80], dumpFile[80];
Main Function
Initializes the simulation parameters and computational domain. Sets up the two-phase flow properties including density and viscosity ratios, surface tension, and gravitational effects.
Key Parameters Set:
Oh
: Ohnesorge number controlling viscous effectsMAXlevel
: Maximum grid refinement leveltheta0
: Contact angle at the substratepatchR
: Size of the contact patchBo
: Bond number for gravitational effects
int main() {
#if !_MPI
= 1e-2;
tmax #else
= 2e0;
tmax #endif
= 0.0066;
Oh = 8;
MAXlevel = 15;
theta0 = 0.184;
patchR = 0.016;
Bo
(1 << MINlevel);
init_grid= Ldomain;
L0 (ferr, "tmax = %g. Oh = %g\n", tmax, Oh);
fprintf
= 1.0;
rho1 = Oh;
mu1 = Rho21;
rho2 = Mu21 * Oh;
mu2
.height = h;
f.sigma = 1.0;
f
.y = -Bo;
G
char comm[80];
(comm, "mkdir -p intermediate");
sprintf(comm);
system
(dumpFile, "restartFile");
sprintf
();
run}
Initialization Event
Sets up the initial condition for the simulation. This event attempts to restore from a previous dump file if available. If no dump file exists, it reads the bubble geometry from an STL file and constructs the initial interface.
Process:
- First attempts to restore from
restartFile
- If restoration fails, reads
InitialCondition.stl
- Computes distance function from STL geometry
- Constructs VOF field using the distance function
- Performs initial mesh adaptation
- Saves initial condition for verification
Notes:
- MPI version only supports restoration from dump files
- Non-MPI version can initialize from STL files using the distance function
(t = 0) {
event init#if _MPI
if (!restore(file = dumpFile)) {
(ferr, "Cannot restored from a dump file!\n");
fprintf}
#else
if (!restore(file = dumpFile)) {
char filename[60];
(filename, "InitialCondition.stl");
sprintfFILE *fp = fopen(filename, "r");
if (fp == NULL) {
(ferr, "There is no file named %s\n", filename);
fprintfreturn 1;
}
*p = input_stl(fp);
coord (fp);
fclose, max;
coord min
(p, &min, &max);
bounding_box(ferr, "xmin %g xmax %g\nymin %g ymax %g\nzmin %g zmax %g\n",
fprintf.x, max.x, min.y, max.y, min.z, max.z);
min(ferr, "x0 = %g, y0 = %g, z0 = %g\n",
fprintf0., -1.0, (min.z + max.z) / 2.);
(0., -1.0 - 0.025, (min.z + max.z) / 2.);
origin
[];
scalar d(d, p);
distancewhile (adapt_wavelet((scalar *){f, d},
(double[]){1e-6, 1e-6 * L0},
).nf);
MAXlevel
[];
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.;
d}
(phi, f);
fractions
() {
foreach() {
foreach_dimension.x[] = 0.0;
u}
}
(file = "dumpInit");
dump(ferr, "Done with initial condition!\n");
fprintf}
#endif
}
Adaptive Mesh Refinement Event
Performs adaptive mesh refinement at each timestep based on error estimates in various fields. This ensures computational efficiency while maintaining accuracy in regions of high gradients.
Refined Fields:
- Volume fraction field (f)
- Velocity components (u.x, u.y, u.z)
- Height function components (h.x, h.y, h.z)
Error Tolerances:
- Interface tracking: fErr
- Velocity field: VelErr
- Height function: hErr
(i++) {
event adapt((scalar *){f, u.x, u.y, u.z, h.x, h.y, h.z},
adapt_wavelet(double[]){fErr, VelErr, VelErr, VelErr,
, hErr, hErr},
hErr, MINlevel);
MAXlevel}
File Output Event
Writes simulation snapshots at regular intervals for post-processing and analysis. Creates both a restart file and timestamped snapshots.
Output Files:
restartFile
: Continuously updated for simulation restart capabilityintermediate/snapshot-*.dump
: Timestamped snapshots for analysis
Timing:
- Triggered at t = 0 and every tsnap interval thereafter
- Continues until tmax + tsnap to ensure final state capture
(t = 0; t += tsnap; t <= tmax + tsnap) {
event writingFiles(file = dumpFile);
dumpchar nameOut[80];
(nameOut, "intermediate/snapshot-%5.4f", t);
sprintf(file = nameOut);
dump}
Diagnostic Logging Event
Computes and logs diagnostic quantities at fine temporal intervals for monitoring simulation progress and analyzing bubble dynamics.
Logged Quantities:
- Iteration number (i)
- Timestep size (dt)
- Simulation time (t)
- Kinetic energy in the gas phase (ke)
Implementation Details:
- Kinetic energy computed only in gas phase using (1-f) weighting
- Volume integral performed using cube(Delta) for cell volumes
- Output written to both stderr and log file for monitoring
(t = 0; t += tsnap2; t <= tmax + tsnap) {
event logWriting
double ke = 0.;
(reduction(+:ke)) {
foreach+= 0.5 * (sq(u.x[]) + sq(u.y[]) + sq(u.z[])) *
ke (1. - f[], 0., 1.) * cube(Delta);
clamp}
if (pid() == 0) {
static FILE *fp;
if (i == 0) {
(ferr, "i dt t ke\n");
fprintf= fopen("log", "w");
fp (fp, "i dt t ke\n");
fprintf(fp, "%d %g %g %g\n", i, dt, t, ke);
fprintf(fp);
fclose} else {
= fopen("log", "a");
fp (fp, "%d %g %g %g\n", i, dt, t, ke);
fprintf(fp);
fclose}
(ferr, "%d %g %g %g\n", i, dt, t, ke);
fprintf}
}