sandbox/huet/cases/lagrangian_caps/contraction20.c
Flow of a capsule through a 20:1 channel contraction at Re = 2000
Definition of the non-dimensional numbers and solver parameters
Geometry defitions
#define RADIUS 1.
#define HEIGHT (5*RADIUS)
#define CURVATURE_RADIUS (RADIUS)
#define DEPTH (2*HEIGHT)
#define L0 (40*HEIGHT)
Fluid properties
#define U_AVG 1.
#define RHO 1.
#define REc 2000.
#define MU (RHO*U_AVG*2*HEIGHT/REc)
#define TAU_ADVECTIVE (40*HEIGHT/U_AVG)
#define STOKES false
Capsule properties
#define NCAPS 1
#define CAPILLARY (0.015)
#define E_S (MU*U_AVG/CAPILLARY)
#define ND_EB 0.005
#define E_B (ND_EB*E_S*sq(RADIUS))
#define INITIAL_POSITION_X (-2*HEIGHT)
#define INITIAL_POSITION_Y (2*HEIGHT)
#define INITIAL_POSITION_Z (0.)
Solver properties
#define T_END (TAU_ADVECTIVE + 100.)
#define DT_MAX (1.e-2)
#define U_TOL (0.05*U_AVG)
#define LAGLEVEL 4
#define MINLEVEL 5
#define MAXLEVEL 11
#define MY_TOLERANCE (1.e-3*U_AVG)
#define JACOBI 1
#define OUTPUT_FREQ ( t += .5 )
#define DUMP_OUTPUT_FREQ ( t += 5. )
#define PARAVIEW_CAPSULE 1
#define PARAVIEW_FLOW_FIELD 0
#define RESTART_CASE 0
#define RESTORE_FRAME -1
Some additional definitions are required for the capsule-free initial simulation
#ifndef INITIAL_FLOW_FIELD
#define INITIAL_FLOW_FIELD 0
#endif
#if INITIAL_FLOW_FIELD
#undef T_END
#define T_END (TAU_ADVECTIVE)
#undef DT_MAX
#define DT_MAX (T_END/100.)
#undef OUTPUT_FREQ
#define OUTPUT_FREQ (i+=10)
#undef NCAPS
#define NCAPS 0
#endif
#define STR(s) #s
#define STRING(s) STR(s)
#include "grid/octree.h"
//#include "lagrangian_caps/adapt2.h"
#include "embed.h"
#include "navier-stokes/centered.h"
#include "lagrangian_caps/capsule-ft.h"
#include "lagrangian_caps/neo-hookean-ft.h"
#include "lagrangian_caps/bending-ft.h"
#include "lagrangian_caps/common-shapes-ft.h"
#include "lagrangian_caps/view-ft.h"
Simulation setup
Because embedded boundaries are used, we have to multiply the density, viscosity and 1/density fields by the fluid volume or face fraction.
We also declare file pointers to output performance and flow convergence data.
scalar rhov[];
face vector muv[];
face vector alphav[];
FILE* fperf = NULL;
FILE* iff_stats = NULL;
FILE* foutput = NULL;
int nb_pic;
int main(int argc, char* argv[]) {
origin(-.5*L0 + 1.e-11, -.5*L0 + 1.e-11, -.5*L0 + 1.e-11);
N = 1 << MINLEVEL;
init_grid(N);
stokes = STOKES;
DT = DT_MAX;
TOLERANCE = MY_TOLERANCE;
TOLERANCE_MU = MY_TOLERANCE;
rho = rhov;
mu = muv;
alpha = alphav;
run();
}
Boundary conditions
u.n[left] = dirichlet(U_AVG/sq(20));
u.t[left] = dirichlet(0.);
u.r[left] = dirichlet(0.);
u.n[right] = neumann(0.);
u.t[right] = dirichlet(0.);
u.r[right] = dirichlet(0.);
u.n[top] = dirichlet(0.);
u.n[bottom] = dirichlet(0.);
u.t[top] = dirichlet(0.);
u.t[bottom] = dirichlet(0.);
u.r[top] = dirichlet(0.);
u.r[bottom] = dirichlet(0.);
u.n[front] = dirichlet(0.);
u.n[back] = dirichlet(0.);
u.t[front] = dirichlet(0.);
u.t[back] = dirichlet(0.);
u.r[front] = dirichlet(0.);
u.r[back] = dirichlet(0.);
u.n[embed] = dirichlet(0.);
u.t[embed] = dirichlet(0.);
u.r[embed] = dirichlet(0.);
p[left] = neumann(0.);
pf[left] = neumann(0.);
p[right] = dirichlet(0.);
pf[right] = dirichlet(0.);
#define large_channel (-x)
#define small_channel (intersection(HEIGHT - fabs(z), HEIGHT - fabs(y)))
#define xc1 (CURVATURE_RADIUS)
#define yc1 (HEIGHT + CURVATURE_RADIUS)
#define zc1 (HEIGHT + CURVATURE_RADIUS)
#define horizontal_curvatures (intersection(intersection(sq(x - xc1) + sq(fabs(y) - yc1) - sq(CURVATURE_RADIUS), xc1 - x), yc1 - fabs(y)))
#define vertical_curvatures (intersection(intersection(sq(x - xc1) + sq(fabs(z) - zc1) - sq(CURVATURE_RADIUS), xc1 - x), zc1 - fabs(z)))
#define curvatures (intersection(horizontal_curvatures, vertical_curvatures))
#define rounded_edge ((fabs(y) > HEIGHT || fabs(z) > HEIGHT) && \
fabs(y) < HEIGHT + CURVATURE_RADIUS && fabs(z) < HEIGHT + CURVATURE_RADIUS && \
x > 0 && x < CURVATURE_RADIUS)
scalar mcs[];
int last_introduced_caps;
event init (i = 0) {
fperf = fopen("perf.csv", "w");
fprintf(fperf, "total_nb_cells, nb_iter_viscous, resb_viscous, resa_viscous, nb_relax_viscous, nb_iter_pressure, resb_pressure, resa_pressure, nb_relax_pressure\n");
for (scalar s in {u})
s.third = true;
#if INITIAL_FLOW_FIELD
iff_stats = fopen("iff_stats.csv", "w");
fprintf(iff_stats, "time iteration number_of_cells avg_velocity_change max_velocity_change max_x_velocity outlet_flow_rate avg_output_vel\n");
astats ss;
int ic = 0;
do {
fprintf(stderr, "Initial mesh refinement: iteration %d\n", ic);
ic++;
solid (cs, fs, union(union(small_channel, large_channel), curvatures));
foreach() mcs[] = cs[];
tag_ibm_stencils();
// foreach() {
// if (rounded_edge) stencils[] = noise();
// }
ss = adapt_wavelet ((scalar*){mcs, stencils}, (double[]){1.e-30, 1.e-30}, MAXLEVEL, MINLEVEL);
} while ((ss.nf || ss.nc) && ic < 100);
#else
char dump_name[64];
#if RESTART_CASE
sprintf(dump_name, "flow_%s.dump", STRING(RESTORE_FRAME));
restore(dump_name);
sprintf(dump_name, "caps_%s.dump", STRING(RESTORE_FRAME));
restore_capsules(dump_name);
last_introduced_caps = NCAPS - 1;
foutput = fopen("output.csv", "a");
#else
foutput = fopen("output.csv", "w");
sprintf(dump_name, "initial_flow_field_re%s_lvl%s.dump",
STRING(REw), STRING(MAXLEVEL));
restore(dump_name);
activate_spherical_capsule(&CAPS(0), level = LAGLEVEL,
radius = RADIUS);
last_introduced_caps = 0;
for(int k=0; k<CAPS(0).nln; k++) {
CAPS(0).nodes[k].pos.x += INITIAL_POSITION_X;
CAPS(0).nodes[k].pos.y += INITIAL_POSITION_Y;
CAPS(0).nodes[k].pos.z += INITIAL_POSITION_Z;
}
generate_lag_stencils(no_warning = true);
comp_centroid(&CAPS(0));
#endif
With the membrane properly created to an initially strain-free spherical shape, we now proceed to refining the Eulerian mesh around the membrane.
astats ss;
int ic = 0;
do {
fprintf(stderr, "Initial mesh refinement: iteration %d\n", ic);
ic++;
solid (cs, fs, union(union(small_channel, large_channel),
curvatures));
foreach() mcs[] = cs[];
tag_ibm_stencils();
// foreach() if (rounded_edge) stencils[] = noise();
ss = adapt_wavelet ((scalar*){mcs, stencils}, (double[]){1.e-30, 1.e-30}, MAXLEVEL, MINLEVEL);
generate_lag_stencils(no_warning = true);
} while ((ss.nf || ss.nc) && ic < 100);
We also erase the content of the files where the membrane position and triangle areas will be stored, and store the initial configuration.
if (pid() == 0) {
for(int k=0; k<NCAPS; k++) {
char fposname[64];
char ftriname[64];
sprintf(fposname, "mb_%d_pos.csv", k);
sprintf(ftriname, "mb_%d_tri.csv", k);
#if !RESTART_CASE
FILE* file = fopen(fposname, "w");
fclose(file);
file = fopen(ftriname, "w");
fclose(file);
#endif
if (CAPS(k).isactive) {
dump_plain_nodes_pos(&CAPS(k), fposname);
dump_plain_triangles(&CAPS(k), ftriname);
}
}
}
#if PARAVIEW_CAPSULE
pv_output_ascii();
#endif
#if PARAVIEW_FLOW_FIELD
scalar * list = {p};
vector * vlist = {u};
save_data(list, vlist, t);
#endif
#endif
nb_pic = RESTORE_FRAME + 1;
}
Updating properties and adapting the mesh
We ensure that the following density, viscosity and 1/density fields are multiplied by the face or volume fractions.
event properties (i++) {
foreach_face() {
muv.x[] = MU*fm.x[];
alpha.x[] = 1./RHO*fm.x[];
}
foreach() rhov[] = RHO*cm[];
}
Throughout the simulation, the mesh is adapted according to the velocity field, and a maximum level is enforced around the channel walls and around the membrane (if applicable).
event adapt (i++) {
tag_ibm_stencils();
foreach() if (rounded_edge) stencils[] = noise();
adapt_wavelet ((scalar*) {mcs, stencils, u}, (double[]) {1.e-30, 1.e-30,
U_TOL, U_TOL, U_TOL}, MAXLEVEL, MINLEVEL);
#if NCAPS>0
generate_lag_stencils();
#endif
}
vector prev_u[];
#if INITIAL_FLOW_FIELD
event output (i ++) {
#else
event output (OUTPUT_FREQ) {
#endif
#if INITIAL_FLOW_FIELD
double max_deltau = -HUGE;
double avg_deltau = 0;
double flow_rate = 0.;
int nbc = 0;
foreach(reduction(max:max_deltau)
reduction(+:avg_deltau) reduction(+:nbc) reduction(+:flow_rate)) {
if (cm[] > 1.e-10) {
nbc++;
double nu = sqrt(sq(u.x[] - prev_u.x[]) + sq(u.y[] - prev_u.y[])
+ sq(u.z[] - prev_u.z[]));
avg_deltau += nu;
if (nu > max_deltau) max_deltau = nu;
foreach_dimension() prev_u.x[] = u.x[];
/* We also output the flow rate at the boundary of the domain */
if (fabs(x - L0/2.) < Delta/2.)
flow_rate += u.x[]*sq(Delta)*fs.x[];
}
}
if (nbc > 0) avg_deltau /= nbc;
double avg_output_vel = flow_rate/sq(2*HEIGHT);
fprintf(iff_stats, "%g %d %d %g %g %g %g %g\n", t, i, nbc, avg_deltau,
max_deltau, normf(u.x).max, flow_rate, avg_output_vel);
fflush(iff_stats);
#else
for(int k=0; k<NCAPS; k++) {
char fposname[64];
char ftriname[64];
sprintf(fposname, "mb_%d_pos.csv", k);
sprintf(ftriname, "mb_%d_tri.csv", k);
dump_plain_nodes_pos(&CAPS(k), fposname);
dump_plain_triangles(&CAPS(k), ftriname);
}
#if PARAVIEW_CAPSULE
pv_output_ascii();
#endif
#if PARAVIEW_FLOW_FIELD
scalar * list = {p};
vector * vlist = {u};
save_data(list, vlist, t);
#endif
#endif
Some figures about perfromance are always insightful:
fprintf(fperf, "%ld %d %g %g %d %d %g %g %d\n", grid->tn, mgu.i, mgu.resb,
mgu.resa, mgu.nrelax, mgp.i, mgp.resb, mgp.resa, mgp.nrelax);
fflush(fperf);
}
Monitor progress
The capsule and the flow field are saved every 10*OUTPUT_FREQ
iterations.
#if NCAPS > 0
event save (DUMP_OUTPUT_FREQ) {
char fname[62];
sprintf(fname, "flow_%d.dump", nb_pic);
dump(file = fname);
sprintf(fname, "caps_%d.dump", nb_pic);
dump_capsules(fname);
}
#endif
We generate pictures featuring the velocity norm and the capsule, as well as the z-vorticity field.
event pictures (OUTPUT_FREQ) {
scalar un[];
foreach() un[] = norm(u);
char name[64];
view(fov = 18.9, bg = {1,1,1},
// tx = -1./8, ty = 1./8,
width = 2400, height = 2400);
clear();
cells(n = {0., 0., 1.});
squares("un", n = {0,0,1}, map = cool_warm, min = 0., max = 2*U_AVG, linear = true);
#if NCAPS > 0
draw_lags(lw = .5, edges = true, facets = true);
#endif
#if INITIAL_FLOW_FIELD
sprintf(name, "iff_un_%d.png", nb_pic);
#else
sprintf(name, "un_%d.png", nb_pic);
#endif
save(name);
view(fov = 18.9*.25, bg = {1,1,1},
tx = -.04,
width = 2400, height = 2400);
clear();
cells(n = {0., 0., 1.});
squares("un", n = {0,0,1}, map = cool_warm, min = 0., max = 2*U_AVG, linear = true);
#if NCAPS > 0
draw_lags(lw = .5, edges = true, facets = true);
#endif
#if INITIAL_FLOW_FIELD
sprintf(name, "iff_un_zoom_%d.png", nb_pic);
#else
sprintf(name, "un_zoom_%d.png", nb_pic);
#endif
save(name);
view(fov = 18.9*.25, bg = {1,1,1},
tx = -.04,
width = 2400, height = 2400);
clear();
squares("un", n = {0,0,1}, map = cool_warm, min = 0., max = 2*U_AVG, linear = true);
#if NCAPS > 0
draw_lags(lw = .2, edges = true, facets = true);
#endif
#if INITIAL_FLOW_FIELD
sprintf(name, "iff_un_zoom_nogrid_%d.png", nb_pic);
#else
sprintf(name, "un_zoom_nogrid_%d.png", nb_pic);
#endif
save(name);
nb_pic += 1.;
fprintf(foutput, "%g, %d, %g, %g, %g\n", t, i, CAPS(0).centroid.x,
CAPS(0).centroid.y, CAPS(0).centroid.z);
fflush(foutput);
}
event end (t = T_END) {
fclose(fperf);
#if INITIAL_FLOW_FIELD
fclose(iff_stats);
char dump_name[64];
sprintf(dump_name, "initial_flow_field_re%s_lvl%s.dump",
STRING(REw), STRING(MAXLEVEL));
dump(file = dump_name);
#else
fclose(foutput);
#endif
return 0.;
}