sandbox/farsoiya/viscous_droplet_breakup.c
Droplet breakage in turbulent flow
Role of viscous droplet breakup for the Farsoiya et. al. 2023.
//~ #define MIN_LEVEL 5
//~ #define LEVEL 10
int MAX_LEVEL = 8; //only for the compilation
//~ #define dR_refine (2.*L0/(1 << LEVEL))
#define F_ERR 1e-10
#include "grid/octree.h"
#include "navier-stokes/centered.h"
#include "two-phase.h"
#include "navier-stokes/conserving.h"
#include "tension.h"
#include "tag.h"
#include "view.h"
We monitor performance statistics and control the maximum runtime.
#include "navier-stokes/perfs.h"
#include "maxruntime.h"
#include <sys/stat.h>
Include density and viscosity ratios.
#define RHOR 1.
//#define MUR 0.004
Defined domain size is
#define WIDTH 120.0
The code takes the level of refinement as optional command-line argument (as well as an optional maximum runtime).
double MAXTIME = 500; //end time default
int FORCED = 1; //equals 0 if it is not forced, 1 otherwise
double amp_force = 0.1; //amplitude of the forcing
double SIGMA = 500.0; // surface tension coeff
double R0 = 8.0; //Radius of the bubble
double SC1 = 1.0;
double mur = 1.;
double We =1.;
double visp = 1.;
double gravity = 0;
double tst = 1;
double conc_liq1 = 0;
//double conc_gas1 = 1.0;
int main (int argc, char * argv[]) {
if (argc > 1)
{
= atoi(argv[1]);
MAX_LEVEL = atoi(argv[2]);
MAXTIME = atof(argv[3]);
SC1
= atoi(argv[4]); //equals 1 if forced, 0 otherwise
FORCED = atof(argv[5]); //amplitude of the forcing
amp_force //visp for Re = 38, visp=2, Re = 55, visp = 1 and Re = 77, visp = 0.5
= atof(argv[6]);
visp = atof(argv[7]);
We = atof(argv[8]);
mur = atof(argv[9]);
tst }
(WIDTH);
size foreach_dimension()
periodic (right);
Use reference density for fluid, and define gas by density ratio.
double epsilon = 10.;
= 1.0;
rho1 = rho1/RHOR;
rho2 = 2.*rho1*pow(epsilon,2./3)*pow(2.*R0,5./3)/We;
SIGMA = visp* 0.01 * sq(WIDTH/(2.0*pi))/(2.0)/2.0;
mu1 = mu1*mur; mu2
And the surface tension.
.sigma = SIGMA;
f
#if TREE
= 1 << (MAX_LEVEL-2);
N #else
= 1 << MAX_LEVEL;
N #endif
Set tolerance on flow divergence.
= 1e-4;
TOLERANCE = 20;
NITERMAX
struct stat st = {0};
char name[80];
sprintf (name, "dat");
char newname[80];
sprintf (name, "dat");
if (stat(name, &st) == -1)
{
(name, 0700);
mkdir}
sprintf (name, "Images");
if (stat(name, &st) == -1)
{
(name, 0700);
mkdir}
Preserve old stats restoring the simulation with a timestamp suffixed.
sprintf (name, "stats.dat");
if (stat(name, &st) == 0)
{
sprintf (newname, "stats-%ld.dat",time(0));
(name, newname);
rename}
sprintf (name, "perfs");
if (stat(name, &st) == 0)
{
sprintf (newname, "perfs-%ld",time(0));
(name, newname);
rename}
sprintf (name, "gas_transfer_stats.dat");
if (stat(name, &st) == 0)
{
sprintf (newname, "gas_transfer_stats-%ld.dat",time(0));
(name, newname);
rename}
sprintf (name, "bubbles.dat");
if (stat(name, &st) == 0)
{
sprintf (newname, "bubbles-%ld.dat",time(0));
(name, newname);
rename}
sprintf (name, "droplets.dat");
if (stat(name, &st) == 0)
{
sprintf (newname, "droplets-%ld.dat",time(0));
(name, newname);
rename}
run();
}
Initial conditions
The initial condition is “ABC” flow. This is a laminar base flow that is easy to implement in both Basilisk and a spectral code.
#include <dirent.h>
event init (i = 0) {
if (!restore (file = "restart") && !restore(file="dump"))
{
double waveno = WIDTH/(2.0*pi);
double amp = WIDTH/(2.0*pi);
foreach() {
[] = 1.;
fdouble xw = x/waveno;
double yw = y/waveno;
double zw = z/waveno;
.x[] = amp*( cos(yw) + sin(zw) );
u.y[] = amp*( sin(xw) + cos(zw) );
u.z[] = amp*( cos(xw) + sin(yw) );
u}
}
else if(restore (file = "dump")){
// do nothing
}
else{
refine(sq(2.0*R0)-sq(x-WIDTH/2.0) - sq(y-WIDTH/2.0) - sq(z-WIDTH/2.0)>0 && level < MAX_LEVEL );
fraction (f, sq(x-WIDTH/2.0) + sq(y-WIDTH/2.0) + sq(z-WIDTH/2.0) - sq(R0));
foreach(){
foreach_dimension(){
.x[] = f[]*u.x[];
u}
}
}
}
Include accelerations:
Linear forcing term
We compute the average velocity and add the corresponding linear forcing term.
if (FORCED)
{
;
coord ubarforeach_dimension() {
stats s = statsf(u.x);
.x = s.sum/s.volume;
ubar}
foreach_face()
.x[] += f[]*amp_force*((u.x[] + u.x[-1])/2. - ubar.x);
av
}
boundary((scalar *){av});
}
Outputs
We log the evolution of viscous dissipation, kinetic energy, and microscale Reynolds number.
event logfile (i++) {
//~ printf("\n%d\t%g\t%g",i,t,dt);
//~ fflush(stdout);
;
coord ubarforeach_dimension() {
stats s = statsf(u.x);
.x = s.sum/s.volume;
ubar}
double ke = 0., vd = 0., vol = 0.;
foreach(reduction(+:ke) reduction(+:vd) reduction(+:vol)) {
+= dv();
vol foreach_dimension() {
// mean fluctuating kinetic energy
+= dv()*sq(u.x[] - ubar.x);
ke // viscous dissipation
+= dv()*(sq(u.x[1] - u.x[-1]) +
vd (u.x[0,1] - u.x[0,-1]) +
sq(u.x[0,0,1] - u.x[0,0,-1]))/sq(2.*Delta);
sq}
}
/= 2.*vol;
ke *= mu1/vol;
vd static FILE * fd = fopen("stats.dat","w");
if (i == 0) {
//~ fprintf (ferr, "t dissipation energy Reynolds\n");
fprintf (fd, "t dissipation energy Reynolds\n");
}
//~ fprintf (ferr, "%g %g %g %g\n",
//~ t, vd, ke, 2./3.*ke/mu1*sqrt(15.*mu1/vd));
fprintf (fd, "%g %g %g %g\n",
, vd, ke, 2./3.*ke/mu1*sqrt(15.*mu1/vd));
tfflush (fd);
}
We use adaptivity.
#if TREE
event adapt (i++) {
double uemax = 0.2*normf(u.x).avg;
// double tr1emax = 0.2*normf(c1).avg;
double femax = 1e-2;
((scalar *){f, u},
adapt_wavelet (double[]){femax, uemax, uemax, uemax},MAX_LEVEL);
}
#endif
We output a full snapshot every time unit.
double endtimemark = 300;
event snapshot (t=0; t <=MAXTIME; t+=1)
{
char namedump[80];
sprintf(namedump,"./dat/dump_%g",t);
dump(file = "dump");
}
We also want to count the drops and bubbles in the flow.
int endflag = 0;
event countDropsBubble(t+=0.01)
{
scalar m1[]; //droplets
scalar m2[]; //bubbles
foreach(){
[] = f[] > 0.05; //i.e. set m true if f[] is close to unity (droplets)
m1[] = f[] < 0.99; //m true if f[] close to zero (bubbles)
m2}
int n1 = tag(m1);
int n2 = tag(m2);
Having counted the bubbles, now we find their size. This example is similar to the jet atomization problem. We are interested in the volumes and positions of each droplet/bubble.
double v1[n1]; //droplet
[n1]; //droplet
coord b1double v2[n2]; //bubble
[n2]; //bubble
coord b2double ar1[n1];
double ar2[n2];
We initialize:
for (int j=0; j<n1; j++)
{
[j] = ar1[j] = b1[j].x = b1[j].y = b1[j].z = 0.0;
v1}
for (int j=0; j<n2; j++)
{
[j] = ar2[j] = b2[j].x = b2[j].y = b2[j].z = 0.0;
v2}
We proceed with calculation.
foreach_leaf() //droplets
{
if (m1[] > 0) {
int j = m1[] - 1;
[j] += dv()*f[]; //increment the volume of the droplet
v1if (f[] > 1e-6 && f[] < 1. - 1e-6) {
= interface_normal (point, f), p;
coord n
double alpha = plane_alpha (f[], n);
[j] += pow(Delta, dimension - 1)*plane_area_center (n, alpha, &p);
ar1}
= {x,y,z};
coord p foreach_dimension()
[j].x += dv()*f[]*p.x;
b1}
}
foreach_leaf() //bubbles
{
if (m2[] > 0) {
int j = m2[] - 1;
[j] += dv()*(1.0-f[]);
v2if (f[] > 1e-6 && f[] < 1. - 1e-6) {
= interface_normal (point, f), p;
coord n
double alpha = plane_alpha (f[], n);
[j] += pow(Delta, dimension - 1)*plane_area_center (n, alpha, &p);
ar2}
= {x,y,z};
coord p foreach_dimension()
[j].x += dv()*(1.0-f[])*p.x;
b2}
}
Reduce for MPI.
#if _MPI
(MPI_IN_PLACE, v1, n1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce (MPI_IN_PLACE, ar1, n1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce
(MPI_IN_PLACE, b1, 3*n1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce (MPI_IN_PLACE, v2, n2, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce (MPI_IN_PLACE, ar2, n2, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce
(MPI_IN_PLACE, b2, 3*n2, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce #endif
Output the volume and position of each droplet to file.
static FILE * fdrop = fopen("droplets.dat","w");
static FILE * fbubb = fopen("bubbles.dat","w");
for (int j=0; j<n1; j++)
{
fprintf (fdrop, "%d %g %d %g %g %g %g %g\n", i, t,
, v1[j], b1[j].x/v1[j], b1[j].y/v1[j],b1[j].z/v1[j],ar1[j]);
j}
for (int j=0; j<n2; j++)
{
fprintf (fbubb, "%d %g %d %g %g %g %g %g\n", i, t,
, v2[j], b2[j].x/v2[j], b2[j].y/v2[j], b2[j].z/v2[j],ar2[j]);
j}
double temp;
// Simulation end criteria
if (n2 > 1){ //if more than one droplet exists
//sort droplet volume ascending
for (int k=0; k<n2; k++){
for (int j=0; j<(n2-k); j++){
if(v2[j] > v2[j+1]){
= *(v2 + j);
temp *(v2 + j) = *(v2 + j + 1);
*(v2 + j + 1) = temp;
}
}
}
double delta = L0/(pow(2,MAX_LEVEL));
// Thanks to Alienor who pointed out that looking the size of smallest droplet can result in judging ficticious breakup
// //hence we will now look for second largest droplet which should be more than 4 delta cube
if (endflag == 0 && v2[n2-2] > 4.*pow(delta,3)){ //if first breakup and smallest droplet has volume more than 4 times cell volume
= 1; //declare fist breakup
endflag = t; //not the time for first breakup
endtimemark dump(file = "breakup"); //save the breakup dump file
}
else if (endflag == 1){
if (t > (endtimemark + 30)){ // end simulation after 10tc
printf("End time after breakup");
dump(file = "end");
exit(0);
}
}
}
}
scalar omg[];
void vorticity3D (vector u, scalar omg) {
foreach() {
[] = 0.;
omgforeach_dimension()
[] += sq((u.y[1] - u.y[-1] -
omg.x[0,1] + u.x[0,-1])/(2*Delta));
u
[] = sqrt(omg[]);
omg}
boundary ((scalar*){omg});
}
event movie (t+=0.1)
{
char namedump[200];
sprintf(namedump,"Images/temp.ppm");
vorticity3D(u, omg);
view (fov = 44, camera = "iso", ty = .2,
= 1200, height = 1200, samples = 4);
width clear();
//box();
draw_vof("f");
squares ("omg", spread = 5, linear = true, map = jet, alpha = 1, n = {1, 0 , 0});
squares ("omg", spread = 5, linear = true, map = jet, alpha = 1, n = {0, 1 , 0});
squares ("omg", spread = 5, linear = true, map = jet, alpha = 1, n = {0, 0 , 1});
save (namedump);
sprintf(namedump,"convert Images/temp.ppm Images/snap-%g.png",t*100);
system(namedump);
}
End the simulation.
References
[farsoiya2023role] |
Palas Kumar Farsoiya, Zehua Liu, Andreas Daiss, Rodney O Fox, and Luc Deike. Role of viscosity in turbulent drop break-up. Journal of Fluid Mechanics, 972:A11, 2023. |