sandbox/huet/cases/lagrangian_caps/narrow_constriction.c

    3D deformation of an initially spherical capsule in a narrow constriction

    Definition of the relevant parameters

    We define the following solver-ralated quantities:

    • the minimum and maximum refinement levels of the Eulerian grid
    • the non-dimensional duration time of the simulation TEND
    • the number of Lagrangian points NLP on the capsule
    • the maximum time-step DT_MAX, which appears to be Reynolds dependant
    • the tolerance of the Poisson solver (maximum admissible residual)
    • the tolerance of the wavelet adaptivity algorithm for the velocity
    • the stokes boolean, in order to ignore the convective term in the Navier-Stokes solver
    • the Jacobi preconditionner, which we switch on for this case.
    • the output frequency: a picture is generated every OUTPUT_FREQ iterations
    #ifndef MINLEVEL
      #define MINLEVEL 2
    #endif
    #ifndef MAXLEVEL
      #define MAXLEVEL 10
    #endif
    #ifndef TEND
      #define TEND (4.)
    #endif
    #ifndef LAG_LEVEL
      #define LAG_LEVEL 5
    #endif
    #ifndef DT_MAX
      #define DT_MAX 2.5e-5
    #endif
    #ifndef MY_TOLERANCE
      #define MY_TOLERANCE 1.e-3
    #endif
    #ifndef U_TOL
      #define U_TOL 0.05
    #endif
    #ifndef OUTPUT_FREQ
      #define OUTPUT_FREQ 100
    #endif
    #ifndef STOKES
      #define STOKES true
    #endif
    #define JACOBI 1

    We also define the following physical quantities:

    • the length of the channel L_0 = 20,
    • the half-height H_c = 1 of the constriction of the channel
    • the radius of the capsule a = H_c = 1,
    • the average x-component of the velocity at the inlet and outlet u_{\text{avg}} = 1
    • the Reynolds number Re \ll 1,
    • the viscosity \mu = 1,
    • the density \rho = \frac{Re \, \mu}{u_{\text{avg}} 2 a},
    • the Capillary number defined - in the case of capsules - as the ratio of viscous forces over elastic forces: Ca = \frac{\mu u_{\text{avg}}}{E_s}
    • the elastic modulus E_s of the membrane
    • the area dilatation modulus: C=1 is a large value to enforce approximate area conservation
    • the prestress coefficient of the membrane \alpha_p = 0.05
    #define L0 20.
    #define H0 (2. + 2.e-7)
    #define HC (.25*H0)
    #define RADIUS (.5*H0)
    #define U_AVG 1.
    #ifndef RE
      #define RE 0.01
    #endif
    #define MU 1.
    #define RHO (RE*MU/(U_AVG*2*RADIUS))
    #ifndef CA
      #define CA 0.1
    #endif
    #define E_S (MU*U_AVG/CA)
    #define AREA_DILATATION_MODULUS 1.
    #define ALPHA_P 0.05
    #define ND_EB 0.001
    #define E_B (ND_EB*E_S*sq(RADIUS))
    #define REF_CURV 0

    Simulation setup

    We import the octree grid, the centered Navier-Stokes solver, the Lagrangian mesh, the Skalak elastic law, a header file containing functions to mesh a sphere, and the Basilisk viewing functions supplemented by a custom function draw\_lag useful to visualize the front-tracking interface.

    #include "grid/octree.h"
    #include "embed.h"
    #include "navier-stokes/centered.h"
    #include "lagrangian_caps/capsule-ft.h"
    #include "lagrangian_caps/skalak-ft.h"
    #include "lagrangian_caps/bending-ft.h"
    #include "lagrangian_caps/common-shapes-ft.h"
    #include "lagrangian_caps/view-ft.h"
    
    FILE* foutput = NULL;
    FILE* fperf = NULL;
    
    scalar rhov[];
    face vector muv[];
    face vector alphav[];
    
    int main(int argc, char* argv[]) {
      origin(-0.5*L0, -0.5*L0, -0.5*L0);
      N = 1 << MINLEVEL;
      init_grid(N);
      mu = muv;
      alpha = alphav;
      rho = rhov;
      TOLERANCE = MY_TOLERANCE;

    We don’t need to compute the convective term in this case, so we set the boolean stokes to false. However it is still important to choose Re \ll 1 since we are solving the unsteady Stokes equation.

      stokes = STOKES;
      DT = DT_MAX;
      run();
    }

    We impose the boundary conditions: the velocity at the inlet and outlet is set to u_{\text{avg}} in the x-direction and to 0 otherwise.

    u.n[left] = dirichlet(U_AVG);
    u.n[right] = dirichlet(U_AVG);
    u.t[left] = dirichlet(0.);
    u.t[right] = dirichlet(0.);
    u.r[left] = 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.);
    
    event init (i = 0) {
      foutput  = fopen("output.txt","w");
      fperf = fopen("perf.txt", "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");

    We initialize a spherical membrane using the pre-defined function in common-shapes-ft.h.

      activate_spherical_capsule(&CAPS(0), level = LAG_LEVEL, radius = RADIUS/(1. + ALPHA_P));

    In this case we use a third order face-flux interpolation.

      for (scalar s in {u})
        s.third = true;
    }
    
    event init_adapt (i = 0) {

    The membrane is pre-inflated, so the stress-free configuration corresponds to a smaller radius a_0, and the current radius a is related to a_0 by the prestress coefficient \alpha_p: a = a_0 (1 + \alpha_p).

      for(int k=0; k<CAPS(0).nln; k++) {
        double cr = 0.;
        foreach_dimension() cr += sq(CAPS(0).nodes[k].pos.x);
        cr = sqrt(cr);
        foreach_dimension() CAPS(0).nodes[k].pos.x *= RADIUS/cr;
        CAPS(0).nodes[k].pos.x -= 2*H0;
      }
      generate_lag_stencils(&CAPS(0));

    We refine the mesh around the embedded geometry, around the membrane, and at the inlet and outlet.

      astats ss;
      int ic = 0;
      do {
        ic++;
        tag_ibm_stencils(&CAPS(0));
        foreach_boundary(left)
          if (cs[] > 1.e-10) stencils[] = noise();
        foreach_boundary(right)
          if (cs[] > 1.e-10) stencils[] = noise();
        solid (cs, fs, intersection(intersection(-fabs(y) + H0,
          -fabs(z) + H0), (fabs(x) < .5*H0) ? -fabs(y) + HC :
          -fabs(y) + H0));
        ss = adapt_wavelet ({stencils, cs}, (double[]) {1.e-30, 1.e-30},
          maxlevel = MAXLEVEL, minlevel = MINLEVEL);
        generate_lag_stencils(&CAPS(0));
      } while ((ss.nf || ss.nc) && ic < 100);

    The initial condition for the x-component of the velocity field is set to the average velocity everywhere, except in the constriction where the velocity is doubled. The y and z components are zero.

      foreach() u.x[] = (fabs(x) < .5*H0) ? (H0/HC)*U_AVG*cm[] : U_AVG*cm[];
      foreach_face() uf.x[] = (fabs(x) < .5*H0) ? (H0/HC)*U_AVG*fm.x[] : U_AVG*fm.x[];
    }

    Due to embedded boundaries, the viscosity and density fields are not constant since they have to be multiplied by the volume fraction.

    event properties (i++) {
      foreach_face() {
        muv.x[] = MU*fm.x[];
        alphav.x[] = 1./RHO*fm.x[];
      }
      boundary((scalar*){muv});
      foreach() rhov[] = RHO*cm[];
    }

    The Eulerian mesh is adapted at every time step, according to two criteria:

    • first, the 5x5x5 stencils neighboring each Lagrangian node need to be at a constant level. For this purpose we tag them in the stencil scalar, which is fed to the adapt\_wavelet algorithm,
    • second, we adapt according to the velocity field.
    event adapt (i++) {
      tag_ibm_stencils(&CAPS(0));
      adapt_wavelet({cs, stencils, u}, (double []){1.e-30,
        1.e-30, U_TOL, U_TOL, U_TOL}, minlevel = MINLEVEL, maxlevel = MAXLEVEL);
      generate_lag_stencils(&CAPS(0));
    }

    In the event below, we output the maximum length of the capsule in the x, y and z directions, as well as the position of the centroid of the capsule.

    event logfile (i += OUTPUT_FREQ) {
      if (pid() == 0) {
        double min_x, max_x, min_y, max_y, min_z, max_z;
        min_x = HUGE; max_x = -HUGE;
        min_y = HUGE; max_y = -HUGE;
        min_z = HUGE; max_z = -HUGE;
        coord xc;
        foreach_dimension() xc.x = 0.;
        for(int i=0; i<CAPS(0).nln; i++) {
          coord* cni = &(CAPS(0).nodes[i].pos);
          if (cni->x > max_x) max_x = cni->x;
          if (cni->x < min_x) min_x = cni->x;
          if (cni->y > max_y) max_y = cni->y;
          if (cni->y < min_y) min_y = cni->y;
          if (cni->z > max_z) max_z = cni->z;
          if (cni->z < min_z) min_z = cni->z;
          foreach_dimension() xc.x += cni->x;
        }
        double l_x = max_x - min_x;
        double l_y = max_y - min_y;
        double l_z = max_z - min_z;
        foreach_dimension() xc.x /= CAPS(0).nln;
        fprintf(foutput, "%g %g %g %g %g %g %g\n", t, xc.x, xc.y, xc.z,
          l_x, l_y, l_z);
        fflush(foutput);
    
        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);
      }
    }

    We also output a snapshot every OUTPUT_FREQ iteration. These four snapshots are then glued side-by-side together and made into a movie using ffmpeg.

    event pictures (i += OUTPUT_FREQ) {
      if (i == 0) {
        view(fov = 19., bg = {1,1,1}, tx = 0.);
        clear();
        squares("u.x", n = {0,0,1}, map = cool_warm,
          min = 0., max = 3*U_AVG);
        cells(n = {0, 0, 1});
        draw_lag(&CAPS(0), lw = 1., edges = true, facets = true);
        save("initial_field.png");
      }
      char name[32];
      view(fov = .25*19.09  , bg = {1,1,1}, width = 2400, height = 600);
      clear();
      squares("u.x", n = {0,0,1}, map = cool_warm,
        min = 0., max = 5*U_AVG);
      cells(n = {0, 0, 1});
      draw_lag(&CAPS(0), lw = 1., edges = true, facets = true, fc = {1., 1., 1.});
      sprintf(name, "ux_%d.png", i);
      save(name);
    }
    
    event dump_data (i += 10*OUTPUT_FREQ) {
      char name[64];
      char name_mb[64];
      sprintf(name_mb, "dump_mb_%d", i);
      sprintf(name, "dump_%d", i);
      if (pid() == 0) dump_capsules(name_mb);
      dump(file = name);
    }
    
    event end (t = TEND) {
      fclose(foutput);
      return 0.;
    }

    References

    [park2013transient]

    Sun-Young Park and P Dimitrakopoulos. Transient dynamics of an elastic capsule in a microfluidic constriction. Soft matter, 9(37):8844–8855, 2013.