sandbox/nlemoine/ice/halfar2D.c
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: \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: \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, \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: V=\frac{2\pi n}{n+1}(aV)\ \Beta\!\left(\frac{3n+1}{2n+1},\frac{2n}{n+1}\right) hence 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 \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.
