sandbox/ysaade/poiseuille.c

    Starting Poiseuille flow in a cylindrical pipe

    Axial velocity.

    At t = 0, a gradient of pressure G is applied between both ends of a cylindrical pipe, the fluid in which is initially at rest. The converged solution has the well-known Poiseuille velocity profile. The aim of this test case, however, is to check the transient development of the flow, and to validate it against the transient analytical solution.

    #include "grid/multigrid.h"
    #include "axi.h"
    #include "navier-stokes/centered.h"
    #include "bessel-roots.h"

    We will be using the multigrid in order to construct a rectangular domain. The aspect ratio AR is used in the dimensions() function. The total size of the domain is also set to AR. This fixes the radial dimension to R = 1, thus simplifying the analytical expressions. Space and time are dimensionless. This is necessary to be able to use the mu = fm trick. The latter means that the dynamic viscosity is \mu = 1 (with the metric incorporated under the hood), and therefore, G is chosen accordingly so as to yield a maximum velocity of u_z(r = 0, t\rightarrow\infty) = 1.

    When ran in parallel, the domain is divided into square subdomains, each assigned to a different processor. The LEVEL of refinement herein pertains to the subdomains. The total grid is then initialized as a multiple of the number of cells in the smaller domains.

    #define LEVEL 5
    #define AR 8
    #define G 4.
    
    int main() {
      dimensions (nx = AR, ny = 1);
      size (AR [0]);
    
      DT = 1e-3 [0];
      
      TOLERANCE = 1e-6;
    
      init_grid (AR*(1 << LEVEL));
      
      run();
    }

    The top boundary is a no-slip rigid wall. The bottom boundary is the axis of symmetry. A constant pressure boundary condition is applied at both ends so that \Delta p/L0 = G.

    u.n[top] = dirichlet(0.);
    u.t[top] = dirichlet(0.);
    
    u.n[left] = neumann(0.);
    p[left] = dirichlet(G*L0);
    pf[left] = dirichlet(G*L0);
    
    u.n[right] = neumann(0.);
    p[right] = dirichlet(0.);
    pf[right] = dirichlet(0.);
    
    scalar un[];

    We define global variables that store the first nr roots \lambda_n of the zeroth-order Bessel function of the first kind J_0(\lambda) = 0. These will be used in the analytical solution of the transient velocity profile defined as \displaystyle \displaystyle u(r,t) = \frac{G}{4\mu}(R^2 - r^2) - \frac{2GR^2}{\mu}\sum_{n = 1}^{\infty}\frac{1}{\lambda_n^3}\frac{J_0(\lambda_n r/R)}{J_1(\lambda_n)}\exp{(-\lambda_n^2\nu t/R^2)}, where J_0 and J_1 are the zeroth- and first-order Bessel functions of the first kind, and \nu is the kinematic viscosity. In our case, the upper bound of the summation is nr. We start from reasonbale approximation of the roots and use a Newton-Raphson method to refine them to a great degree of accuracy. This is done in bessel-roots.h.

    int nr;
    double * roots;
    
    event init (t = 0) {
      mu = fm;
    
      nr = 50;
      roots = calloc (nr, sizeof(double));
      zeros (nr, roots);

    We initialize the reference velocity.

      foreach()
        un[] = u.x[];
    }

    We save the veloctiy profiles u(r = L0/2,t) as well as the respective analytical solution calculated using the equation above.

    event sampling (t += 0.1) {
      int s = 3*N/AR;
      int k = 0;
      double * v = calloc (s, sizeof(double));
      
      foreach_face(x, reduction(max:k) reduction(max:v[:s])) {
        if (x == L0/2.) {
          v[k] = y;
          v[k + N/AR] = (u.x[] + u.x[-1])/2.;
          double temp = 0;
          for (int i = 0; i < nr; i++)
    	temp += j0(y*roots[i])*exp(-t*sq(roots[i]))/
    	  j1(roots[i])/cube(roots[i]);
          temp *= -2.*G;
          temp += G*(1 - sq(y))/4.;
          v[k + 2*N/AR] = temp;
          k++;
        }
      }
    
      if (pid() == 0) {
        char name[80];
        sprintf (name, "ux-%g.dat", t);
        FILE * fp = fopen (name, "a");
        for (int i = 0; i < N/AR; i++)
          fprintf (fp, "%.6f\t%.6f\t%.6f\n",
    	       v[i], v[i + N/AR], v[i + 2*N/AR]);
        fclose(fp);
      }  
      free(v);
    }

    We generate a movie of the flow development.

    event movie (t += 0.05) {
      output_ppm (u.x, file = "mov.mp4",
    	      min = 0., max = 1., linear = true);
    }

    We check for a stationary solution.

    event convergence (t += 0.1; i <= 10000) {
      double du = change (u.x, un);
      if (i > 0 && du < 1e-12)
        return 1; 			/* stop */
    }

    We also save the converged velocity profile to be checked against the Poiseuille velocity profile \displaystyle \displaystyle u(r) = \frac{G}{4\mu}(R^2 - r^2).

    event error (t = end) {
      int s = 2*N/AR;
      int k = 0;
      double * v = calloc (s, sizeof(double));
      
      foreach_face(x, reduction(max:k) reduction(max:v[:s])) {
        if (x == L0/2.) {
          v[k] = y;
          v[k + N/AR] = (u.x[] + u.x[-1])/2.;
          k++;
        }
      }
      
      if (pid() == 0) {
        FILE * fp = fopen ("final.dat","a");
        for (int i = 0; i < N/AR; i++)
          fprintf (fp, "%.6f\t%.6f\n", v[i], v[i + N/AR]);
        fclose(fp);
      }
      free(v); free(roots);
    }
    set xlabel 'r'
    set ylabel 'u(r)'
    set size ratio -1
    set xrange[0:1]
    set yrange[0:1]
    f(x) = 1 - x*x
    plot 'final.dat' u 1:2 w p title 'Basilisk' ,\
    f(x) w l title 'Analytical'
    Converged velocity profile (script)

    Converged velocity profile (script)

    set xlabel 'r'
    set ylabel 'u(r)'
    set size ratio -1
    set xrange[0:1]
    set yrange[0:1]
    l = 10
    array styles[2]
    styles[1] = "p pt 7"
    styles[2] = "l"
    cmd = "plot "
    cmd = cmd . "keyentry w p pt 7 lc 1 title \"Basilisk\","
    cmd = cmd . "keyentry w l lc 1 title \"Analytical\","
    do for [i = 0:l] {
    do for [c = 1:2] {
    cmd = cmd . sprintf("'ux-%g.dat' u 1:%d w %s lc %d notitle",\
    0.1*i, c + 1, styles[c], i)
    if (i*c != 2*l) {cmd = cmd . ", "}
    }
    }
    eval cmd
    Transient velocity profiles at t = [0:0.1:1] (script)

    Transient velocity profiles at t = [0:0.1:1] (script)