sandbox/nlemoine/ice/halfar2D.c

    Return to my homepage

    2D test case for the Shallow Ice Approximation diffusive equation:

    This is the 2D version of Halfar’s similarity solution (see halfar1D.c) for a flat substratum (z_b = 0) and a zero mass balance. The solution has a radial symmetry: \displaystyle \eta(r,t)=h(r,t)=(aV)^{1/3}f^2(t)\,g\!\left(\frac{r f(t)}{(aV)^{1/3}}\right)

    #include "grid/quadtree.h"
    #include "run.h"
    #include "diffusion.h"
    #include "view.h"
    #include "nlemoine/view-utils.h"
    
    #define LEVEL         8
    #define MINLEVEL      6
    #define ETAE          20. // error on surface elevation
    #define sec_per_year  31557600.
    #define tfin          9000.
    #define nGlen	      3.
    #define AGlen	      5.0e-24
    #define M_PI          acos(-1.0)
    
    scalar zb[],eta[];
    face vector D[];
    double Volume,aV;
    double t0,Gamma;
    
    double dt, tini;
    mgstats mgd;
    
    double Beta(double xx, double yy){
     return exp(lgamma(xx)+lgamma(yy)-lgamma(xx+yy));
    }

    Diffusivity function

    int update_diffusivity(scalar zb, scalar eta, face vector D)
    {
      //    /!\ reminder : D.x and D.y are not co-located
      foreach_face()
      {
        double hm = (eta[]+eta[-1,0]-zb[]-zb[-1,0])/2.; // ice thickness interpolated at face center
        hm = max(hm,0.);
        double sn = (eta[]-eta[-1,0])/Delta; // slope component along face normal
        double st = (eta[0,-1]+eta[-1,-1]-eta[0,1]-eta[-1,1])/4./Delta;	// slope component transverse to face normal
        D.x[] = sec_per_year*Gamma*pow(hm,5.)*(sq(sn)+sq(st));
      }
    
     return(0);
    }

    Halfar 2D similarity solution (Halfar, 1983)

    In Halfar’s paper the solution is scaled using total volume V multiplied by a constant a. Noting R(t) the radius at time t, the explanation is as follows: \displaystyle \begin{aligned} V(t) & = \int_{\theta=0}^{2\pi}\int_{r=0}^{R(t)}h(r,t)\,r\,dr\,d\theta = 2\pi\int_0^{R(t)=\frac{(aV)^{1/3}}{f(t)}}(aV)^{1/3}f^2(t)\,g\!\left(\frac{r}{R(t)}\right)r\,dr\\ & = 2\pi(aV)\int_0^1 g(\xi)\,\xi\,d\xi \end{aligned} Moreover, \displaystyle \int_0^1 g(\xi)\,\xi\,d\xi = \int_0^1 \left(1-\xi^{\frac{n+1}{n}}\right)^{\frac{n}{2n+1}}\xi\,d\xi = \frac{n}{n+1}\int_0^1 \left(1-\xi'\right)^{\frac{n}{2n+1}}\xi'^{\frac{2n}{n+1}-1}d\xi'

    The latter integral is the Beta function \Beta\!\left(\frac{3n+1}{2n+1},\frac{2n}{n+1}\right), so we find the condition for a: \displaystyle V=\frac{2\pi n}{n+1}(aV)\ \Beta\!\left(\frac{3n+1}{2n+1},\frac{2n}{n+1}\right) hence \displaystyle a=\frac{n+1}{2\pi n\ \Beta\!\left(\frac{3n+1}{2n+1},\frac{2n}{n+1}\right)}

    double Halfar2D(double xx, double yy, double tt)
    {
      double rr = sqrt(sq(xx)+sq(yy));
      double ft = pow(t0/tt,1./(5.*nGlen+3.)); // eq. (5)
      double cbrtaV = cbrt(aV);
      double argg = rr*ft/cbrtaV;  // eq. (3)
      double res = argg < 1. ? cbrtaV*sq(ft)*pow( (1.0 - pow(argg,1.+1./nGlen)) , nGlen/(2.*nGlen+1.) ) : 0. ; // eq. (4)
      return res  ;
    }

    Main

    int main (int argc, char * argv[])
    {
      N = 1 << LEVEL;
      L0 = 2.0e6;
      size (L0);
      origin (-L0/2.,-L0/2.);
    
      Volume = 2.0e15;  // Total volume (m3)
    
      double bet = Beta((3.*nGlen+1.)/(2.*nGlen+1.),2.*nGlen/(nGlen+1.));
      aV = Volume*(nGlen+1.)/(2.*M_PI*nGlen*bet);
      Gamma  = 2. * AGlen * pow(9.81*910.,nGlen)/(nGlen+2.) ;
      t0 = pow((2.*nGlen+1.)/(nGlen+1.),nGlen)*pow(aV,-nGlen/3.)/(5.*nGlen+3.); // eq. (6)
      t0 = t0/Gamma/sec_per_year;
    
      tini = 1.;
    
      run();
      return(0);
    }
    
    event init (i=0)
    {
      foreach()
      {
        zb[] = 0.;
        eta[] = Halfar2D(x,y,tini);
      }
      boundary({zb,eta});
    }
    
    scalar l[];
    scalar analytical[],numerical[];
    scalar msk_ana[], msk_num[];
    
    event movie (t=0.; t += 25.)
    {
      foreach()
      {
        analytical[] = Halfar2D(x,y,t+tini);
        numerical[] = eta[];
        boundary({analytical,numerical});
        msk_ana[] = x > 0. ? -1 : 1.;
        msk_num[] = x > 0. ? 1. : -1;
      }
    
      float qview_x[4],qview_z[4],qview[4];
      (void) gl_axis_to_quat ((float[]){1,0,0}, 2.*M_PI/6., qview_x);
      (void) gl_axis_to_quat ((float[]){0,0,1}, M_PI/32., qview_z);
      gl_add_quats(qview_x, qview_z, qview);
    
      view (fov = 19., quat = {qview[0],qview[1],qview[2],qview[3]},
    	tx = 0., ty = -0.05,
            sx = 1., sy = 1., sz = 200.,
            width = 1200, height = 768);
      char s[80];
      masked_squares ("analytical", linear = true, z = "analytical", 
                      min = 0., max = 4000., mask = msk_ana);
      masked_squares ("numerical", linear = true, z = "numerical",
                      min = 0., max = 4000., mask = msk_num);
      //surf_cells(analytical, mask = msk_ana);
      surf_cells(numerical, mask = msk_num);
    
      sprintf (s," left : analytical");
      draw_string (s, 0, size = 48, lw = 2);
      sprintf (s,"right : numerical ");
      draw_string (s, 3, size = 48, lw = 2);
    
      sprintf (s,"t = %3.1f ka ", t/1000.);
      draw_string (s, 2, size = 48, lw = 2);
      sprintf (s," Z-stretch x 200");
      draw_string (s, 1, size = 48, lw = 2);
      
      char fname[200];
      sprintf(fname,"frame-%4.4d.png",(int)t);  
      save (fname);
      
      stats s_ana = statsf (analytical);
      stats s_num = statsf (numerical);
      fprintf(stderr,"t = %.0f ka, Max. analytical solution: %.2lf m, Max. numerical solution: %.2lf m\n",t,s_ana.max,s_num.max);
    }
    
    event stop (t = tfin)
    {  
      system ("for f in frame-*.png; do convert $f ppm:- && rm -f $f; done | "
    	  "ppm2mp4 animate.mp4");
      fprintf(stderr,"Done.\n");
      return 1;
    }

    Time integration

    The SIA is basically a diffusion equation, so we use the Poisson solver for integration. Since this solver is time-implicit, there isn’t any stability problem; however, since the diffusivity depends on the solution, setting a too large time step will cause spurious features to develop in the solution if the diffusivity field is not updated often enough. So we set the time step to a ‘’reasonable’’ (up to 10) multiple of the explicit time step \displaystyle \Delta t_\textrm{explicit} = \frac{1}{4}\frac{\Delta x^2}{D_\textrm{max}}

    event integration (i++)
    {
      (void) update_diffusivity(zb,eta,D);
    
      stats s = statsf (D.x);
      double dtExplicit = 0.25*L0*L0/N/N/s.max;
      dt = dtnext(4.0*dtExplicit);
    
      mgd = diffusion(eta,dt,D);
    
      // preserve positivity of ice tickness
      foreach()
       eta[] = max(zb[],eta[]);
    }
    
    // Adaptivity
    
    int adapt() {
    #if TREE
      astats s = adapt_wavelet ({eta}, (double[]){ETAE},
    			    LEVEL, MINLEVEL);
    //  fprintf (ferr, "# refined %d cells, coarsened %d cells\n", s.nf, s.nc);
      return s.nf;
    #else // Cartesian
      return 0;
    #endif
    }
    
    // And finally we set the event that will apply our *adapt()* function at every timestep.
    
    event do_adapt (i++) adapt();

    Animation of the solution

    Animation of the solution. The left-hand part of the image displays the analytical solution, and the right-hand part displays the numerical solution and the adaptive grid.