simulationCases/pinchOff.c
Viscoelastic Liquid Jet Pinch-Off Simulation
This file implements an axisymmetric simulation of the pinch-off dynamics of a viscoelastic liquid jet. The simulation uses a two-phase approach with log-conformation formulation for the viscoelastic stress tensor.
The model incorporates: - Axisymmetric Navier-Stokes equations - Log-conformation viscoelastic constitutive model - Two-phase interface with surface tension - Adaptive mesh refinement based on interface curvature and velocity gradients
File Information
- File: pinchOff.c
- Version: 0.2
- Author: Vatsal Sanjay
- Date: Oct 18, 2024
#include "axi.h"
// #include "grid/octree.h"
// #include "grid/quadtree.h"
#include "navier-stokes/centered.h"
#define VANILLA 126
#if VANILLA
#include "../src-local/log-conform-viscoelastic.h"
#define logFile "logAxi-vanilla.dat"29
#else
#if AXI
#include "../src-local/log-conform-viscoelastic-scalar-2D.h"
#define logFile "logAxi-scalar.dat"33
#else
#include "../src-local/log-conform-viscoelastic-scalar-3D.h"
#define logFile "log3D-scalar.dat"36
#endif
#endif
#define FILTERED // Smear density and viscosity jumps
#include "../src-local/two-phaseVE.h"
#include "navier-stokes/conserving.h"
#include "tension.h"
## Simulation Parameters
Configuration of time steps, error tolerances, and physical parameters.
Time between snapshots
#define tsnap (1e-2)5354
### Error Tolerances
- fErr: Error tolerance in volume fraction field (f1 VOF)
- KErr: Error tolerance in interface curvature calculation using height function
- VelErr: Error tolerances in velocity field Use 1e-2 for low Oh (Ohnesorge number) cases Use 1e-3 to 5e-3 for high Oh/moderate to high J cases
#define fErr (1e-3)64
#define KErr (1e-6)65
#define VelErr (1e-2)6667
### Geometry Parameters
Parameters defining the initial geometry of the liquid jet
#define epsilon (0.5)73
#define R2(x,y,z,e) (sqrt(sq(y) + sq(z)) + (e*sin(x/4.)))7475
## Boundary Conditions
Neumann boundary condition for velocity and Dirichlet for pressure at the top
u.n[top] = neumann(0.0);
p[top] = dirichlet(0.0);
## Global Variables
- MAXlevel: Maximum level of mesh refinement
- Oh: Ohnesorge number for the liquid phase (solvent)
- Oha: Ohnesorge number for the gas phase (air)
- De: Deborah number - ratio of relaxation time to flow time
- Ec: Elasto-capillary number - ratio of elastic to capillary forces
int MAXlevel;
double Oh, Oha, De, Ec, tmax;
char nameOut[80], dumpFile[80];
## Main Function
Initializes the simulation parameters and starts the simulation.
- Sets domain size
- Configures physical parameters (Oh, De, Ec)
- Initializes grid
- Sets up file storage
- Configures material properties
### Parameters - argc: Command line argument count - argv: Command line argument values
### Returns - Exit status code
int main(int argc, char const *argv[]) {
L0 = 2*pi;
// Values taken from the terminal
MAXlevel = 6;
tmax = 10;
Oh = 1e-2;
Oha = 1e-2 * Oh;
De = 1.0; // 1e-1;
Ec = 1.0; // 1e-2;
init_grid(1 << 4);
// Create a folder named intermediate where all the simulation snapshots are stored
char comm[80];
sprintf(comm, "mkdir -p intermediate");
system(comm);
// Name of the restart file. See writingFiles event
sprintf(dumpFile, "restart");
// Set material properties
rho1 = 1., rho2 = 1e-3;
mu1 = Oh, mu2 = Oha;
lambda1 = De, lambda2 = 0.;
G1 = Ec, G2 = 0.;
f.sigma = 1.0;
run();
}
## Initialization Event
Sets up the initial condition for the simulation.
- Attempts to restore from a restart file if available
- Otherwise initializes the interface using the geometric function
- Refines the mesh around the interface
### Parameters - t: Simulation time (starts at 0)
event init(t = 0) {
if (!restore(file = dumpFile)) {
refine(R2(x,y,z,epsilon) < (1+epsilon) && R2(x,y,z,epsilon) > (1-epsilon)
&& level < MAXlevel);
fraction(f, (1-R2(x,y,z,epsilon)));
}
}
## Adaptive Mesh Refinement
Dynamically adjusts the mesh resolution based on interface curvature and flow features.
- Calculates interface curvature
- Refines mesh based on error criteria for volume fraction, velocity, and curvature fields
### Parameters - i: Iteration number
event adapt(i++) {
scalar KAPPA[];
curvature(f, KAPPA);
adapt_wavelet((scalar *){f, u.x, u.y, KAPPA},
(double[]){fErr, VelErr, VelErr, KErr},
MAXlevel, 4);
}
## Snapshot Generation
Saves the state of the simulation at regular intervals.
- Creates a restart file for potential simulation recovery
- Generates a snapshot file with timestamped name
### Parameters - t: Simulation time (starts at 0, incremented by tsnap until tmax)
event writingFiles(t = 0; t += tsnap; t <= tmax) {
dump(file = dumpFile);
sprintf(nameOut, "intermediate/snapshot-%5.4f", t);
dump(file = nameOut);
}
## Simulation Termination
Outputs final information when the simulation ends.
- Prints the maximum refinement level and Ohnesorge number
### Parameters - t: Simulation time (at end)
event end(t = end) {
if (pid() == 0)
fprintf(ferr, "Level %d, Oh %2.1e\n", MAXlevel, Oh);
}
## Data Logging
Records simulation statistics at each iteration.
- Calculates total kinetic energy
- Identifies the minimum position of the interface along the y-axis
- Writes data to the log file
- Checks for simulation stability based on kinetic energy
### Parameters - i: Iteration number
### Notes - Terminates the simulation if kinetic energy is too high (blow-up) or too low - Creates a final restart file if the simulation is terminated early
event logWriting(i++) {
// Calculate kinetic energy
double ke = 0.;
foreach (reduction(+:ke)) {
ke += (2*pi*y)*(0.5*rho(f[])*(sq(u.x[]) + sq(u.y[])+ sq(u.z[])))*sq(Delta);
}
static FILE * fp;
if (pid() == 0) {
const char* mode = (i == 0) ? "w" : "a";
fp = fopen(logFile, mode);
if (fp == NULL) {
fprintf(ferr, "Error opening log file\n");
return 1;
}
// Find minimum position of interface along y-axis
scalar pos[];
position(f, pos, {0,1,0});
double ymin = statsf(pos).min;
// Write header for first iteration
if (i == 0) {
fprintf(ferr, "Level %d, Oh %2.1e, Oha %2.1e, De %2.1e, Ec %2.1e\n",
MAXlevel, Oh, Oha, De, Ec);
fprintf(ferr, "i dt t ke ymin\n");
fprintf(fp, "Level %d, Oh %2.1e, Oha %2.1e, De %2.1e, Ec %2.1e\n",
MAXlevel, Oh, Oha, De, Ec);
fprintf(fp, "i dt t ke ymin\n");
}
// Write data row
fprintf(fp, "%d %g %g %g %g\n", i, dt, t, ke, ymin);
fprintf(ferr, "%d %g %g %g %g\n", i, dt, t, ke, ymin);
fflush(fp);
fclose(fp);
}
// Check for negative kinetic energy (should never happen)
assert(ke > -1e-10);
// Check for simulation stability after a few iterations
if (i > 1e1 && pid() == 0) {
if (ke > 1e2 || ke < 1e-8) {
const char* message = (ke > 1e2) ?
"The kinetic energy blew up. Stopping simulation\n" :
"kinetic energy too small now! Stopping!\n";
fprintf(ferr, "%s", message);
fp = fopen("log", "a");
fprintf(fp, "%s", message);
fflush(fp);
fclose(fp);
dump(file = dumpFile);
return 1;
}
}
}