src/grid/multigrid.h

    #if SINGLE_PRECISION
    typedef float real;
    #else
    typedef double real;
    #endif
    
    #ifndef GRIDNAME
    # define GRIDNAME "Multigrid"
    #endif
    #define GHOSTS 2
    
    /* By default only one layer of ghost cells is used on the boundary to
       optimise the cost of boundary conditions. */
    
    #ifndef BGHOSTS
    @ define BGHOSTS 1
    #endif
    
    #if _MPI
    # define ND(i) ((size_t)(1 << point.level))
    #else
    # define ND(i) ((size_t)(1 << point.level)*((int *)&Dimensions)[i])
    #endif
    
    #define _I     (point.i - GHOSTS)
    #define _J     (point.j - GHOSTS)
    #define _K     (point.k - GHOSTS)
    
    int Dimensions_scale = 1;
    #define _DELTA (1./((1 << point.level)*Dimensions_scale))
    
    typedef struct {
      Grid g;
      char * d;
      size_t * shift;
    } Multigrid;
    
    struct _Point {
      int i;
    #if dimension > 1
      int j;
    #endif
    #if dimension > 2
      int k;
    #endif
      int level;
    #if dimension == 1
      struct { int x; } n;
    #elif dimension == 2
      struct { int x, y; } n;
    #elif dimension == 3
      struct { int x, y, z; } n;
    #endif
    #if LAYERS
      int l;
      @define _BLOCK_INDEX , point.l
    #else
      @define _BLOCK_INDEX
    #endif
    };
    static Point last_point;
    
    #if LAYERS
    # include "grid/layers.h"
    #endif
    
    #define multigrid ((Multigrid *)grid)
    #define CELL(m,level,i)  (*((Cell *) &m[level][(i)*datasize]))
    
    /***** Cartesian macros *****/
    #if dimension == 1
    @undef val
    @def val(a,k,l,m) (((real *)multigrid->d)[point.i + (k) +
    					  multigrid->shift[point.level] +
    					  _index(a,m)*multigrid->shift[depth() + 1]])
    @
    #elif dimension == 2
    @undef val
    @def val(a,k,l,m) (((real *)multigrid->d)[point.j + (l) +
    					  (point.i + (k))*(ND(1) + 2*GHOSTS) +
    					  multigrid->shift[point.level] +
    					  _index(a,m)*multigrid->shift[depth() + 1]])
    @
    #elif dimension == 3
    @undef val
    @def val(a,l,m,o) (((real *)multigrid->d)[point.k + (o) +
    					  (ND(2) + 2*GHOSTS)*
    					  (point.j + (m) +
    					   (point.i + (l))*(ND(1) + 2*GHOSTS)) +
    					  multigrid->shift[point.level] +
    					  _index(a,0)*multigrid->shift[depth() + 1]])
    @
    #endif
    
    /* low-level memory management */
    #if dimension == 1
    # if BGHOSTS == 1
    @define allocated(...) true
    # else // BGHOST != 1
    @define allocated(k,l,m) (point.i+(k) >= 0 && point.i+(k) < ND(0) + 2*GHOSTS)
    # endif // BGHOST != 1
    @def allocated_child(k,l,m) (level < depth() &&
                                 point.i > 0 && point.i <= ND(0) + 2)
    @
    #elif dimension == 2
    # if BGHOSTS == 1
    @define allocated(...) true
    # else // BGHOST != 1
    @def allocated(k,l,m) (point.i+(k) >= 0 && point.i+(k) < ND(0) + 2*GHOSTS &&
    		       point.j+(l) >= 0 && point.j+(l) < ND(1) + 2*GHOSTS)
    @
    # endif // BGHOST != 1
    @def allocated_child(k,l,m)  (level < depth() &&
    			      point.i > 0 && point.i <= ND(0) + 2 &&
    			      point.j > 0 && point.j <= ND(1) + 2)
    @			   
    #else // dimension == 3
    # if BGHOSTS == 1
    @define allocated(...) true
    #else // BGHOST != 1
    @def allocated(a,l,m) (point.i+(a) >= 0 &&
    		       point.i+(a) < ND(0) + 2*GHOSTS &&
    		       point.j+(l) >= 0 &&
    		       point.j+(l) < ND(1) + 2*GHOSTS &&
    		       point.k+(m) >= 0 &&
    		       point.k+(m) < ND(2) + 2*GHOSTS)
    @
    #endif // BGHOST != 1
    @def allocated_child(a,l,m)  (level < depth() &&
    			      point.i > 0 && point.i <= ND(0) + 2 &&
    			      point.j > 0 && point.j <= ND(1) + 2 &&
    			      point.k > 0 && point.k <= ND(2) + 2)
    @
    #endif // dimension == 3
    
    /***** Multigrid variables and macros *****/
    @define depth()       (grid->depth)
    #if dimension == 1
    @def fine(a,k,l,m)
    (((real *)multigrid->d)[2*point.i - GHOSTS + (k) +
    			multigrid->shift[point.level + 1] +
    			_index(a,m)*multigrid->shift[depth() + 1]])
    @
    @def coarse(a,k,l,m)
    (((real *)multigrid->d)[(point.i + GHOSTS)/2 + (k) +
    			multigrid->shift[point.level - 1] +
    			_index(a,m)*multigrid->shift[depth() + 1]])
    @
    
    macro POINT_VARIABLES (Point point = point)
    {
      VARIABLES();
      int level = point.level; NOT_UNUSED(level);
      struct { int x; } child = { 2*((point.i+GHOSTS)%2)-1 }; NOT_UNUSED(child);
      Point parent = point; NOT_UNUSED(parent);
      parent.level--;
      parent.i = (point.i + GHOSTS)/2;
    }
    
    #elif dimension == 2
    @def fine(a,k,l,m)
    (((real *)multigrid->d)[2*point.j - GHOSTS + (l) +
    			(2*point.i - GHOSTS + (k))*(ND(1)*2 + 2*GHOSTS) +
    			multigrid->shift[point.level + 1] +
    			_index(a,m)*multigrid->shift[depth() + 1]])
    @
    @def coarse(a,k,l,m)
    (((real *)multigrid->d)[(point.j + GHOSTS)/2 + (l) +
    			((point.i + GHOSTS)/2 + (k))*(ND(1)/2 + 2*GHOSTS) +
    			multigrid->shift[point.level - 1] +
    			_index(a,m)*multigrid->shift[depth() + 1]])
    @
    
    macro POINT_VARIABLES (Point point = point) {
      VARIABLES();
      int level = point.level; NOT_UNUSED(level);
      struct { int x, y; } child = {
        2*((point.i+GHOSTS)%2)-1, 2*((point.j+GHOSTS)%2)-1
      }; NOT_UNUSED(child);
      Point parent = point;	NOT_UNUSED(parent);
      parent.level--;
      parent.i = (point.i + GHOSTS)/2; parent.j = (point.j + GHOSTS)/2;
    }
    
    #elif dimension == 3
    @def fine(a,l,m,o)
    (((real *)multigrid->d)[2*point.k - GHOSTS + (o) +
    			(ND(2)*2 + 2*GHOSTS)*
    			(2*point.j - GHOSTS + (m) +
    			 (2*point.i - GHOSTS + (l))*(ND(1)*2 + 2*GHOSTS)) +
    			multigrid->shift[point.level + 1] +
    			_index(a,0)*multigrid->shift[depth() + 1]])
    @
    @def coarse(a,l,m,o)
    (((real *)multigrid->d)[(point.k + GHOSTS)/2 + (o) +
    			(ND(2)/2 + 2*GHOSTS)*
    			((point.j + GHOSTS)/2 + (m) +
    			 ((point.i + GHOSTS)/2 + (l))*(ND(1)/2 + 2*GHOSTS)) +
    			multigrid->shift[point.level - 1] +
    			_index(a,0)*multigrid->shift[depth() + 1]])
    @
    
    macro POINT_VARIABLES (Point point = point)
    {
      VARIABLES();
      int level = point.level; NOT_UNUSED(level);
      struct { int x, y, z; } child = {
        2*((point.i + GHOSTS)%2) - 1,
        2*((point.j + GHOSTS)%2) - 1,
        2*((point.k + GHOSTS)%2) - 1
      }; NOT_UNUSED(child);
      Point parent = point;	NOT_UNUSED(parent);
      parent.level--;
      parent.i = (point.i + GHOSTS)/2;
      parent.j = (point.j + GHOSTS)/2;
      parent.k = (point.k + GHOSTS)/2;
    }
    
    #endif // dimension == 3
    
    #if _MPI
    #if dimension == 1
    # define SET_DIMENSIONS() point.n.x = 1 << point.level
    #elif dimension == 2
    # define SET_DIMENSIONS() point.n.x = point.n.y = 1 << point.level
    #elif dimension == 3
    # define SET_DIMENSIONS() point.n.x = point.n.y = point.n.z = 1 << point.level
    #endif
    #else // !_MPI
    #if dimension == 1
    # define SET_DIMENSIONS() point.n.x = (1 << point.level)*Dimensions.x
    #elif dimension == 2
    # define SET_DIMENSIONS()		       \
      point.n.x = (1 << point.level)*Dimensions.x, \
      point.n.y = (1 << point.level)*Dimensions.y
    #elif dimension == 3
    # define SET_DIMENSIONS()		       \
      point.n.x = (1 << point.level)*Dimensions.x, \
      point.n.y = (1 << point.level)*Dimensions.y, \
      point.n.z = (1 << point.level)*Dimensions.z
    #endif  
    #endif // !_MPI
    
    macro2 foreach_level (int l, char flags = 0, Reduce reductions = None) {
      OMP_PARALLEL (reductions) {
        int ig = 0, jg = 0, kg = 0; NOT_UNUSED(ig); NOT_UNUSED(jg); NOT_UNUSED(kg);
        Point point = {0};
        point.level = l;
        SET_DIMENSIONS();
        int _k;
        OMP(omp for schedule(static))
        for (_k = GHOSTS; _k < point.n.x + GHOSTS; _k++) {
          point.i = _k;
    #if dimension > 1
          for (point.j = GHOSTS; point.j < point.n.y + GHOSTS; point.j++)
    #if dimension > 2
    	for (point.k = GHOSTS; point.k < point.n.z + GHOSTS; point.k++)
    #endif
    #endif
    	  {...}
        }
      }
    }
    
    macro2 foreach (char flags = 0, Reduce reductions = None) {
      OMP_PARALLEL (reductions) {
        int ig = 0, jg = 0, kg = 0; NOT_UNUSED(ig); NOT_UNUSED(jg); NOT_UNUSED(kg);
        Point point = {0};
        point.level = depth();
        SET_DIMENSIONS();
        int _k;
        OMP(omp for schedule(static))
          for (_k = GHOSTS; _k < point.n.x + GHOSTS; _k++) {
    	point.i = _k;
    #if dimension > 1
    	for (point.j = GHOSTS; point.j < point.n.y + GHOSTS; point.j++)
    #if dimension > 2
    	  for (point.k = GHOSTS; point.k < point.n.z + GHOSTS; point.k++)
    #endif
    #endif
    	    {...}
          }
      }
    }
    
    @define is_active(cell) (true)
    @define is_leaf(cell)   (point.level == depth())
    @define is_local(cell)  (true)
    @define leaf            2
    @def refine_cell(...) do {
      fprintf (stderr, "grid depths do not match. Aborting.\n");
      assert (0);
    } while (0)
    @
    @define tree multigrid
    #include "foreach_cell.h"
    
    macro2 foreach_face_generic (char flags = 0, Reduce reductions = None,
    			     const char * order = "xyz")
    {
      OMP_PARALLEL (reductions) {
        int ig = 0, jg = 0, kg = 0; NOT_UNUSED(ig); NOT_UNUSED(jg); NOT_UNUSED(kg);
        Point point = {0};
        point.level = depth();
        SET_DIMENSIONS();
        int _k;
        OMP(omp for schedule(static))
          for (_k = GHOSTS; _k <= point.n.x + GHOSTS; _k++) {
    	point.i = _k;
    #if dimension > 1
    	for (point.j = GHOSTS; point.j <= point.n.y + GHOSTS; point.j++)
    #if dimension > 2
    	  for (point.k = GHOSTS; point.k <= point.n.z + GHOSTS; point.k++)
    #endif
    #endif
    	    {...}
          }
      }
    }
    
    @define is_coarse() (point.level < depth())
    
    #if dimension == 1
    macro1 is_face_x() {
      {
        int ig = -1; NOT_UNUSED(ig);
        {...}
      }
    }
    
    // foreach_edge?
    
    macro1 foreach_child (Point point = point, break = (_k = 2)) {
      {
        int _i = 2*point.i - GHOSTS;
        point.level++;
        point.n.x *= 2;
        for (int _k = 0; _k < 2; _k++) {
          point.i = _i + _k;
          POINT_VARIABLES();
          {...}
        }
        point.i = (_i + GHOSTS)/2;
        point.level--;
        point.n.x /= 2;
      }
    }
    
    #elif dimension == 2
    #define foreach_edge() foreach_face(y,x)
    
    macro1 is_face_x (Point p = point) {
      if (p.j < p.n.y + GHOSTS) {
        int ig = -1; NOT_UNUSED(ig);
        {...}
      }
    }
    
    macro1 is_face_y (Point p = point) {
      if (p.i < p.n.x + GHOSTS) {
        int jg = -1; NOT_UNUSED(jg);
        {...}
      }
    }
    
    macro1 foreach_child (Point point = point, break = (_k = _l = 2))
    {
      {
        int _i = 2*point.i - GHOSTS, _j = 2*point.j - GHOSTS;
        point.level++;
        point.n.x *= 2, point.n.y *= 2;
        for (int _k = 0; _k < 2; _k++)
          for (int _l = 0; _l < 2; _l++) {
    	point.i = _i + _k; point.j = _j + _l;
    	POINT_VARIABLES();
    	{...}
          }
        point.i = (_i + GHOSTS)/2; point.j = (_j + GHOSTS)/2;
        point.level--;
        point.n.x /= 2, point.n.y /= 2;
      }
    }
    
    #elif dimension == 3
    macro1 is_face_x (Point p = point) {
      if (p.j < p.n.y + GHOSTS && p.k < p.n.z + GHOSTS) {
        int ig = -1; NOT_UNUSED(ig);
        {...}
      }
    }
    
    macro1 is_face_y (Point p = point) {
      if (p.i < p.n.x + GHOSTS && p.k < p.n.z + GHOSTS) {
        int jg = -1; NOT_UNUSED(jg);
        {...}
      }
    }
    
    macro1 is_face_z (Point p = point) {
      if (p.i < p.n.x + GHOSTS && p.j < p.n.y + GHOSTS) {
        int kg = -1; NOT_UNUSED(kg);
        {...}
      }
    }
    
    macro1 foreach_child (Point point = point, break = (_l = _m = _n = 2))
    {
      {
        int _i = 2*point.i - GHOSTS;
        int _j = 2*point.j - GHOSTS;
        int _k = 2*point.k - GHOSTS;
        point.level++;
        point.n.x *= 2, point.n.y *= 2, point.n.z *= 2;
        for (int _l = 0; _l < 2; _l++)
          for (int _m = 0; _m < 2; _m++)
    	for (int _n = 0; _n < 2; _n++) {
    	  point.i = _i + _l; point.j = _j + _m; point.k = _k + _n;
    	  POINT_VARIABLES();
    	  {...}
    	}
        point.i = (_i + GHOSTS)/2;
        point.j = (_j + GHOSTS)/2;
        point.k = (_k + GHOSTS)/2;
        point.level--;
        point.n.x /= 2, point.n.y /= 2, point.n.z /= 2;
      }
    }
    #endif
      
    @if TRASH
    @ undef trash
    @ define trash(list) reset(list, undefined)
    @endif
    
    #include "neighbors.h"
    
    void reset (void * alist, double val)
    {
      scalar * list = (scalar *) alist;
      for (scalar s in list)
        if (!is_constant(s))
          for (int b = 0; b < s.block; b++) {
    	real * data = (real *) multigrid->d;
    	data += (s.i + b)*multigrid->shift[depth() + 1];
    	for (size_t i = 0; i < multigrid->shift[depth() + 1]; i++, data++)
    	  *data = val;
          }
    }
    
    // Boundaries
    
    #if dimension == 1
    macro2 foreach_boundary_dir (int l, int d, Reduce reductions = None)
    {
      {
        int ig = 0, jg = 0, kg = 0; NOT_UNUSED(ig); NOT_UNUSED(jg); NOT_UNUSED(kg);
        Point point = {0};
        point.level = l < 0 ? depth() : l;
        SET_DIMENSIONS();
        if (d == left) {
          point.i = GHOSTS;
          ig = -1;
        }
        else if (d == right) {
          point.i = point.n.x + GHOSTS - 1;
          ig = 1;
        }
        {...}
      }
    }
    
    @define neighbor(o,p,q) ((Point){point.i+o, point.level, point.n _BLOCK_INDEX})
    @define is_boundary(point) (point.i < GHOSTS || point.i >= point.n.x + GHOSTS)
    
    #elif dimension == 2
    macro2 foreach_boundary_dir (int l, int d, Reduce reductions = None)
    {
      OMP_PARALLEL (reductions) {
        int ig = 0, jg = 0, kg = 0; NOT_UNUSED(ig); NOT_UNUSED(jg); NOT_UNUSED(kg);
        Point point = {0};
        point.level = l < 0 ? depth() : l;
        SET_DIMENSIONS();
        int * _i = &point.j, _n = point.n.y;
        if (d == left) {
          point.i = GHOSTS;
          ig = -1;
        }
        else if (d == right) {
          point.i = point.n.x + GHOSTS - 1;
          ig = 1;
        }
        else if (d == bottom) {
          point.j = GHOSTS;
          _i = &point.i, _n = point.n.x;
          jg = -1;
        }
        else if (d == top) {
          point.j = point.n.y + GHOSTS - 1;
          _i = &point.i, _n = point.n.x;
          jg = 1;
        }
        int _l;
        OMP(omp for schedule(static))
          for (_l = 0; _l < _n + 2*GHOSTS; _l++) {
    	*_i = _l;
    	{...}
          }
      }
    }
    
    @def neighbor(o,p,q)
      ((Point){point.i+o, point.j+p, point.level, point.n _BLOCK_INDEX})
    @
    @def is_boundary(point) (point.i < GHOSTS || point.i >= point.n.x + GHOSTS ||
    			 point.j < GHOSTS || point.j >= point.n.y + GHOSTS)
    @
    
    #elif dimension == 3
    macro2 foreach_boundary_dir (int l, int d, Reduce reductions = None) {
      OMP_PARALLEL (reductions) {
        int ig = 0, jg = 0, kg = 0; NOT_UNUSED(ig); NOT_UNUSED(jg); NOT_UNUSED(kg);
        Point point = {0};
        point.level = l < 0 ? depth() : l;
        SET_DIMENSIONS();
        int * _i = &point.j, * _j = &point.k;
        int _n[2] = { point.n.y, point.n.z };
        if (d == left) {
          point.i = GHOSTS;
          ig = -1;
        }
        else if (d == right) {
          point.i = point.n.x + GHOSTS - 1;
          ig = 1;
        }
        else if (d == bottom) {
          point.j = GHOSTS;
          _i = &point.i, _n[0] = point.n.x;
          jg = -1;
        }
        else if (d == top) {
          point.j = point.n.y + GHOSTS - 1;
          _i = &point.i, _n[0] = point.n.x;
          jg = 1;
        }
        else if (d == back) {
          point.k = GHOSTS;
          _i = &point.i; _j = &point.j;
          _n[0] = point.n.x, _n[1] = point.n.y;
          kg = -1;
        }
        else if (d == front) {
          point.k = point.n.z + GHOSTS - 1;
          _i = &point.i; _j = &point.j;
          _n[0] = point.n.x, _n[1] = point.n.y;
          kg = 1;
        }
        int _l;
        OMP(omp for schedule(static))
          for (_l = 0; _l < _n[0] + 2*GHOSTS; _l++) {
    	*_i = _l;
    	for (int _m = 0; _m < _n[1] + 2*GHOSTS; _m++) {
    	  *_j = _m;
    	  {...}
    	}
          }
      }
    }
    
    @def neighbor(o,p,q)
      ((Point){point.i+o, point.j+p, point.k+q, point.level, point.n _BLOCK_INDEX})
    @
    @def is_boundary(point) (point.i < GHOSTS || point.i >= point.n.x + GHOSTS ||
    			 point.j < GHOSTS || point.j >= point.n.y + GHOSTS ||
    			 point.k < GHOSTS || point.k >= point.n.z + GHOSTS)
    @
    
    #endif // dimension == 3
    
    extern double (* default_scalar_bc[]) (Point, Point, scalar, bool *);
    static double periodic_bc (Point point, Point neighbor, scalar s, bool * data);
    
    macro2 foreach_boundary (int b, Reduce reductions = None)
    {
      if (default_scalar_bc[b] != periodic_bc)
        foreach_boundary_dir (depth(), b, reductions)
          if (!is_boundary(point))
    	{...}
    }
    
    @define neighborp(k,l,o) neighbor(k,l,o)
    
    static void box_boundary_level (const Boundary * b, scalar * scalars, int l)
    {
      disable_fpe (FE_DIVBYZERO|FE_INVALID);
      for (int d = 0; d < 2*dimension; d++)
        if (default_scalar_bc[d] == periodic_bc)
          for (scalar s in scalars)
    	if (!is_constant(s) && s.block > 0) {
    	  if (is_vertex_scalar (s))
    	    s.boundary[d] = s.boundary_homogeneous[d] = NULL;
    	  else if (s.face) {
    	    vector v = s.v;
    	    v.x.boundary[d] = v.x.boundary_homogeneous[d] = NULL;
    	  }
    	}
      for (int bghost = 1; bghost <= BGHOSTS; bghost++)
        for (int d = 0; d < 2*dimension; d++) {
    
          scalar * list = NULL, * listb = NULL;
          for (scalar s in scalars)
    	if (!is_constant(s) && s.block > 0) {
    	  scalar sb = s;
    #if dimension > 1
    	  if (s.v.x.i >= 0) {
    	    // vector component
    	    int j = 0;
    	    while ((&s.v.x)[j].i != s.i) j++;
    	    sb = (&s.v.x)[(j - d/2 + dimension) % dimension];
    	  }
    #endif
    	  if (sb.i >= 0 && sb.boundary[d] && sb.boundary[d] != periodic_bc) {
    	    list = list_append (list, s);
    	    listb = list_append (listb, sb);
    	  }
    	}
          
          if (list) {
    	foreach_boundary_dir (l, d) {
    	  scalar s, sb;
    	  for (s,sb in list,listb) {
    	    if ((s.face && sb.i == s.v.x.i) || is_vertex_scalar (s)) {
    	      // normal component of face vector, or vertex scalar
    	      if (bghost == 1)
    		foreach_block()
    		  s[(ig + 1)/2,(jg + 1)/2,(kg + 1)/2] =
    		  sb.boundary[d] (point, neighborp(ig,jg,kg), s, NULL);
    	    }
    	    else
    	      // tangential component of face vector or centered
    	      foreach_block()
    		s[bghost*ig,bghost*jg,bghost*kg] =
    		sb.boundary[d] (neighborp((1 - bghost)*ig,
    					  (1 - bghost)*jg,
    					  (1 - bghost)*kg),
    				neighborp(bghost*ig,bghost*jg,bghost*kg),
    				s, NULL);
    	  }
    	}
    	free (list);
    	free (listb);
          }
        }
      enable_fpe (FE_DIVBYZERO|FE_INVALID);
    }
    
    /* Periodic boundaries */
    
    #if !_MPI
    
    #if dimension == 1
    
    static void periodic_boundary_level_x (const Boundary * b, scalar * list, int l)
    {
      scalar * list1 = NULL;
      for (scalar s in list)
        if (!is_constant(s) && s.block > 0 && s.boundary[right] == periodic_bc)
          list1 = list_add (list1, s);
      if (!list1)
        return;
    
      if (l == 0) {
        foreach_level(0, noauto)
          for (scalar s in list1)
    	for (int b = 0; b < s.block; b++) {
    	  scalar sb = {s.i + b};
    	  real v = sb[];
    	  foreach_neighbor()
    	    sb[] = v;
    	}
        free (list1);
        return;
      }
    
      Point point = {0};
      point.level = l < 0 ? depth() : l;
      SET_DIMENSIONS();
      for (int i = 0; i < GHOSTS; i++)
        for (scalar s in list1)
          for (int b = 0; b < s.block; b++) {
    	scalar sb = {s.i + b};
    	sb[i] = sb[i + point.n.x];
          }
      for (int i = point.n.x + GHOSTS; i < point.n.x + 2*GHOSTS; i++)
        for (scalar s in list1)
          for (int b = 0; b < s.block; b++) {
    	scalar sb = {s.i + b};
    	sb[i] = sb[i - point.n.x];
          }
      free (list1);
    }
        
    #else // dimension != 1
    
    @define VT _attribute[s.i].v.y
    
    foreach_dimension()
    static void periodic_boundary_level_x (const Boundary * b, scalar * list, int l)
    {
      scalar * list1 = NULL;
      for (scalar s in list)
        if (!is_constant(s) && s.block > 0) {
          if (s.face) {
    	scalar vt = VT;
    	if (vt.i >= 0 && vt.boundary[right] == periodic_bc)
    	  list1 = list_add (list1, s);
          }
          else if (s.boundary[right] == periodic_bc)
    	list1 = list_add (list1, s);
        }
      if (!list1)
        return;
    
      if (l == 0) {
        foreach_level (0, noauto)
          for (scalar s in list1)
    	for (int b = 0; b < s.block; b++) {
    	  scalar sb = {s.i + b};
    	  real v = sb[];
    	  foreach_neighbor()
    	    sb[] = v;
    	}
        free (list1);
        return;
      }
      
      OMP_PARALLEL() {
        Point point = {0};
        point.level = l < 0 ? depth() : l;
        SET_DIMENSIONS();
    #if dimension == 2  
        int j;
        OMP(omp for schedule(static))
          for (j = 0; j < point.n.y + 2*GHOSTS; j++) {
    	for (int i = 0; i < GHOSTS; i++)
    	  for (scalar s in list1)
    	    for (int b = 0; b < s.block; b++) {
    	      scalar sb = {s.i + b};
    	      sb[i,j] = sb[i + point.n.x,j];
    	    }
    	for (int i = point.n.x + GHOSTS; i < point.n.x + 2*GHOSTS; i++)
    	  for (scalar s in list1)
    	    for (int b = 0; b < s.block; b++) {
    	      scalar sb = {s.i + b};
    	      sb[i,j] = sb[i - point.n.x,j];
    	    }
          }
    #else // dimension == 3
        int j;
        OMP(omp for schedule(static))
          for (j = 0; j < point.n.y + 2*GHOSTS; j++)
    	for (int k = 0; k < point.n.z + 2*GHOSTS; k++) {
    	  for (int i = 0; i < GHOSTS; i++)
    	    for (scalar s in list1)
    	      for (int b = 0; b < s.block; b++) {
    		scalar sb = {s.i + b};
    		sb[i,j,k] = sb[i + point.n.x,j,k];
    	      }
    	  for (int i = point.n.x + GHOSTS; i < point.n.x + 2*GHOSTS; i++)
    	    for (scalar s in list1)
    	      for (int b = 0; b < s.block; b++) {
    		scalar sb = {s.i + b};
    		sb[i,j,k] = sb[i - point.n.x,j,k];
    	      }
    	}
    #endif
      }
      free (list1);
    }
    
    @undef VT
    
    #endif // dimension != 1  
      
    #endif // !_MPI
    
    void free_grid (void)
    {
      if (!grid)
        return;
      free_boundaries();
      Multigrid * m = multigrid;
      free (m->d);
      free (m->shift);
      free (m);
      grid = NULL;
    }
    
    int log_base2 (int n) {
      int m = n, r = 0;
      while (m > 1)
        m /= 2, r++;
      return (1 << r) < n ? r + 1 : r;
    }
     
    void init_grid (int n)
    {
      free_grid();
      Multigrid * m = qmalloc (1, Multigrid);
      grid = (Grid *) m;
      grid->depth = grid->maxdepth = log_base2(n/Dimensions.x);
      N = (1 << grid->depth)*Dimensions.x;
    #if !_MPI
      // mesh size
      grid->n = 1 << dimension*depth();
      foreach_dimension()
        grid->n *= Dimensions.x;
      grid->tn = grid->n;
    #endif // !_MPI
      // box boundaries
      Boundary * b = qcalloc (1, Boundary);
      b->level = box_boundary_level;
      add_boundary (b);
    #if _MPI
      Boundary * mpi_boundary_new();
      mpi_boundary_new();
    #else
      // periodic boundaries
      foreach_dimension() {
        Boundary * b = qcalloc (1, Boundary);
        b->level = periodic_boundary_level_x;
        add_boundary (b);
      }
    #endif
      // allocate grid: this must be after mpi_boundary_new() since this modifies depth()
      m->shift = qmalloc (depth() + 2, size_t);
      size_t totalsize = 0;
      for (int l = 0; l <= depth() + 1; l++) {
        m->shift[l] = totalsize;
        struct _Point point = { .level = l };
        SET_DIMENSIONS();
        size_t size = 1;
        foreach_dimension()
          size *= point.n.x + 2*GHOSTS;
        totalsize += size;
      }
      m->d = (char *) malloc(m->shift[depth() + 1]*datasize);
      reset (all, 0.);
    }
    
    void realloc_scalar (int size)
    {
      Multigrid * p = multigrid;
      datasize += size;
      qrealloc (p->d, p->shift[depth() + 1]*datasize, char);
    }
    
    #if _MPI
    int mpi_coords[dimension];
    #undef _I
    #undef _J
    #undef _K
    #define _I     (point.i - GHOSTS + mpi_coords[0]*(1 << point.level))
    #define _J     (point.j - GHOSTS + mpi_coords[1]*(1 << point.level))
    #define _K     (point.k - GHOSTS + mpi_coords[2]*(1 << point.level))
    #endif
    
    Point locate (double xp = 0, double yp = 0, double zp = 0)
    {
      Point point = {0};
      point.level = depth();
      SET_DIMENSIONS();
      point.level = -1;
    #if _MPI // fixme: can probably simplify with below
      point.i = (xp - X0)/L0*point.n.x*Dimensions.x + GHOSTS - mpi_coords[0]*point.n.x;
      if (point.i < GHOSTS || point.i >= point.n.x + GHOSTS)
        return point;
    #if dimension >= 2
      point.j = (yp - Y0)/L0*point.n.x*Dimensions.x + GHOSTS - mpi_coords[1]*point.n.x;
      if (point.j < GHOSTS || point.j >= point.n.y + GHOSTS)
        return point;
    #endif
    #if dimension >= 3
      point.k = (zp - Z0)/L0*point.n.x*Dimensions.x + GHOSTS - mpi_coords[2]*point.n.x;
      if (point.k < GHOSTS || point.k >= point.n.z + GHOSTS)
        return point;
    #endif  
    #else // !_MPI
      point.i = (xp - X0)/L0*point.n.x + GHOSTS;
      if (point.i < GHOSTS || point.i >= point.n.x + GHOSTS)
        return point;
    #if dimension >= 2
      point.j = (yp - Y0)/L0*point.n.x + GHOSTS;
      if (point.j < GHOSTS || point.j >= point.n.y + GHOSTS)
        return point;
    #endif
    #if dimension >= 3
      point.k = (zp - Z0)/L0*point.n.x + GHOSTS;
      if (point.k < GHOSTS || point.k >= point.n.z + GHOSTS)
        return point;
    #endif  
    #endif // !_MPI
      point.level = depth();
      return point;
    }
    
    #if _GPU
    # include "variables.h"
    #else
    # include "multigrid-common.h"
    #endif
    
    macro2 foreach_vertex (char flags = 0, Reduce reductions = None) {
      foreach_face_generic (reductions) {
        int ig = -1; NOT_UNUSED(ig);
    #if dimension > 1
        int jg = -1; NOT_UNUSED(jg);
    #endif
    #if dimension > 2
        int kg = -1; NOT_UNUSED(kg);
    #endif
        {...}
      }
    }
    
    #if dimension == 3
    macro foreach_edge (char flags = 0, Reduce reductions = None) {
      foreach_vertex (flags, reductions) {
        struct { int x, y, z; } _a = {point.i, point.j, point.k};
        foreach_dimension()
          if (_a.x < point.n.x + GHOSTS)
    	{...}
      }
    }
    #endif // dimension == 3
    
    ivec dimensions (int nx = 0, int ny = 0, int nz = 0)
    {
      if (nx != 0 || ny != 0 || nz != 0) {
        Dimensions.x = Dimensions_scale = max(nx, 1);
    #if dimension > 1
        Dimensions.y = max(ny, 1);
    #endif
    #if dimension > 2
        Dimensions.z = max(nz, 1);
    #endif
      }
      return Dimensions;
    }
    
    #if _MPI
    # include "multigrid-mpi.h"
    #else // !_MPI
    
    #if dimension == 1
    macro2 foreach_cell_multigrid()
    {
      for (int ox = 0; ox < Dimensions.x; ox++)
        foreach_cell() {
          point.i += ox*(1 << point.level);
          {...}
          point.i -= ox*(1 << point.level);
        }
    }
    #elif dimension == 2
    macro2 foreach_cell_multigrid()
    {
      for (int ox = 0; ox < Dimensions.x; ox++)
        for (int oy = 0; oy < Dimensions.y; oy++)
          foreach_cell() {
    	point.i += ox*(1 << point.level);
    	point.j += oy*(1 << point.level);
    	{...}
      	point.i -= ox*(1 << point.level);
    	point.j -= oy*(1 << point.level);
          }
    }
    #elif dimension == 3
    macro2 foreach_cell_multigrid()
    {
      for (int ox = 0; ox < Dimensions.x; ox++)
        for (int oy = 0; oy < Dimensions.y; oy++)
          for (int oz = 0; oz < Dimensions.z; oz++)
    	foreach_cell() {
    	  point.i += ox*(1 << point.level);
    	  point.j += oy*(1 << point.level);
    	  point.k += oz*(1 << point.level);
    	  {...}
    	  point.i -= ox*(1 << point.level);
    	  point.j -= oy*(1 << point.level);
    	  point.k -= oz*(1 << point.level);
    	}
    }
    #endif // dimension == 3
    
    #define foreach_cell() foreach_cell_multigrid()
    
    #endif // !_MPI