Task: Implement Cartoon2DBC.c for CactusNumerical/Cartoon2D thorn

thorn_name: CactusNumerical/Cartoon2D
src_filename: Cartoon2DBC.c
interface: # Interface definition for thorn Cartoon2D
# $Header$

implements: cartoon2d

INCLUDES HEADER: Cartoon2D_tensors.h in Cartoon2D_tensors.h
INCLUDES HEADER: Cartoon2D.h in Cartoon2D.h

USES INCLUDE: SpaceMask.h

# Function aliases:

CCTK_INT FUNCTION SymmetryRegister (CCTK_STRING IN sym_name)
REQUIRES FUNCTION SymmetryRegister

# which_faces        : array [N_FACES] 
# symmetry_zone_width: array [N_FACES]
CCTK_INT FUNCTION                                           \
    SymmetryRegisterGrid                                    \
        (CCTK_POINTER IN cctkGH,                            \
         CCTK_INT IN sym_handle,                            \
         CCTK_INT IN ARRAY which_faces,                     \
         CCTK_INT IN ARRAY symmetry_zone_width)
REQUIRES FUNCTION SymmetryRegisterGrid

CCTK_INT FUNCTION                                           \
    SymmetryRegisterGridInterpolator                        \
        (CCTK_POINTER IN cctkGH,                            \
         CCTK_INT IN sym_handle,                            \
         CCTK_INT CCTK_FPOINTER IN symmetry_interpolate     \
             (CCTK_POINTER_TO_CONST IN cctkGH,              \
              CCTK_INT IN N_dims,                           \
              CCTK_INT IN local_interp_handle,              \
              CCTK_INT IN param_table_handle,               \
              CCTK_INT IN coord_system_handle,              \
              CCTK_INT IN N_interp_points,                  \
              CCTK_INT IN interp_coords_type,               \
              CCTK_POINTER_TO_CONST ARRAY IN interp_coords, \
              CCTK_INT IN N_input_arrays,                   \
              CCTK_INT ARRAY IN input_array_indices,        \
              CCTK_INT IN N_output_arrays,                  \
              CCTK_INT ARRAY IN output_array_types,         \
              CCTK_POINTER ARRAY IN output_arrays,          \
              CCTK_INT IN faces))
REQUIRES FUNCTION SymmetryRegisterGridInterpolator

CCTK_INT FUNCTION                                           \
    SymmetryInterpolateFaces                                \
        (CCTK_POINTER_TO_CONST IN cctkGH,                   \
         CCTK_INT IN N_dims,                                \
         CCTK_INT IN local_interp_handle,                   \
         CCTK_INT IN param_table_handle,                    \
         CCTK_INT IN coord_system_handle,                   \
         CCTK_INT IN N_interp_points,                       \
         CCTK_INT IN interp_coords_type,                    \
         CCTK_POINTER_TO_CONST ARRAY IN interp_coords,      \
         CCTK_INT IN N_input_arrays,                        \
         CCTK_INT ARRAY IN input_array_indices,             \
         CCTK_INT IN N_output_arrays,                       \
         CCTK_INT ARRAY IN output_array_types,              \
         CCTK_POINTER ARRAY IN output_arrays,               \
         CCTK_INT IN faces)
REQUIRES FUNCTION SymmetryInterpolateFaces

CCTK_INT FUNCTION                                           \
    GetBoundarySpecification                                \
        (CCTK_INT IN size,                                  \
         CCTK_INT OUT ARRAY nboundaryzones,                 \
         CCTK_INT OUT ARRAY is_internal,                    \
         CCTK_INT OUT ARRAY is_staggered,                   \
         CCTK_INT OUT ARRAY shiftout)
REQUIRES FUNCTION GetBoundarySpecification



CCTK_INT FUNCTION Boundary_SelectedGVs(CCTK_POINTER_TO_CONST IN GH, \
  CCTK_INT IN array_size, CCTK_INT ARRAY OUT var_indicies, \
  CCTK_INT ARRAY OUT faces, CCTK_INT ARRAY OUT boundary_widths, \
  CCTK_INT ARRAY OUT table_handles, CCTK_STRING IN bc_name)
REQUIRES FUNCTION Boundary_SelectedGVs

private:

int excision_variables type=SCALAR
{
  excision_active
  old_mask_vi
  new_mask_vi
  new_excision_field
  new_excision_descriptor
} "Internal variables to store information about excision"

schedule: # Schedule definitions for thorn Cartoon2D
# $Header$

if (cartoon_active)
{
  STORAGE: excision_variables

  schedule Cartoon2D_CheckTensorTypes at CCTK_PARAMCHECK
  {
    LANG: C
    OPTIONS: meta
  } "Check tensor type definitions for consistency"

  schedule Cartoon2D_InitExcisionVars at CCTK_BASEGRID
  {
    LANG: C
    OPTIONS: global
  } "Initialize the excision variables"

  if (allow_grid_resize)
  {
    schedule Cartoon2D_SetGrid at CCTK_RECOVER_PARAMETERS
    {
      LANG: C
    } "Adjust grid sizes"
  }

  schedule Cartoon2D_RegisterSymmetries in SymmetryRegister
  {
    LANG: C
    OPTIONS: global
  } "Register symmetry boundaries"

  schedule Cartoon2D_CheckParameters at CCTK_PARAMCHECK
  {
    LANG: C
    OPTIONS: meta
  } "Check Cartoon2D parameters"

  # Apply cartoon boundary conditions after physical boundaries:
  schedule Cartoon_ApplyBoundaries in BoundaryConditions after Boundary_ApplyPhysicalBCs
  {
    LANG: C
  } "Apply Cartoon boundary conditions"
}

param: # Parameter definitions for thorn Cartoon2D
# $Header$

private:

BOOLEAN cartoon_active "Activate cartoon boundary condition"
{
} "no"

BOOLEAN verbose "Verbose information"
{
} "no"

BOOLEAN stencil "Use custom 2D stencil if available"
{
} "yes"

INT order "Cartoon's interpolation order"
{
  1:5 :: "From linear to fifth order."
} 4

INT eno_order "The interpolation order applied to the ENO interpolator"
{
  1:5 :: "From linear to fifth order."
} 4

BOOLEAN allow_grid_resize "Allow grid to be resized in a cartoon-compatible way"
{
} "no"

BOOLEAN old_excision "Are we doing excision based on the old style mask?"
{
} "no"

BOOLEAN new_excision "Are we doing excision based on the new style mask?"
{
} "no"

STRING old_style_excision_var "The variable to be checked for old style excision"
{
  ".*" :: "Expected to be \'Spacemask::emask\'"
} ""

STRING new_style_excision_var "The variable to be checked for new style excision"
{
  ".*" :: "Expected to be \'Spacemask::space_mask\'"
} ""

STRING new_mask_field_name "The name of the field that describes excision for the new mask"
{
  ".*" :: "Could be anything"
} ""

STRING new_mask_excised_name "The name of the descriptor that says the point is excised for the new mask"
{
  ".*" :: "Could be anything"
} ""

configuration: # Configuration definition for thorn Cartoon2D
# $Header$

REQUIRES SpaceMask

context: SymInterp.c:
```
/* $Header$ */

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cctk.h"
#include "cctk_Arguments.h"
#include "util_ErrorCodes.h"
#include "util_Table.h"

#include "Cartoon2D.h"



/*
 * NOTE:
 *
 * The contents of this file have been largely copied from the thorn
 * AEIDevelopment/RotatingSymmetry90.  Please do not invest much
 * effort in changing this file here -- changes to its ancestor should
 * be easy to port over.  The only difference between this file and
 * its ancestor are a few lines in the coordinate rotation routines.
 * I hope that a generic tensor infrastructure will come up, and then
 * this duplication will go away.
 *
 * Erik Schnetter, 2004-05-22
 */




/* This is pretty hard coded into all the tensor types and cannot be
   changed easily.  */
#define DIM 3 /* spatial dimension */



/* These can be increased if necessary, but not all necessary tensor
   types may be defined .  */
#define MAX_RANK                9 /* maximum tensor rank */
#define MAX_TIME_LEVELS         3 /* maximum number of time levels */
#define MAX_SPATIAL_DERIV_ORDER 2 /* maximum spatial derivative order */
#define MAX_TIME_DERIV_ORDER    2 /* maximum time derivative order */



/* A tensor description */
struct tensor
{
  const char * name;            /* description */
  int dim;
  int rank;
  int ncomps;                   /* dim^rank */
  int * restrict vars;          /* map component to variable */
  int * restrict parity;        /* parity for the above */
  int nvars;                    /* depends on symmetries */
  int * restrict comps;         /* map variable to component */
};



/* A scalar */
static int scalar_vars[] = {
  0
};
static int scalar_parity[] = {
  +1
};
static int scalar_comps[] = {
  0
};
static struct tensor scalar = {
  "scalar",
  3, 0, 1, scalar_vars, scalar_parity, 1, scalar_comps
};



/* A vector */
static int vector_vars[] = {
  0,1,2
};
static int vector_parity[] = {
  +1,+1,+1
};
static int vector_comps[] = {
  0,1,2
};
static struct tensor vector = {
  "vector",
  3, 1, 3, vector_vars, vector_parity, 3, vector_comps
};



/* A second rank tensor without symmetries */
static int tensor_vars[] = {
  0,1,2,   3,4,5,   6,7,8
};
static int tensor_parity[] = {
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1
};
static int tensor_comps[] = {
  0,1,2,   3,4,5,   6,7,8
};
static struct tensor tensor = {
  "tensor",
  3, 2, 9, tensor_vars, tensor_parity, 9, tensor_comps
};



/* A symmetric second rank tensor */
static int symmtensor_vars[] = {
  0,1,2,   1,3,4,   2,4,5
};
static int symmtensor_parity[] = {
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1
};
static int symmtensor_comps[] = {
  0,1,2,   4,5,   8
};
static struct tensor symmtensor = {
  "symmetric tensor T_(ij)",
  3, 2, 9, symmtensor_vars, symmtensor_parity, 6, symmtensor_comps
};



/* A third rank tensor with symmetry in the first two indices */
static int symmtensor3a_vars[] = {
   0, 1, 2,
   3, 4, 5,
   6, 7, 8,
  
   3, 4, 5,
   9,10,11,
  12,13,14,
  
   6, 7, 8,
  12,13,14,
  15,16,17
};
static int symmtensor3a_parity[] = {
  +1,+1,+1,
  +1,+1,+1,
  +1,+1,+1,
  
  +1,+1,+1,
  +1,+1,+1,
  +1,+1,+1,
  
  +1,+1,+1,
  +1,+1,+1,
  +1,+1,+1,
};
static int symmtensor3a_comps[] = {
   0, 1, 2,
   3, 4, 5,
   6, 7, 8,
  
  12,13,14,
  15,16,17,
  
  24,25,26
};
static struct tensor symmtensor3a = {
  "symmetric tensor T_(ij)k",
  3, 3, 27, symmtensor3a_vars, symmtensor3a_parity, 18, symmtensor3a_comps
};



/* A third rank tensor with symmetry in the last two indices */
static int symmtensor3b_vars[] = {
   0, 1, 2,    1, 3, 4,    2, 4, 5,
   6, 7, 8,    7, 9,10,    8,10,11,
  12,13,14,   13,15,16,   14,16,17
};
static int symmtensor3b_parity[] = {
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1
};
static int symmtensor3b_comps[] = {
   0, 1, 2,    4, 5,    8,
   9,10,11,   13,14,   17,
  18,19,20,   22,23,   26
};
static struct tensor symmtensor3b = {
  "symmetric tensor T_i(jk)",
  3, 3, 27, symmtensor3b_vars, symmtensor3b_parity, 18, symmtensor3b_comps
};



/* A fourth rank tensor with symmetries both in its first and last two
   indices */
static int symmtensor4_vars[] = {
   0, 1, 2,    1, 3, 4,    2, 4, 5,
   6, 7, 8,    7, 9,10,    8,10,11,
  12,13,14,   13,15,16,   14,16,17,
  
   6, 7, 8,    7, 9,10,    8,10,11,
  18,19,20,   19,21,22,   20,22,23,
  24,25,26,   25,27,28,   26,28,29,
  
  12,13,14,   13,15,16,   14,16,17,
  24,25,26,   25,27,28,   26,28,29,
  30,31,32,   31,33,34,   32,34,35
};
static int symmtensor4_parity[] = {
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1,
  +1,+1,+1,   +1,+1,+1,   +1,+1,+1
};
static int symmtensor4_comps[] = {
   0, 1, 2,    4, 5,    8,
   9,10,11,   13,14,   17,
  18,19,20,   22,23,   26,
  
  36,37,38,   40,41,   44,
  45,46,47,   49,50,   53,
  
  72,73,74,   76,77,   80
};
static struct tensor symmtensor4 = {
  "symmetric tensor T_(ij)(kl)",
  3, 4, 81, symmtensor4_vars, symmtensor4_parity, 36, symmtensor4_comps
};


static int
ipow (int base, int expo)
{
  int res;
  assert (expo >= 0);
  res = expo & 1 ? base : 1;
  while (expo >>= 1)
  {
    base *= base;
    if (expo & 1) res *= base;
  }
  return res;
}



static int
ilog (int res, int const base)
{
  int expo;
  assert (base > 0);
  assert (res >= 0);
  for (expo = 0; res > 0; ++ expo) {
    res /= base;
  }
  return expo;
}



/* Ensure that all tensor declarations are internally consistent */
static void
CheckTensorType (struct tensor const * restrict const atensor)
{
  int i, n;
  assert (atensor->name);
  assert (atensor->dim>=0);
  assert (atensor->rank>=0);
  assert (atensor->ncomps>=0);
  assert (atensor->ncomps == floor(pow(atensor->dim, atensor->rank) + 0.5));
  assert (atensor->nvars>=0 && atensor->nvars<=atensor->ncomps);
  assert (atensor->vars);
  for (i=0; i<atensor->ncomps; ++i) {
    assert (atensor->vars[i]>=0 && atensor->vars[i]<atensor->nvars);
    assert (abs(atensor->parity[i]) <= 1);
  }
  assert (atensor->comps);
  for (n=0; n<atensor->nvars; ++n) {
    assert (atensor->comps[n]>=0 && atensor->comps[n]<atensor->ncomps);
    assert (atensor->vars[atensor->comps[n]] == n);
  }
}

void
Cartoon2D_CheckTensorTypes (CCTK_ARGUMENTS)
{
  int gi;
  
  /* Check all internal tensor type definitions */
  CheckTensorType (&scalar);
  CheckTensorType (&vector);
  CheckTensorType (&tensor);
  CheckTensorType (&symmtensor);
  CheckTensorType (&symmtensor3a);
  CheckTensorType (&symmtensor3b);
  CheckTensorType (&symmtensor4);
  
  /* Check tensor types of all groups */
  for (gi=0; gi<CCTK_NumGroups(); ++gi) {
    
    char tensortypealias[100];
    int numvars, firstvar;
    int table;
    int ierr;
    
    numvars = CCTK_NumVarsInGroupI(gi);
    if (numvars == 0) continue;
    assert (numvars>0);
    firstvar = CCTK_FirstVarIndexI(gi);
    assert (firstvar>=0);
    table = CCTK_GroupTagsTableI(gi);
    assert (table>=0);
    
    ierr = Util_TableGetString
      (table, sizeof tensortypealias, tensortypealias, "tensortypealias");
    if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
      char * groupname = CCTK_GroupName(gi);
      assert (groupname);
      CCTK_VWarn (2, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Tensor type alias not declared for group \"%s\" -- assuming a scalar",
                  groupname);
      free (groupname);
      strcpy (tensortypealias, "");
    } else if (ierr<0) {
      char * groupname = CCTK_GroupName(gi);
      assert (groupname);
      CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Error in tensor type alias declaration for group \"%s\"",
                  groupname);
      free (groupname);
    }
    
    if (CCTK_EQUALS (tensortypealias, "")) {
      /* do nothing */
    } else if (CCTK_EQUALS (tensortypealias, "scalar")) {
      /* scalar */
      if (numvars != 1) {
        char * groupname = CCTK_GroupName(gi);
        assert (groupname);
        CCTK_VWarn (3, __LINE__, __FILE__, CCTK_THORNSTRING,
                    "Group \"%s\" has the tensor type alias \"scalar\", but contains more than 1 element",
                    groupname);
        free (groupname);
      }
    } else if (CCTK_EQUALS (tensortypealias, "4scalar")) {
      /* 4-scalar */
      if (numvars != 1) {
        char * groupname = CCTK_GroupName(gi);
        assert (groupname);
        CCTK_VWarn (3, __LINE__, __FILE__, CCTK_THORNSTRING,
                    "Group \"%s\" has the tensor type alias \"4scalar\", but contains more than 1 element",
                    groupname);
        free (groupname);
      }
    } else if (CCTK_EQUALS (tensortypealias, "u")
               || CCTK_EQUALS (tensortypealias, "d"))
    {
      /* vector */
      assert (numvars == DIM);
    } else if (CCTK_EQUALS (tensortypealias, "4u")
               || CCTK_EQUALS (tensortypealias, "4d"))
    {
      /* 4-vector */
      assert (numvars == DIM+1);
    } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
               || CCTK_EQUALS (tensortypealias, "dd_sym")) {
      /* symmetric tensor */
      assert (numvars == DIM*(DIM+1)/2);
    } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
               || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
      /* symmetric 4-tensor */
      assert (numvars == (DIM+1)*(DIM+2)/2);
    } else {
      char * groupname = CCTK_GroupName(gi);
      assert (groupname);
      CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Illegal tensor type alias for group \"%s\"",
                  groupname);
      free (groupname);
    }
    
  }
}
  


/* Symmetry interpolation */
CCTK_INT
Cartoon2D_SymmetryInterpolate (CCTK_POINTER_TO_CONST const cctkGH_,
                               CCTK_INT const N_dims,
                               CCTK_INT const local_interp_handle,
                               CCTK_INT const param_table_handle,
                               CCTK_INT const coord_system_handle,
                               CCTK_INT const N_interp_points,
                               CCTK_INT const interp_coords_type,
                               CCTK_POINTER_TO_CONST const interp_coords[],
                               CCTK_INT const N_input_arrays,
                               CCTK_INT const input_array_indices[],
                               CCTK_INT const N_output_arrays,
                               CCTK_INT const output_array_types[],
                               CCTK_POINTER const output_arrays[],
                               CCTK_INT const faces)
{
  CCTK_POINTER new_interp_coords[DIM];
  CCTK_POINTER * restrict new_output_arrays;
  CCTK_INT new_faces;
  CCTK_INT * restrict input_array_time_levels;
  CCTK_INT * restrict operand_indices;
  CCTK_INT * restrict operation_codes;
  CCTK_INT * restrict time_deriv_order;
  CCTK_INT * restrict output_array_indices;
  
  struct tensor const * restrict * restrict thetensor
    [MAX_TIME_DERIV_ORDER+1][MAX_SPATIAL_DERIV_ORDER+1][MAX_TIME_LEVELS+1];
  int * restrict * restrict thevars
    [MAX_TIME_DERIV_ORDER+1][MAX_SPATIAL_DERIV_ORDER+1][MAX_TIME_LEVELS+1];
  int * restrict thebase;
  int * restrict thevar;
  
  int m;                        /* output array */
  int n;                        /* point */
  int i;                        /* var */
  int d;                        /* dimension */
  int p;                        /* number of time derivatives */
  int q;                        /* number of spatial derivatives */
  int tl;                       /* time level */
  int r;                        /* rank */
  
  int iret;                     /* interpolator return value */
  int ierr;
  
  /* Check arguments */
  assert (N_dims == DIM);
  assert (N_interp_points >= 0);
  assert (interp_coords_type >= 0);
  for (d=0; d<DIM; ++d) {
    assert (N_interp_points == 0 || interp_coords[d]);
  }
  assert (N_output_arrays >= 0);
  
  /* Coordinates must be CCTK_REAL */
  assert (interp_coords_type == CCTK_VARIABLE_REAL);
  for (m=0; m<N_output_arrays; ++m) {
    assert (output_array_types[m] == CCTK_VARIABLE_REAL);
  }
  
  
  
  /* Claim faces */
  assert (faces & (1 << 0));
  assert (faces & (1 << 2));
  assert (faces & (1 << 3));
  new_faces = faces;
  new_faces &= ~ (1 << 0);
  new_faces &= ~ (1 << 2);
  new_faces &= ~ (1 << 3);
  
  
  
  /* Copy coordinates */
  for (d=0; d<DIM; ++d) {
    new_interp_coords[d] = malloc (N_interp_points * sizeof(CCTK_REAL));
    assert (new_interp_coords[d] || N_interp_points==0);
  }
  
  /* Fold coordinates */
  for (n=0; n<N_interp_points; ++n) {
    /* Rotate the point onto the positive x axis */
    CCTK_REAL x = ((CCTK_REAL const *)interp_coords[0])[n];
    CCTK_REAL y = ((CCTK_REAL const *)interp_coords[1])[n];
    CCTK_REAL z = ((CCTK_REAL const *)interp_coords[2])[n];
    CCTK_REAL const r = sqrt(x*x + y*y);
    /* CCTK_REAL const w = atan2(y, x); */
    ((CCTK_REAL *)new_interp_coords[0])[n] = r;
    ((CCTK_REAL *)new_interp_coords[1])[n] = 0;
    ((CCTK_REAL *)new_interp_coords[2])[n] = z;
  }
  
  
  
  /* Allocate new output arrays */
  new_output_arrays = malloc (N_output_arrays * sizeof *new_output_arrays);
  assert (new_output_arrays);
  for (m=0; m<N_output_arrays; ++m) {
    new_output_arrays[m] = malloc (N_interp_points * sizeof(CCTK_REAL));
    assert (new_output_arrays[m] || N_interp_points==0);
  }
  
  
  
  /* Recursive call */
  iret = SymmetryInterpolateFaces
    (cctkGH_, N_dims,
     local_interp_handle, param_table_handle, coord_system_handle,
     N_interp_points, interp_coords_type, new_interp_coords,
     N_input_arrays, input_array_indices,
     N_output_arrays, output_array_types, new_output_arrays,
     new_faces);
  
  
  
  /* Free coordinates */
  for (d=0; d<DIM; ++d) {
    free (new_interp_coords[d]);
  }
  
  
  
  /* Find output variable indices */
  input_array_time_levels
    = malloc (N_input_arrays * sizeof *input_array_time_levels);
  assert (input_array_time_levels);
  ierr = Util_TableGetIntArray
    (param_table_handle, N_input_arrays,
     input_array_time_levels, "input_array_time_levels");
  if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
    for (m=0; m<N_input_arrays; ++m) {
      input_array_time_levels[m] = 0; /* time level is 0 */
    }
  } else {
    assert (ierr == N_input_arrays);
  }
  
  operand_indices = malloc (N_output_arrays * sizeof *operand_indices);
  assert (operand_indices);
  ierr = Util_TableGetIntArray
    (param_table_handle, N_output_arrays, operand_indices, "operand_indices");
  if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
    assert (N_output_arrays == N_input_arrays);
    for (m=0; m<N_output_arrays; ++m) {
      operand_indices[m] = m;   /* output index equals input index */
    }
  } else {
    assert (ierr == N_output_arrays);
  }
  
  operation_codes = malloc (N_output_arrays * sizeof *operation_codes);
  assert (operation_codes);
  ierr = Util_TableGetIntArray
    (param_table_handle, N_output_arrays, operation_codes, "operation_codes");
  if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
    assert (N_output_arrays == N_input_arrays); /* why? */
    for (m=0; m<N_output_arrays; ++m) {
      operation_codes[m] = 0;     /* do not take derivatives */
    }
  } else {
    assert (ierr == N_output_arrays);
  }
    for (m=0; m<N_output_arrays; ++m) {
    assert (operation_codes[m] >= 0
            && operation_codes[m] < ipow (10, MAX_SPATIAL_DERIV_ORDER));
  }
  
  time_deriv_order = malloc (N_output_arrays * sizeof *time_deriv_order);
  assert (time_deriv_order);
  ierr = Util_TableGetIntArray
    (param_table_handle, N_output_arrays, time_deriv_order, "time_deriv_order");
  if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
    for (m=0; m<N_output_arrays; ++m) {
      time_deriv_order[m] = 0;  /* do not take time derivatives */
    }
  } else {
    assert (ierr == N_output_arrays);
  }
  for (m=0; m<N_output_arrays; ++m) {
    assert (time_deriv_order[m] >= 0
            && time_deriv_order[m] <= MAX_TIME_DERIV_ORDER);
  }

  output_array_indices
    = malloc (N_output_arrays * sizeof *output_array_indices);
  assert (output_array_indices);
  for (m=0; m<N_output_arrays; ++m) {
    assert (operand_indices[m]>=0 && operand_indices[m]<N_input_arrays);
    output_array_indices[m] = input_array_indices[operand_indices[m]];
    assert (output_array_indices[m]>=0
            && output_array_indices[m]<CCTK_NumVars());
  }
  
  
  
  /* Map Cactus variables to tensor objects */
  for (p=0; p<=MAX_TIME_DERIV_ORDER; ++p) {
    for (q=0; q<=MAX_SPATIAL_DERIV_ORDER; ++q) {
      for (tl=0; tl<=MAX_TIME_LEVELS; ++tl) {
        thetensor[p][q][tl]
          = malloc (CCTK_NumVars() * sizeof *thetensor[p][q][tl]);
        assert (thetensor[p][q][tl]);
        thevars[p][q][tl] = malloc (CCTK_NumVars() * sizeof *thevars[p][q][tl]);
        assert (thevars[p][q][tl]);
        for (n=0; n<CCTK_NumVars(); ++n) {
          thetensor[p][q][tl][n] = NULL;
          thevars[p][q][tl][n] = NULL;
        }
      }
    }
  }
  
  /* Map output arrays to the base Cactus variable (i.e. the first
     component in the tensor objects) */
  thebase = malloc (N_output_arrays * sizeof *thebase);
  assert (thebase);
  thevar = malloc (N_output_arrays * sizeof *thevar);
  assert (thevar);
  
  for (m=0; m<N_output_arrays; ++m) {
    
    int vi, gi;
    int numvars, firstvar;
    int table;
    
    char tensortypealias[100];
    
    struct tensor const * tensortype;
    int basevar;
    int var;
    int indices[MAX_RANK+1];
    int oldrank;
    int num_time_derivs;
    int num_derivs;
    int time_level;
    
    /* Get some variable information */
    vi = output_array_indices[m];
    assert (vi>=0 && vi<CCTK_NumVars());
    gi = CCTK_GroupIndexFromVarI (vi);
    assert (gi>=0 && gi<CCTK_NumGroups());
    numvars = CCTK_NumVarsInGroupI(gi);
    assert (numvars>0);
    firstvar = CCTK_FirstVarIndexI(gi);
    assert (firstvar>=0);
    table = CCTK_GroupTagsTableI(gi);
    assert (table>=0);
    
    /* Get the tensor type alias */
    ierr = Util_TableGetString
      (table, sizeof tensortypealias, tensortypealias, "tensortypealias");
    if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
      char * groupname = CCTK_GroupName(gi);
      assert (groupname);
      CCTK_VWarn (1, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Tensor type alias not declared for group \"%s\" -- assuming a scalar",
                  groupname);
      free (groupname);
      strcpy (tensortypealias, "scalar");
    } else if (ierr<0) {
      char * groupname = CCTK_GroupName(gi);
      assert (groupname);
      CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Error in tensor type alias declaration for group \"%s\"",
                  groupname);
      free (groupname);
    }
    
    /* Find the tensor type */
    tensortype = NULL;
    basevar = -1;
    var = -1;
    if (CCTK_EQUALS (tensortypealias, "scalar")) {
      /* scalar */
      if (numvars != 1) {
        char * groupname = CCTK_GroupName(gi);
        assert (groupname);
        CCTK_VWarn (2, __LINE__, __FILE__, CCTK_THORNSTRING,
                    "Group \"%s\" has the tensor type alias \"scalar\", but contains more than 1 element",
                    groupname);
        free (groupname);
      }
      tensortype = &scalar;
      basevar = vi;
      var = 0;
    } else if ((CCTK_EQUALS (tensortypealias, "u")) ||(CCTK_EQUALS (tensortypealias, "d"))) {
      /* vector */
      assert (numvars == DIM);
      tensortype = &vector;
      basevar = firstvar;
      var = vi - firstvar;
    } else if (CCTK_EQUALS (tensortypealias, "4u")
               || CCTK_EQUALS (tensortypealias, "4d"))
    {
      /* 4-vector */
      assert (numvars == DIM+1);
      if (vi == firstvar) {
        /* temporal component */
        int const off = 0;
        tensortype = &scalar;
        basevar = firstvar + off;
        var = vi - basevar;
      } else {
        /* spatial components */
        int const off = 1;
        tensortype = &vector;
        basevar = firstvar + off;
        var = vi - basevar;
      }
    } else if (CCTK_EQUALS (tensortypealias, "dd_sym")) {
      /* symmetric tensor */
      assert (numvars == DIM*(DIM+1)/2);
      tensortype = &symmtensor;
      basevar = firstvar;
      var = vi - firstvar;
    } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
               || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
      /* symmetric 4-tensor */
      assert (numvars == (DIM+1)*(DIM+2)/2);
      if (vi == firstvar) {
        /* temporal-temporal component */
        int const off = 0;
        tensortype = &scalar;
        basevar = firstvar + off;
        var = vi - basevar;
      } else if (vi < firstvar+DIM+1) {
        /* temporal-spatial components */
        int const off = 1;
        tensortype = &vector;
        basevar = firstvar + off;
        var = vi - basevar;
      } else {
        /* spatial-spatial components */
        int const off = DIM+1;
        tensortype = &symmtensor;
        basevar = firstvar + off;
        var = vi - basevar;
      }
    } else {
      char * groupname = CCTK_GroupName(gi);
      assert (groupname);
      CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Illegal tensor type alias for group \"%s\"",
                  groupname);
      free (groupname);
    }
    thebase[m] = basevar;
    
    /* Find my component */
    {
      int component;
      assert (var>=0 && var<tensortype->nvars);
      component = tensortype->comps[var];
      assert (tensortype->rank>=0 && tensortype->rank<=MAX_RANK);
      for (r=tensortype->rank-1; r>=0; --r) {
        indices[r] = component % tensortype->dim;
        assert (indices[r]>=0 && indices[r]<tensortype->dim);
        component /= tensortype->dim;
      }
      assert (component == 0);
      oldrank = tensortype->rank;
    }
        
    /* Take time derivative order (i.e., time derivatives) into account */
    num_time_derivs = time_deriv_order[m];
    assert (num_time_derivs>=0 && num_time_derivs<=MAX_TIME_DERIV_ORDER);
    
    /* Take operation code (i.e., spatial derivatives) into account */
    num_derivs = ilog (operation_codes[m], 10);
    assert (num_derivs>=0 && num_derivs<=MAX_SPATIAL_DERIV_ORDER);
    
    /* Get the time level */
    time_level = input_array_time_levels[operand_indices[m]];
    assert (time_level>=0 && time_level<=MAX_TIME_LEVELS);
    
    if (tensortype == &scalar) {
      switch (num_derivs) {
      case 0: break;
      case 1: tensortype = &vector; break;
      case 2: tensortype = &symmtensor; break;
      default: assert (0);
      }
    } else if (tensortype == &vector) {
      switch (num_derivs) {
      case 0: break;
      case 1: tensortype = &tensor; break;
      case 2: tensortype = &symmtensor3b; break;
      default: assert (0);
      }
    } else if (tensortype == &symmtensor) {
      switch (num_derivs) {
      case 0: break;
      case 1: tensortype = &symmtensor3a; break;
      case 2: tensortype = &symmtensor4; break;
      default: assert (0);
      }
    } else {
      assert (0);
    }
    
    /* Find the additional indices */
    {
      int code;
      assert (tensortype->rank>=0 && tensortype->rank<=MAX_RANK);
      assert (tensortype->rank >= oldrank);
      code = operation_codes[m];
      for (r=oldrank; r<tensortype->rank; ++r) {
        const int thedir = code % 10 - 1;
        code /= 10;
        assert (thedir>=0 && thedir<tensortype->dim);
        indices[r] = thedir;
      }
    }
    
    /* Re-calculate component */
    {
      int component = 0;
      for (r=0; r<tensortype->rank; ++r) {
        component = component * tensortype->dim + indices[r];
      }
      assert (component>=0 && component<tensortype->ncomps);
      var = tensortype->vars[component];
      assert (var>=0 && var<tensortype->nvars);
      thevar[m] = var;
    }
    
    /* Create or cross-check the tensor object */
    if (! thetensor[num_time_derivs][num_derivs][time_level][basevar]) {
      thetensor[num_time_derivs][num_derivs][time_level][basevar] = tensortype;
      assert (! thevars[num_time_derivs][num_derivs][time_level][basevar]);
      thevars[num_time_derivs][num_derivs][time_level][basevar]
        = malloc (tensortype->nvars
                  * sizeof *thevars[num_time_derivs][num_derivs][time_level][basevar]);
      assert (thevars[num_time_derivs][num_derivs][time_level][basevar]);
      for (i=0; i<tensortype->nvars; ++i) {
        thevars[num_time_derivs][num_derivs][time_level][basevar][i] = -1;
      }
    }
    assert (thetensor[num_time_derivs][num_derivs][time_level][basevar]
            == tensortype);
    assert (thevars[num_time_derivs][num_derivs][time_level][basevar]);
    /* This does not hold if the caller requests the same
       interpolation to be done into different output arrays.  This
       may happen e.g. when CarpetInterp needs to differentiate in
       time.  This is arguably a performance bug in CarpetInterp.  */
    /* See whether this goes away now.  */
    assert (thevars[num_time_derivs][num_derivs][time_level][basevar][var]
            == -1);
    thevars[num_time_derivs][num_derivs][time_level][basevar][var] = m;
    
  } /* for m */
  
  
  
  /* Loop over all output arrays */
  for (m=0; m<N_output_arrays; ++m) {
    
    int num_time_derivs;
    int num_derivs;
    int time_level;
    int basevar;
    struct tensor const * restrict tensortype;
    int const * restrict vars;
    int var;
    int indices[MAX_RANK+1];
    
    /* Take time derivative order (i.e., time derivatives) into account */
    num_time_derivs = time_deriv_order[m];
    assert (num_time_derivs>=0 && num_time_derivs<=MAX_TIME_DERIV_ORDER);
    
    /* Take operation code (i.e., spatial derivatives) into account */
    num_derivs = ilog (operation_codes[m], 10);
    assert (num_derivs>=0 && num_derivs<=MAX_SPATIAL_DERIV_ORDER);
    
    /* Get the time level */
    time_level = input_array_time_levels[operand_indices[m]];
    assert (time_level>=0 && time_level<=MAX_TIME_LEVELS);
        
    /* Get the tensor type */
    basevar = thebase[m];
    assert (basevar>=0 && basevar<=CCTK_NumVars());
    tensortype = thetensor[num_time_derivs][num_derivs][time_level][basevar];
    assert (tensortype);
    vars = thevars[num_time_derivs][num_derivs][time_level][basevar];
    assert (vars);
    var = thevar[m];
    assert (var>=0 && var<tensortype->nvars);
    
    /* Transform into indices */
    {
      int component = tensortype->comps[var];
      assert (tensortype->rank>=0 && tensortype->rank<=MAX_RANK);
      for (r=tensortype->rank-1; r>=0; --r) {
        indices[r] = component % tensortype->dim;
        assert (indices[r]>=0 && indices[r]<tensortype->dim);
        component /= tensortype->dim;
      }
    }
    
    /* Loop over all grid points */
    for (n=0; n<N_interp_points; ++n) {
      
      CCTK_REAL const x = ((CCTK_REAL const *)interp_coords[0])[n];
      CCTK_REAL const y = ((CCTK_REAL const *)interp_coords[1])[n];
/*       CCTK_REAL const z = ((CCTK_REAL const *)interp_coords[2])[n]; */
      
      CCTK_REAL const r = sqrt(x*x + y*y);
/*       CCTK_REAL const w = atan2(y, x); */
      
/*       CCTK_REAL const cw = cos(w); */
/*       CCTK_REAL const sw = sin(w); */
      CCTK_REAL const cw = (x+1.0e-10)/(r+1.0e-10);
      CCTK_REAL const sw = y/(r+1.0e-10);
      
      CCTK_REAL rot[3][3];
      rot[0][0] =  cw;
      rot[0][1] = -sw;
      rot[0][2] =  0;
      rot[1][0] =  sw;
      rot[1][1] =  cw;
      rot[1][2] =  0;
      rot[2][0] =  0;
      rot[2][1] =  0;
      rot[2][2] =  1;
      
      CCTK_REAL myval = 0;
      
      int thecomponent;
      for (thecomponent=0; thecomponent<tensortype->ncomps; ++thecomponent) {
        int theindices[MAX_RANK+1];
        int thevar;
        int theparity;
        int mm;
        assert (tensortype->rank<MAX_RANK+1);
        {
          int r;
          int component = thecomponent;
          for (r=tensortype->rank-1; r>=0; --r) {
            theindices[r] = component % tensortype->dim;
            assert (theindices[r]>=0 && theindices[r]<tensortype->dim);
            component /= tensortype->dim;
          }
          assert (component == 0);
        }
        theparity = tensortype->parity[thecomponent];
        assert (abs(theparity) <= 1);
        thevar = tensortype->vars[thecomponent];
        assert (thevar>=0 && thevar<tensortype->nvars);
        mm = vars[thevar];
        assert (mm>=0 && mm<N_output_arrays);
        {
          int r;
          CCTK_REAL val
            = theparity * ((CCTK_REAL const *)new_output_arrays[mm])[n];
          for (r=0; r<tensortype->rank; ++r) {
            val *= rot[indices[r]][theindices[r]];
          }
          myval += val;
        }
      }
      
      ((CCTK_REAL *)output_arrays[m])[n] = myval;
      
    } /* for n */
    
  } /* for m */
  
  
  
  /* Free output variable descriptions */
  for (m=0; m<N_output_arrays; ++m) {
    free (new_output_arrays[m]);
  }
  free (new_output_arrays);
  
  free (input_array_time_levels);
  free (operand_indices);
  free (operation_codes);
  free (output_array_indices);
  
  
  for (p=0; p<=MAX_TIME_DERIV_ORDER; ++p) {
    for (q=0; q<=MAX_SPATIAL_DERIV_ORDER; ++q) {
      for (tl=0; tl<=MAX_TIME_LEVELS; ++tl) {
        free ((void *) (thetensor[p][q][tl]));
        free ((void *) (thevars[p][q][tl]));
      }
    }
  }
  free (thebase);
  free (thevar);
  
  

  return iret;
}

```

Cartoon2D.h:
```
/* $Header$ */

#include "cctk.h"

/* Function prototypes */
#ifdef __cplusplus 
extern "C" {
#endif

int BndCartoon2DVI(const cGH *GH, int tensortype, int prolongtype, int vi);

CCTK_INT
Cartoon2D_SymmetryInterpolate (CCTK_POINTER_TO_CONST const cctkGH_,
                               CCTK_INT const N_dims,
                               CCTK_INT const local_interp_handle,
                               CCTK_INT const param_table_handle,
                               CCTK_INT const coord_system_handle,
                               CCTK_INT const N_interp_points,
                               CCTK_INT const interp_coords_type,
                               CCTK_POINTER_TO_CONST const interp_coords[],
                               CCTK_INT const N_input_arrays,
                               CCTK_INT const input_array_indices[],
                               CCTK_INT const N_output_arrays,
                               CCTK_INT const output_array_types[],
                               CCTK_POINTER const output_arrays[],
                               CCTK_INT const faces);

#ifdef __cplusplus 
}
#endif

```

interpolate.c:
```
/* interpolate.c -- interpolation functions */
/* $Header$ */

/*
 * interpolate_local - ... (local)
 * interpolate_local_order1 - ... (order 1) (linear) (2-point)
 * interpolate_local_order2 - ... (order 2) (3-point)
 * interpolate_local_order3 - ... (order 3) (4-point)
 * interpolate_local_order4 - ... (order 4) (5-point)
 * interpolate_local_order5 - ... (order 5) (6-point)
 */

/* To make porting easy, only the interpolate_local* functions 
 * have been retained in this file. JT's original code (kept in 
 * the archive directory) also has functions for derivatives and
 * interpolation on non-uniform grids. ---Sai.
 */

#include <math.h>
#include <stdio.h>

#include "cctk.h"

CCTK_REAL interpolate_local(int order, CCTK_REAL x0, CCTK_REAL dx, 
                            CCTK_REAL y[],
                            CCTK_REAL x,
                            CCTK_INT excised_points[]);

CCTK_REAL interpolate_eno(CCTK_INT order, CCTK_REAL x0, CCTK_REAL dx,
                          CCTK_REAL y[],
                          CCTK_REAL x,
                            CCTK_INT excised_points[]);

/* prototypes for private functions defined in this file */
static CCTK_REAL interpolate_local_order1(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x);
static CCTK_REAL interpolate_local_order2(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x);
static CCTK_REAL interpolate_local_order3(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x);
static CCTK_REAL interpolate_local_order4(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x);
static CCTK_REAL interpolate_local_order5(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x);

/*****************************************************************************/

/*
 * This function does local Lagrange polynomial interpolation on a
 * uniform grid.  That is, given  order+1  uniformly spaced data points
 *  (x0 + i*dx, y[i]) , i = 0...order, it interpolates a degree-(order)
 * (Lagrange) polynomial through them and evaluates this polynomial at
 * a specified  x  coordinate.  In general the error in this computed
 * function value is  O(h^(order+1)) .
 *
 * Except for possible end effects (see  end_action  (below)), the local
 * interpolation is internally centered to improve its conditioning.  Even
 * so, the interpolation is highly ill-conditioned for small  dx  and/or
 * large  order  and/or  x  outside the domain of the data points.
 *
 * The interpolation formulas were (are) all derived via Maple, see
 * "./interpolate.in" and "./interpolate.out".
 *
 * Arguments:
 * order = (in) The order of the local interpolation, i.e. the degree
 *		of the interpolated polynomial.
 * x0 = (in) The x coordinate corresponding to y[0].
 * dx = (in) The x spacing between the data points.
 * y[0...order] = (in) The y data array.
 * x = (in) The x coordinate for the interpolation.
 */
CCTK_REAL interpolate_local(int order,
                            CCTK_REAL x0, CCTK_REAL dx,
                            CCTK_REAL y[],
                            CCTK_REAL x,
                            CCTK_INT excised_points[])
{

  /* 
     We assume that the excised region has one of the following 4 forms:
     
     o o o o o         (No excision)
     x o o o o         (left excision)
     o o o o x         (right excision)
     x o o o x         (left and right excision)

     Anything else (e.g., x o x o x) will go horribly wrong.
  */

  int start, end, available_order;
  
  start = 0;
  end   = order;
  
  while ((start < order + 1)&&(excised_points[start]))
  {
    ++start;
  }
  while ((end > -1)&&(excised_points[end]))
  {
    --end;
  }
  
  if (end < start)
  {
    /* The whole block is excised */
    return y[0];
  }

  /* The possible interpolation order that can be used */

  available_order = end - start;

  if (available_order == 0)
  {
    /* There is only one non-excised point */
    return y[start];
  }

  if (dx * start + x0 > x)
  {
    /* We would have to extrapolate to the left */
    return y[start];
  }
  
  if (dx * end + x0 < x)
  {
    /* We would have to extrapolate to the right */
    return y[end];
  }

  switch (available_order) 
  {
  case 1: return interpolate_local_order1(x0 + start * dx, dx, &y[start], x);
  case 2: return interpolate_local_order2(x0 + start * dx, dx, &y[start], x);
  case 3: return interpolate_local_order3(x0 + start * dx, dx, &y[start], x);
  case 4: return interpolate_local_order4(x0 + start * dx, dx, &y[start], x);
  case 5: return interpolate_local_order5(x0 + start * dx, dx, &y[start], x);
  default: CCTK_WARN(0, "Interpolation order not supported");
    return 0; /* Avoid warning */
  }
}

/*****************************************************************************/

/*
 * This function interpolates a 1st-order (linear) Lagrange polynomial
 * in a 2-point [0...1] data array centered about the [0.5] grid position.
 */
static CCTK_REAL interpolate_local_order1(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x)
{
CCTK_REAL c0 = (+1.0/2.0)*y[0] + (+1.0/2.0)*y[1];
CCTK_REAL c1 =          - y[0] +          + y[1];

CCTK_REAL xc = x0 + 0.5*dx;
CCTK_REAL xr = (x - xc) / dx;

return(  c0 + xr*c1  );
}

/*****************************************************************************/

/*
 * This function interpolates a 2nd-order (quadratic) Lagrange polynomial
 * in a 3-point [0...2] data array centered about the [1] grid position.
 */
static CCTK_REAL interpolate_local_order2(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x)
{
CCTK_REAL c0 = y[1];
CCTK_REAL c1 = (-1.0/2.0)*y[0]        + (+1.0/2.0)*y[2];
CCTK_REAL c2 = (+1.0/2.0)*y[0] - y[1] + (+1.0/2.0)*y[2];

CCTK_REAL xc = x0 + 1.0*dx;
CCTK_REAL xr = (x - xc) / dx;

return(  c0 + xr*(c1 + xr*c2)  );
}

/*****************************************************************************/

/*
 * This function interpolates a 3rd-order (cubic) Lagrange polynomial
 * in a 4-point [0...3] data array centered about the [1.5] grid position.
 */
static CCTK_REAL interpolate_local_order3(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x)
{
CCTK_REAL c0 =   (-1.0/16.0)*y[0] + (+9.0/16.0)*y[1]
	    + (+9.0/16.0)*y[2] + (-1.0/16.0)*y[3];
CCTK_REAL c1 =   (+1.0/24.0)*y[0] + (-9.0/ 8.0)*y[1]
	    + (+9.0/ 8.0)*y[2] + (-1.0/24.0)*y[3];
CCTK_REAL c2 =   (+1.0/ 4.0)*y[0] + (-1.0/ 4.0)*y[1]
	    + (-1.0/ 4.0)*y[2] + (+1.0/ 4.0)*y[3];
CCTK_REAL c3 =   (-1.0/ 6.0)*y[0] + (+1.0/ 2.0)*y[1]
	    + (-1.0/ 2.0)*y[2] + ( 1.0/ 6.0)*y[3];

CCTK_REAL xc = x0 + 1.5*dx;
CCTK_REAL xr = (x - xc) / dx;

return(  c0 + xr*(c1 + xr*(c2 + xr*c3))  );
}

/*****************************************************************************/

/*
 * This function interpolates a 4th-order (quartic) Lagrange polynomial
 * in a 5-point [0...4] data array centered about the [2] grid position.
 */
static CCTK_REAL interpolate_local_order4(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x)
{
CCTK_REAL c0 = y[2];
CCTK_REAL c1 =   (+1.0/12.0)*y[0] + (-2.0/ 3.0)*y[1]
	    + (+2.0/ 3.0)*y[3] + (-1.0/12.0)*y[4];
CCTK_REAL c2 = + (-1.0/24.0)*y[0] + (+2.0/ 3.0)*y[1] + (-5.0/ 4.0)*y[2]
	    + (+2.0/ 3.0)*y[3] + (-1.0/24.0)*y[4];
CCTK_REAL c3 = + (-1.0/12.0)*y[0] + (+1.0/ 6.0)*y[1]
	    + (-1.0/ 6.0)*y[3] + (+1.0/12.0)*y[4];
CCTK_REAL c4 = + (+1.0/24.0)*y[0] + (-1.0/ 6.0)*y[1] + (+1.0/ 4.0)*y[2]
	    + (-1.0/ 6.0)*y[3] + (+1.0/24.0)*y[4];

CCTK_REAL xc = x0 + 2.0*dx;
CCTK_REAL xr = (x - xc) / dx;

return(  c0 + xr*(c1 + xr*(c2 + xr*(c3 + xr*c4)))  );
}

/*****************************************************************************/

/*
 * This function interpolates a 5th-order (quintic) Lagrange polynomial
 * in a 6-point [0...5] data array centered about the [2.5] grid position.
 */
static CCTK_REAL interpolate_local_order5(CCTK_REAL x0, CCTK_REAL dx,
                                          CCTK_REAL y[],
                                          CCTK_REAL x)
{
CCTK_REAL c0 =   (+ 3.0/256.0)*y[0] + (-25.0/256.0)*y[1]
	    + (+75.0/128.0)*y[2] + (+75.0/128.0)*y[3]
	    + (-25.0/256.0)*y[4] + (+ 3.0/256.0)*y[5];
CCTK_REAL c1 =   (- 3.0/640.0)*y[0] + (+25.0/384.0)*y[1]
	    + (-75.0/ 64.0)*y[2] + (+75.0/ 64.0)*y[3]
	    + (-25.0/384.0)*y[4] + (+ 3.0/640.0)*y[5];
CCTK_REAL c2 =   (- 5.0/ 96.0)*y[0] + (+13.0/ 32.0)*y[1]
	    + (-17.0/ 48.0)*y[2] + (-17.0/ 48.0)*y[3]
	    + (+13.0/ 32.0)*y[4] + (- 5.0/ 96.0)*y[5];
CCTK_REAL c3 =   (+ 1.0/ 48.0)*y[0] + (-13.0/ 48.0)*y[1]
	    + (+17.0/ 24.0)*y[2] + (-17.0/ 24.0)*y[3]
	    + (+13.0/ 48.0)*y[4] + (- 1.0/ 48.0)*y[5];
CCTK_REAL c4 =   (+ 1.0/ 48.0)*y[0] + (- 1.0/ 16.0)*y[1]
	    + (+ 1.0/ 24.0)*y[2] + (+ 1.0/ 24.0)*y[3]
	    + (- 1.0/ 16.0)*y[4] + (+ 1.0/ 48.0)*y[5];
CCTK_REAL c5 =   (- 1.0/120.0)*y[0] + (+ 1.0/ 24.0)*y[1]
	    + (- 1.0/ 12.0)*y[2] + (+ 1.0/ 12.0)*y[3]
	    + (- 1.0/ 24.0)*y[4] + (+ 1.0/120.0)*y[5];

CCTK_REAL xc = x0 + 2.5*dx;
CCTK_REAL xr = (x - xc) / dx;

return(  c0 + xr*(c1 + xr*(c2 + xr*(c3 + xr*(c4 + xr*c5))))  );
}


/*****************************************************************************/

CCTK_REAL interpolate_eno(CCTK_INT order, 
                          CCTK_REAL x0, CCTK_REAL dx,
                          CCTK_REAL y[],
                          CCTK_REAL x,
                          CCTK_INT excised_points[])
{

/*   CCTK_REAL diff[4]; */
/*   CCTK_INT seed = 0; */
/*   CCTK_INT j,r;   */

/*   CCTK_REAL c0; */
/*   CCTK_REAL c1; */
/*   CCTK_REAL c2; */

/*   CCTK_REAL xc; */
/*   CCTK_REAL xr; */

  CCTK_REAL result;
  CCTK_REAL undiv_diff [13][6];
  CCTK_INT diff, i, ii, r;
  CCTK_REAL fp_i, fp_ii, x_rel;

  /* Find seed index */
/*   while (x > x0 + dx * ((CCTK_REAL)seed+0.5)) seed++; */

/*   if (seed!=2) { */
    /* Not enough stencil, only perform linear interpolation */   
/*     seed = 0; */
/*     while (x > x0 + dx * ((CCTK_REAL)(seed+1))) seed++; */
/*     if (seed==4) seed=3; */
/*     return y[seed] + (y[seed+1]-y[seed])/dx * (x-x0-(CCTK_REAL)seed*dx); */
/*   } */

  /*  Calculate the undivided differences */
/*   for (j=0; j<=3; j++) */
/*     diff[j] = y[j+1] - y[j]; */

  /*  Find the stencil */
/*   if ( fabs(diff[1]) < fabs(diff[2]) ) { */
/*     if ( fabs(diff[1]-diff[0]) < fabs(diff[2]-diff[1]) ) */
/*       r = 0; */
/*     else */
/*       r = 1; */
/*   } else { */
/*     if ( fabs(diff[2]-diff[1]) < fabs(diff[3]-diff[2]) ) */
/*       r = 1; */
/*     else */
/*       r = 2; */
/*   } */

  /* Interpolate second order */
/*   c0 = y[r+1]; */
/*   c1 = (-1.0/2.0)*y[r] + (+1.0/2.0)*y[r+2]; */
/*   c2 = (+1.0/2.0)*y[r] - y[r+1] + (+1.0/2.0)*y[r+2]; */

/*   xc = x0 + dx * (CCTK_REAL)(r+1); */
/*   xr = (x - xc) / dx; */
/*   result = (  c0 + xr*(c1 + xr*c2)  ); */

  for (i = 1; i < 2 * order + 2; ++i)
  {
    undiv_diff[i][0] = y[i-1];
  }
  for (i = 0; i < 6; ++i)
  {
    undiv_diff[0][i] = 1e10;
    undiv_diff[2 * order + 2][i] = 1e10;
  }

  for (diff = 1; diff < order + 1; ++diff)
  {
    for (i = 1; i < 2 * order + 2; ++i)
    {
      undiv_diff[i][diff] = undiv_diff[i][diff-1] - undiv_diff[i-1][diff-1];
    }
    undiv_diff[0][diff] = -10 * undiv_diff[1][diff];
    undiv_diff[2 * order + 2][diff]= -10 * undiv_diff[2 * order + 1][diff];
  }
  
  fp_i = (x - x0) / dx;
  fp_ii = floor(fp_i);
  x_rel = fp_i - fp_ii;
  
  ii = (CCTK_INT)(fp_ii) + 1;
  
  if (ii < 1)
  {
    ii = 1;
    x_rel = fp_i - (CCTK_REAL)(ii) + 1.0;
  }
  else if (ii > 2 * order + 1)
  {
    ii = 2 * order + 1;
    x_rel = fp_i - (CCTK_REAL)(ii) + 1.0;
  }
  
  r = 0;
  for (diff = 1; diff < order + 1; ++diff)
  {
    if (fabs(undiv_diff[ii-r][diff]) < fabs(undiv_diff[ii-r+1][diff]) )
    {
      ++r;
    }
    
    if (ii - r < diff + 1)
    {
      --r;
    }
    else if (ii - r + diff > 2 * order + 1)
    {
      ++r;
    }
  }

  result = undiv_diff[ii-r][0];

  switch (order)
  {
    case 1:
      result += (x_rel + r - 0.0) / 1.0 * undiv_diff[ii-r+1][1];
      break;
    case 2:
      result += (x_rel + r - 0.0) / 1.0 * (undiv_diff[ii-r+1][1] +
                (x_rel + r - 1.0) / 2.0 * (undiv_diff[ii-r+2][2]));
      break;
    case 3:
      result += (x_rel + r - 0.0) / 1.0 * (undiv_diff[ii-r+1][1] +
                (x_rel + r - 1.0) / 2.0 * (undiv_diff[ii-r+2][2] +
                (x_rel + r - 2.0) / 3.0 * (undiv_diff[ii-r+3][3])));
      break;
    case 4:
      result += (x_rel + r - 0.0) / 1.0 * (undiv_diff[ii-r+1][1] +
                (x_rel + r - 1.0) / 2.0 * (undiv_diff[ii-r+2][2] +
                (x_rel + r - 2.0) / 3.0 * (undiv_diff[ii-r+3][3] +
                (x_rel + r - 3.0) / 4.0 * (undiv_diff[ii-r+4][4]))));
      break;
    case 5:
      result += (x_rel + r - 0.0) / 1.0 * (undiv_diff[ii-r+1][1] +
                (x_rel + r - 1.0) / 2.0 * (undiv_diff[ii-r+2][2] +
                (x_rel + r - 2.0) / 3.0 * (undiv_diff[ii-r+3][3] +
                (x_rel + r - 3.0) / 4.0 * (undiv_diff[ii-r+4][4] +
                (x_rel + r - 4.0) / 5.0 * (undiv_diff[ii-r+5][5])))));
      break;
  }
  
/* #define CARTOON_ENO_DBG 1 */
#ifdef CARTOON_ENO_DBG

  printf("Cartoon ENO interpolation.\n"
         "Input: order %d x0 %g dx %g x %g.\n",
         order, x0, dx, x);
  printf("Data is\n");
  for (i = 0; i < 2 * order + 1; ++i)
  {
    printf(" %g", y[i]);
  }
  printf("\nEntries used were\n");
  printf("0: %g   1: %g   2: %g\n",
         undiv_diff[ii-r][0], undiv_diff[ii-r+1][1], undiv_diff[ii-r+2][2]);
  printf("Parameters are fp_i %g fp_ii %g x_rel %g ii %d r %d\n",
         fp_i, fp_ii, x_rel, ii, r);
  printf("Result is %g\n", result);
  
#endif

  return result;  

}

```

SetGrid.c:
```
/*@@
   @file      SetGrid.c
   @date      April 2002
   @author    Denis Pollney
   @desc
              Reset the grid sizes before they are set by CartGrid3D, so
              that they have sensible cartoon-compatible values.
   @enddesc
   @version   $Id$
 @@*/

#include <stdio.h>
#include <string.h>

#include "cctk.h"
#include "cctk_Parameter.h"
#include "cctk_Parameters.h"

static const char *rcsid = "$Header$";

CCTK_FILEVERSION(BetaThorns_Cartoon2D_SetGrid_c);

/********************************************************************
 *********************     Local Data Types   ***********************
 ********************************************************************/

/********************************************************************
 ********************* Local Routine Prototypes *********************
 ********************************************************************/

/********************************************************************
 ***************** Scheduled Routine Prototypes *********************
 ********************************************************************/

int Cartoon2D_SetGrid(void);

/********************************************************************
 ********************* Other Routine Prototypes *********************
 ********************************************************************/

/********************************************************************
 *********************     Local Data   *****************************
 ********************************************************************/

/********************************************************************
 *********************     External Routines   **********************
 ********************************************************************/

 /*@@
   @routine    Cartoon_SetGrid
   @date       April 2002
   @author     Denis Pollney
   @desc
               Resets the grid dimensions in a cartoon-compatible way if
               the user has specified grid::type="byspacing".

               Generally, "byspacing" would put the z-axis in the middle
               of the x range. However, for cartoon, it is required to
               be at the edge of the range with only some zombie-zones
               extending across. This routine ensures that this is the
               case, by fixing the x range and re-specifying nx so that
               the dx value which was specified by the user is unchanged.
               The y range is reset to be a width of 1 plus space for the
               zombie points.

               In the process, a couple of other parameters such as
               grid::bitant_plane and grid::avoid_originy are also checked
               to ensure that they are cartoon-compatible.

               If the grid type is specified to be "byrange", then this
               routine does nothing and it is up to the user to choose
               appropriate ranges for what they want to do.

               Note that this routine currently needs to be scheduled at
               CCTK_RECOVER_PARAMETERS, because this is the only place where
               it is still possible to modify non-steerable parameters.
   @enddesc
   @@*/
int Cartoon2D_SetGrid(void)
{
  DECLARE_CCTK_PARAMETERS

  const CCTK_INT *cctk_int_ptr;
  const CCTK_REAL *cctk_real_ptr;
  int nx, ny, nz, ghost_size_y, avoid_y0, cartoon_ny;
  double dx, dy, dz;
  double xmin, xmax, ymin, ymax, zmin, zmax;
  char p_val[80];
  const char *domain, *plane;


  CCTK_WARN (0, "Erik Schnetter, 2006-05-11: This routine does not work.  The schedule bin RECOVER_PARAMETERS is special; not all routines in this bin are executed.");

  /*
   * Determine the y ghostzone size.
   */
  cctk_int_ptr = (const CCTK_INT *) CCTK_ParameterGet("ghost_size_y", "pugh", NULL);
  if (cctk_int_ptr == NULL)
    CCTK_WARN(0, "pugh::ghost_size_y must be set explicitly");

  ghost_size_y = *cctk_int_ptr;
  cartoon_ny = 2 * ghost_size_y + 1;

  /*
   * Get the x,y,z grid sizes.
   */
  cctk_int_ptr = (const CCTK_INT *) CCTK_ParameterGet("global_nsize", "pugh", NULL);
  if ((cctk_int_ptr == NULL) || (*cctk_int_ptr == -1))
  {
    nx = *(const CCTK_INT *) CCTK_ParameterGet("global_nx", "pugh", NULL);
    ny = *(const CCTK_INT *) CCTK_ParameterGet("global_ny", "pugh", NULL);
    nz = *(const CCTK_INT *) CCTK_ParameterGet("global_nz", "pugh", NULL);
  }
  else
  {
    nx = ny = nz = *cctk_int_ptr;
  }

  /*
   * Reset the y grid size to be one layer thick, plus twice the ghostzone
   * size.
   */
  if (ny != cartoon_ny)
  {
    CCTK_VInfo(CCTK_THORNSTRING, "Resetting pugh::global_ny to %d", cartoon_ny);
    sprintf(p_val, "%d", cartoon_ny);
    CCTK_ParameterSet("global_ny", "pugh",  p_val);
  }


  /*
   * Reset the grid sizes if the user has specified a "byspacing" grid.
   */
  if (strcmp(CCTK_ParameterValString("type", "cartgrid3d"), "byrange"))
  {
    /*
     * We need to set the grid spacings explicitly, so from now on
     * consider the grid to be 'byrange'.
     */
    CCTK_ParameterSet("type", "cartgrid3d", "byrange");

    /*
     * Get the x,y,z grid spacing.
     */
    cctk_real_ptr = (const CCTK_REAL *) CCTK_ParameterGet("dxyz", "cartgrid3d", NULL);
    if (cctk_real_ptr == NULL)
    {
      dx = *(const CCTK_REAL *) CCTK_ParameterGet("dx", "cartgrid3d", NULL);
      dy = *(const CCTK_REAL *) CCTK_ParameterGet("dy", "cartgrid3d", NULL);
      dz = *(const CCTK_REAL *) CCTK_ParameterGet("dz", "cartgrid3d", NULL);
    }
    else
    {
      dx = dy = dz = *cctk_real_ptr;
    }

    /*
     * Set the x grid dimensions.
     */
    nx += ghost_size_y + 1;
    CCTK_VInfo(CCTK_THORNSTRING, "Adding pugh::ghost_size_y+1 to pugh::global_nx");
    sprintf(p_val, "%d", nx);
    CCTK_ParameterSet("global_nx", "pugh",  p_val);

    xmin = -(ghost_size_y-0.5) * dx;
    xmax = xmin + (nx-1) * dx;

    CCTK_VInfo(CCTK_THORNSTRING, "Setting x-range to [%f, %f]", xmin, xmax);

    sprintf(p_val, "%f", xmin);
    if (CCTK_ParameterSet("xmin", "cartgrid3d",  p_val) < 0)
    {
       CCTK_WARN(0,"Error setting parameter");
    }
    sprintf(p_val, "%f", xmax);
    if (CCTK_ParameterSet("xmax", "cartgrid3d",  p_val) < 0)
    {
       CCTK_WARN(0,"Error setting parameter");
    }

    /*
     * Set the y grid dimensions.
     */
    avoid_y0 = *(const CCTK_INT *) CCTK_ParameterGet("avoid_originy", "cartgrid3d", NULL);
    if (avoid_y0)
      CCTK_WARN(0, "Cartoon2D requires grid::avoid_originy=\"no\"");

    ymax = ghost_size_y * dy;
    ymin = -ymax;

    CCTK_VInfo(CCTK_THORNSTRING, "Setting y-range to [%f, %f]", ymin, ymax);

    sprintf(p_val, "%f", ymin);
    if (CCTK_ParameterSet("ymin", "cartgrid3d",  p_val) < 0)
    {
       CCTK_WARN(0,"Error setting parameter");
    }
    sprintf(p_val, "%f", ymax);
    if (CCTK_ParameterSet("ymax", "cartgrid3d",  p_val) < 0)
    {
       CCTK_WARN(0,"Error setting parameter");
    }

    /*
     * Set the z grid dimensions for bitant mode.
     */

    zmin = zmax = 0;
    domain = *((const char * const *) CCTK_ParameterGet("domain", "cartgrid3d", NULL));
    if (!strcmp(domain, "bitant"))
    {
      plane = *(const char * const *) CCTK_ParameterGet("bitant_plane", "cartgrid3d", NULL);
      if (strcmp(plane, "xy"))
        CCTK_WARN(0, "Cartoon2D requires grid::bitant_plane=\"xy\"");

      cctk_int_ptr = (const CCTK_INT *) CCTK_ParameterGet("ghost_size", "pugh", NULL);
      if ((cctk_int_ptr == NULL) || (*cctk_int_ptr < 0))
        cctk_int_ptr = (const CCTK_INT *) CCTK_ParameterGet("ghost_size_z", "pugh", NULL);

      nz += 1;
      CCTK_VInfo(CCTK_THORNSTRING, "Increasing PUGH::global_nz by 1");
      sprintf(p_val, "%d", nz);
      CCTK_ParameterSet("global_nz", "pugh",  p_val);

      zmax = (nz-0.5) * dz;
      zmin = -zmax;

      if ((cctk_int_ptr != NULL) && (*cctk_int_ptr > 0))
      {
        nz += (*cctk_int_ptr);
        CCTK_VInfo(CCTK_THORNSTRING, "Increasing PUGH::global_nz by "
                                     "PUGH::ghost_size_z");
        sprintf(p_val, "%d", nz);
        CCTK_ParameterSet("global_nz", "pugh",  p_val);
      }
    }
    else if (!strcmp(domain, "full"))
    {
      nz += 2;
      CCTK_VInfo(CCTK_THORNSTRING, "Increasing PUGH::global_nz by 2");
      sprintf(p_val, "%d", nz);
      CCTK_ParameterSet("global_nz", "pugh",  p_val);

      zmax = (nz-1) * dz/2;
      zmin = -zmax;
    }
    else
    {
      CCTK_VWarn(0, __LINE__, __FILE__, CCTK_THORNSTRING,
                     "Cartoon2D is unable to handle grid::domain=\"%s\"",
                   domain);
    }

    CCTK_VInfo(CCTK_THORNSTRING, "Setting z-range to [%f,%f]", zmin, zmax);

    sprintf(p_val, "%f", zmin);
    if (CCTK_ParameterSet("zmin", "cartgrid3d",  p_val) < 0)
    {
       CCTK_WARN(0,"Error setting parameter");
    }
    sprintf(p_val, "%f", zmax);
    if (CCTK_ParameterSet("zmax", "cartgrid3d",  p_val) < 0)
    {
       CCTK_WARN(0,"Error setting parameter");
    }
  }

  return 0;
}

```

make.code.defn:
```
# Main make.code.defn file for thorn Cartoon2D
# $Header$

# Source files in this directory
SRCS = ApplyCartoon.c CheckParameters.c Cartoon2DBC.c interpolate.c RegisterSym.c SymInterp.c SetGrid.c

# Subdirectories containing source files
SUBDIRS = 

```

CheckParameters.c:
```
/*@@
   @file      CheckParameters.c
   @date      Wed Nov  3 10:17:46 MET 1999
   @author    Sai Iyer
   @desc 
              Check Cartoon2D parameters
   @enddesc 
 @@*/

#include "cctk.h" 
#include "cctk_Parameters.h"
#include "cctk_Arguments.h"

static const char *rcsid = "$Id$";

CCTK_FILEVERSION(Development_Cartoon2D_CheckParameters_c)

/********************************************************************
 *********************     Local Data Types   ***********************
 ********************************************************************/

/********************************************************************
 ********************* Local Routine Prototypes *********************
 ********************************************************************/

/********************************************************************
 ***************** Scheduled Routine Prototypes *********************
 ********************************************************************/

void Cartoon2D_CheckParameters(CCTK_ARGUMENTS);

/********************************************************************
 ********************* Other Routine Prototypes *********************
 ********************************************************************/

/********************************************************************
 *********************     Local Data   *****************************
 ********************************************************************/

/********************************************************************
 *********************     External Routines   **********************
 ********************************************************************/

 /*@@
   @routine    Cartoon2D_CheckParameters
   @date       Wed Nov  3 10:17:46 MET 1999
   @author     Sai Iyer
   @desc 
               Check Cartoon2D parameters
   @enddesc 
   @calls      
   @calledby   
   @history 

   @endhistory 

@@*/

void Cartoon2D_CheckParameters(CCTK_ARGUMENTS)
{
  DECLARE_CCTK_ARGUMENTS
  DECLARE_CCTK_PARAMETERS

  if(order > 2*cctk_nghostzones[0])
  {
    CCTK_PARAMWARN("Ghostzone width in x direction too small.");
  }

  return;
}


```

ApplyCartoon.c:
```
/*@@
  @file      ApplyCartoon.c
  @date      6 Jan 2003
  @author    David Rideout
  @desc 
  Applies the Cartoon boundary condition to all variables 
  selected for a bc.
  @enddesc 
  @version   $Header$
  @@*/

#include "cctk.h"
#include "cctk_Arguments.h"
#include "cctk_Parameters.h"
#include "util_Table.h"
#include "Cartoon2D.h"
#include "Cartoon2D_tensors.h"
#include "cctk_Groups.h"
#include <stdlib.h>
#include <stdio.h>


static const char *rcsid = "$Header$";

CCTK_FILEVERSION(BetaThorns_Cartoon2D_ApplyCartoon_c);

/********************************************************************
 *********************     Local Data Types   ***********************
 ********************************************************************/
/* #define DEBUG 1 */
#define TENSORTYPE_BUFF_SIZE 7
#define PROLONG_BUFF_SIZE 1000

/********************************************************************
 ********************* Local Routine Prototypes *********************
 ********************************************************************/

/********************************************************************
 ***************** Aliased Routine Prototypes ***********************
 ********************************************************************/

/********************************************************************
 ***************** Scheduled Routine Prototypes *********************
 ********************************************************************/

void Cartoon_ApplyBoundaries(CCTK_ARGUMENTS);

/********************************************************************
 ********************* Other Routine Prototypes *********************
 ********************************************************************/

/********************************************************************
 *********************     Local Data   *****************************
 ********************************************************************/

/********************************************************************
 *********************     Aliased Routines   **********************
 ********************************************************************/
 
/********************************************************************
 *********************     Scheduled Routines   **********************
 ********************************************************************/

/*@@
  @routine    Cartoon_ApplyBoundaries
  @date       6 Jan 2003
  @author     David Rideout
  @desc  
  This will apply the Cartoon boundary condition to all
  variables selected for any (physical) boundary condition.
  @enddesc 
  @calls      
  @history 
  @endhistory 
  @var        CCTK_ARGUMENTS
  @vdesc      Cactus argument list
  @vtype      CCTK_*
  @vio        in
  @endvar
  @returntype void
  @returndesc
  @endreturndesc
  @@*/

void Cartoon_ApplyBoundaries(CCTK_ARGUMENTS) 
{

  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;

  int num_vars, err, i, gi, group_tags_table;
  CCTK_INT * vars;
  char tensortype[TENSORTYPE_BUFF_SIZE];
  char prolongtype[PROLONG_BUFF_SIZE];
  int prolongmethod = -1;

  int whiskycartoon, len;

  


  /* Check grid size */
  if(cctk_gsh[1] != 2*cctk_nghostzones[1]+1)
    {
      CCTK_WARN(0, "Grid size in y direction inappropriate.");
    }

  /* Allocate memory to hold selected bcs */
  num_vars = Boundary_SelectedGVs(cctkGH, 0, NULL, NULL, NULL, NULL, NULL);
#ifdef DEBUG
  printf("Cartoon_ApplyBoundaries: num_vars is %d\n",num_vars);
#endif
  vars = malloc(num_vars*sizeof *vars);

  /* get all selected vars */
  err = Boundary_SelectedGVs(cctkGH, num_vars, vars, NULL, NULL, NULL, NULL);
  if (err != num_vars)
    {
      CCTK_VWarn(1, __LINE__, __FILE__, CCTK_THORNSTRING,
		 "Boundary_SelectedGVs returned %d selected variables, but %d "
		 "expected", err, num_vars);
    }

  /* Apply CartoonBC to each of them. */
  /* One should probably check to see that the entire group has been
   * selected, since Cartoon operates on an entire group at a time.
   * For now I'll just skip a variable if it is not a 'group leader'.
   */
  for (i=0; i<num_vars; ++i) {
#ifdef DEBUG
    printf("Cartoon_ApplyBoundaries: i=%d applying cartoon to vi %d\n",i,
	   vars[i]);
#endif
    gi = CCTK_GroupIndexFromVarI(vars[i]);
    if (gi<0) {
      CCTK_VWarn(1, __LINE__, __FILE__, CCTK_THORNSTRING,
		 "Invalid variable index %d selected for a boundary "
		 "condition", gi);
    }
    if (vars[i] != CCTK_FirstVarIndexI(gi)) 
      {
	/* not a group leader -- skip to next var index */
	continue;
      }

    /* Here one should check that the entire group is registered,
     * using CCTK_NumVarsInGroupI.
     */

    /* Get table handle for group tags table */
    group_tags_table = CCTK_GroupTagsTableI(gi);
    if (group_tags_table<0) {
      CCTK_VWarn(1, __LINE__, __FILE__, CCTK_THORNSTRING,
		 "Tags table for variable group %s is %d", CCTK_GroupName(gi),
		 group_tags_table);
    } 
   

    /* This lines are writen for Whisky2D to avoid to treat the hydrovariables with Cartoon */
 
    len = Util_TableGetString(group_tags_table,0,NULL,"whiskycartoon");

    whiskycartoon = 1;          /* default is yes */
    if (len >= 0)
      {
        char* value = malloc (len + 1);
        Util_TableGetString (group_tags_table, len + 1, value, "whiskycartoon");
        if (CCTK_Equals(value, "yes"))
          {
            whiskycartoon = 1;
          }
        else if (CCTK_Equals(value, "no"))
          {
            whiskycartoon = 0;
          }
        else
          {
            CCTK_VWarn(1, __LINE__, __FILE__, CCTK_THORNSTRING,
                       "Tags table entry \"whiskycartoon\" for variable group %s should be either \"yes\" or \"no\", but is \"%s\"",
                       CCTK_GroupName(gi), value);
          }
        free (value);
      }

    if (whiskycartoon)
      {
  
	/*###################################################################*/


	/* Get tensor type from group tags table */
	err = Util_TableGetString(group_tags_table, TENSORTYPE_BUFF_SIZE,
				  tensortype, "tensortypealias");
	if (err<0) 
	  {
	    CCTK_VWarn(1, __LINE__, __FILE__, CCTK_THORNSTRING,
		       "Error in TAGS table for variable group %s",
		       CCTK_GroupName(gi));
	  }
#ifdef DEBUG
	printf("Cartoon_ApplyBoundaries: tensor type is %s\n",tensortype);
#endif

	/* Get prolongation type from group tags table */

	err = Util_TableGetString(group_tags_table, PROLONG_BUFF_SIZE,
				  prolongtype, "Prolongation");

	if (err == UTIL_ERROR_TABLE_NO_SUCH_KEY) 
	  {
	    /* Use the default */

	    prolongmethod = PROLONG_LAGRANGE;
	  }
	else if (err < 0)
	  {
	    CCTK_VWarn(0, __LINE__, __FILE__, CCTK_THORNSTRING,
		       "Error (%d) in TAGS table for variable group %s", err,
		       CCTK_GroupName(gi));
	  }
	else
	  {
	    if (CCTK_Equals(prolongtype, "None")) 
	      {
		prolongmethod = PROLONG_NONE; /* But why? */
	      } 
	    else if (CCTK_Equals(prolongtype, "Lagrange")) 
	      {
		prolongmethod = PROLONG_LAGRANGE;
	      } 
	    else if (CCTK_Equals(prolongtype, "TVD")) 
	      {
		prolongmethod = PROLONG_ENO;
	      } 
	    else if (CCTK_Equals(prolongtype, "ENO")) 
	      {
		prolongmethod = PROLONG_ENO;
	      } 
	    else 
	      {
		CCTK_VWarn(0, __LINE__, __FILE__, CCTK_THORNSTRING,
			   "Error in TAGS table for variable group %s",
			   CCTK_GroupName(gi));
	      }
	  }    
    
	/* Here one should check that the group's size is correct for the
	 * specified tensortype.
	 */

	/* Call BndCartoon2DVI, passing the appropriate tensor type integer 
	   macro */
	if (CCTK_Equals(tensortype, "scalar"))
	  {
	    BndCartoon2DVI(cctkGH, TENSORTYPE_SCALAR, prolongmethod, vars[i]);
	  } else if ((CCTK_Equals(tensortype, "u")) ||
		     (CCTK_Equals(tensortype, "d")))
	  {
	    BndCartoon2DVI(cctkGH, TENSORTYPE_U, prolongmethod, vars[i]);
	  } else if (CCTK_Equals(tensortype, "dd_sym"))
	  {
	    BndCartoon2DVI(cctkGH, TENSORTYPE_DDSYM, prolongmethod, vars[i]);
	  } else 
	  {
	    CCTK_VWarn(0, __LINE__, __FILE__, CCTK_THORNSTRING,
		       "invalid tensor type for group %s", CCTK_GroupName(gi));
	  }  
      }
  }
  /* Free data */
  free(vars);
}

```

Cartoon2D_tensors.h:
```
/* Number of tensor types defined */
#define N_TENSORTYPES      3

/* Tensor types */
#define TENSORTYPE_SCALAR  0
#define TENSORTYPE_U       1
#define TENSORTYPE_DDSYM   2

/* Prolongation methods */
#define PROLONG_LAGRANGE 1
#define PROLONG_ENO 2
#define PROLONG_NONE 3

```

RegisterSym.c:
```
/*@@
   @file      RegisterSym.c
   @date      February 2004
   @author    Erik Schnetter
   @desc 
              Register the symmetry boundary faces.
   @enddesc
 @@*/

#include "cctk.h"
#include "cctk_Arguments.h"
#include "cctk_Parameters.h"

#include "Cartoon2D.h"



void Cartoon2D_RegisterSymmetries (CCTK_ARGUMENTS)
{
  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;
  
  CCTK_INT nboundaryzones[6];
  CCTK_INT is_internal[6];
  CCTK_INT is_staggered[6];
  CCTK_INT shiftout[6];
  
  int f;
  CCTK_INT handle;
  CCTK_INT faces[6];
  CCTK_INT width[6];
  CCTK_INT ierr;
  
  /* Get the boundary specification */
  ierr = GetBoundarySpecification
    (6, nboundaryzones, is_internal, is_staggered, shiftout);
  if (ierr < 0)
  {
    CCTK_WARN (0, "Could not get the boundary specification");
  }
  
  faces[0] = 1;
  faces[1] = 0;
  faces[2] = 1;
  faces[3] = 1;
  faces[4] = 0;
  faces[5] = 0;
  
  for (f=0; f<6; ++f) {
    width[f] = nboundaryzones[f/2];
  }
  
  handle = SymmetryRegister ("cartoon");
  if (handle < 0) {
    CCTK_WARN (0, "Could not register Cartoon boundary condition");
  }
  
  ierr = SymmetryRegisterGrid (cctkGH, handle, faces, width);
  if (ierr < 0) {
    CCTK_WARN (0, "Could not register the Cartoon boundaries -- probably some other thorn has already registered the same boundary faces for a different symmetry");
  }
  
  ierr = SymmetryRegisterGridInterpolator
    (cctkGH, handle, Cartoon2D_SymmetryInterpolate);
  if (ierr < 0) {
    CCTK_WARN (0, "Could not register the symmetry interpolator");
  }
  
}

```


combined_doc_context: ## README (README):
```
Cactus Code Thorn Cartoon2D
Author(s)    : Sai Iyer
               Denis Pollney
               Thomas Radke
               David Rideout
Maintainer(s): Cactus team
Licence      : LGPL
--------------------------------------------------------------------------

1. Purpose

Implements a "cartoon" axisymmetric evolution according to the method
described in gr-qc/9908012.  The original idea for 'Cartoon2D' was due
to Steve Brandt.

See doc/documentation.tex for usage notes.

See the test/ directory for sample parameter files.

```

## Documentation (.tex files):
Files: documentation.tex

```tex
=== documentation.tex ===
% /*@@
%   @file      documentation.tex
%   @date      16 April 2002
%   @author    Denis Pollney
%   @desc 
%              Cartoon2D user/maintainer guide.
%   @enddesc 
%   @version $Header$      
% @@*/


\documentclass{article}

% Use the Cactus ThornGuide style file
% (Automatically used from Cactus distribution, if you have a 
%  thorn without the Cactus Flesh download this from the Cactus
%  homepage at www.cactuscode.org)
\usepackage{../../../../doc/latex/cactus}

\begin{document}

\title{Using Cartoon2D}
\author{Denis Pollney}
\date{$ $Date$ $}

\maketitle

% Do not delete next line
% START CACTUS THORNGUIDE

\begin{abstract}
  The \texttt{Cartoon2D} thorn allows fully 3D codes to be used
  to model axisymmetric systems, by considering a 3D evolution which
  is only one plane thick and applying a rotational tensor
  transformation to the flat faces of the plane as a boundary
  condition. This allows 3D codes to be tested efficiently on 2D
  problems, as well as providing a means of carrying out axisymmetric
  evolutions in cartesian coordinates without problems at the axis.
\end{abstract}

\section{Background}

The \texttt{Cartoon2D} thorn is the implementation of an idea first
presented in \cite{Alcubierre-etal-2001}. The principle is to use a
3-dimensional Cartesian $(x,y,z)$ coordinate grid which covers the
$y=0$ plane, but is only one finite difference molecule in width in
the $y$-direction. The field variables in the $y=0$ plane can be
updated using standard 3D $(x,y,z)$ coordinate finite differencing,
with off-plane derivatives calculated by performing appropriate
rotations of field variables using the axisymmetic assumption.
This technique has the advantage of removing a number of the axis
problems normally associated with axisymmetric codes.

The `cartoon' method was first implemented in Cactus3 by Steve Brandt
and Bernd Br\"ugmann, and was translated to Cactus4 by Sai
Iyer. Details of the method can be found in
\cite{Alcubierre-etal-2001}. This document provides a practical guide
to using the thorn as it is currently implemented.

\section{Basic usage}

Only a small number of parameters need to be set to use
\texttt{Cartoon2D}.

\begin{description}

  \item[\texttt{cartoon\_active}] A flag which determines whether or
  not the cartoon boundary condition should be applied. This should be
  set to ``yes'' to use \texttt{Cartoon2D}, otherwise the evolution
  will be assumed to be standard 3D.

  \item[\texttt{order}] Determines the order of interpolations to be
  carried out. This will determine the number of ghost zones that you
  need to specify in the $y$-direction.

  \item[\texttt{allow\_grid\_resize}] When this flag is set to
  ``yes'', \texttt{Cartoon2D} is allowed to modify the grid sizes
  specified in the parameter file so that a cartoon-compatible grid is
  used. See below.

  \item[\texttt{verbose}] Causes \texttt{Cartoon2D} to print a lot of
  messages.

%
% FIXME: The param.ccl file also mentions a stencil parameter, which
% seems not to be used in the code.
%

\end{description}

For example, the following is a section of a parameter file which sets
up a cartoon-style grid in bitant mode.
\begin{verbatim}

activethorns                            = "cartoon2d cartgrid3d pugh"

cartoon2d::cartoon_active               = "yes"
cartoon2d::order                        = 3
cartoon2d::allow_grid_resize            = "yes"

grid::type                              = "byspacing"
grid::domain                            = "bitant"
grid::bitant_plane                      = "xy"
grid::dxyz                              = 0.2

driver::global_nx                       = 16
driver::global_ny                       = 3
driver::global_nz                       = 16

driver::ghost_size_x                    = 2
driver::ghost_size_y                    = 1
driver::ghost_size_z                    = 2

driver::processor_topology              = "manual"
driver::processor_topology_3d_x         = 1
driver::processor_topology_3d_y         = 1
driver::processor_topology_3d_z         = 2

grid::avoid_originy                     = "no"

\end{verbatim}
The following features are worth noting:
\begin{itemize}

  \item The \texttt{order} parameter specifies that 3rd order
  interpolation is to be used. This requires that only one ghost zone
  is required in the y direction (\texttt{ghost\_size\_y}). If 4th
  order interpolation was specified, then two ghost zones would be
  required.

  \item The \texttt{allow\_grid\_resize} flag has been set, allowing
  \texttt{Cartoon2D} to modify the grid appropriately so that, for
  instance, it only extends a given number of ghost zones beyond the
  $z$-axis.

  \item The bitant plane should always be ``\texttt{xy}'', which means
  that only the positive $z$-axis is evolved and a reflection boundary
  is imposed along $z=0$.

  \item Though not necessary, it is often helpful to specify the
  processor topology explicitly to ensure that that only one processor
  is allocated to the $y$ direction, and that the processors in the
  $x$ and $z$ directions reflect the relative lengths of these axes
  (though in this example it doesn't matter which of these axes gets
  two processors).

  \item The \texttt{avoid\_originy} parameter needs to be turned off
    so that the cartoon plane corresponds to $y=0$.

\end{itemize}
Also, it is important to keep in mind that other thorns may also
require their own parameters to be set in order to interact
appropriately with \texttt{Cartoon2D}. For instance, see Section
\ref{sec:interaction}. Examples of working parameter files can be
found in the \texttt{Cartoon2D/test/} directory.

\begin{figure}
  \centering
  \includegraphics[angle=0,height=8cm]{cartoon_plane}
  \caption{The cartoon plane layout. The symmetry axis is the
    $z$-axis, with grid functions defined on the $y=0$ plane, in this
    example with two ghost zones.}
\end{figure}

\section{Automatic grid re-sizing}
\label{sec:regrid}

The \texttt{Cartoon2D} thorn has some non-standard requirements of the
grid which is normally set up by the \texttt{grid} implementation (for
instance, by \texttt{CartGrid3D}). In particular,

\begin{itemize}

  \item the grid in the $y$ direction should be exactly one plane in
    width, plus twice the number of $y$ ghost zones.

  \item the grid in the $x$ direction only needs to extend to the
    $z$-axis, plus a few extra hang-overs. The number of extra points
    is determined by the number of $y$ ghost zone points.

\end{itemize}

These requirements (in particular the second) can not be met if the
grid is specified \texttt{byspacing} (ie. by giving a $dx$ value),
since in this case the grid is set up assuming that the axes should be
centred in each grid direction.

One way to get around this is to specify the grid \texttt{byrange},
giving minimum and maximum coordinate values for each axis so that the
$(dx,dy,dz)$ values are determined by dividing the range by the number
of grid points. However, using this method it can be quite complicated
to ensure that the grid spacing is even in each direction and that an
appropriate number of ghost points extend past the $z$ axis.

A simple hack to get around this complication is to specify the grid
\texttt{byspacing}, but to set the \texttt{allow\_grid\_resize}
parameter. In this case, the $(dx,dy,dz)$ values can be set in the
parameter file, and the grid ranges will be calculated based on the
above criteria. Note that this involves modifying the ``standard''
behaviour of the \texttt{CartGrid3D} thorn (to place the $z$-axis
along one edge of the grid). This is accomplished by internally
resetting the grid type to \texttt{byrange}, and then specifying
ranges which are compatible with the cartoon conditions and with the
appropriate number of grid points and ghost zones. Note that this
involves a reset of otherwise non-steerable parameters, and so must be
scheduled at \texttt{CCTK\_RECOVER\_PARAMETERS} and before the grid is
set up.

\section{Interaction with other thorns}
\label{sec:interaction}

Some thorns will need to know that a cartoon grid is being used in
order to function properly. In principle, they should use the
\texttt{cartoon\_active} parameter to check whether a cartoon grid is
in use.

In practice, however, to avoid dependencies on \texttt{Cartoon2D}, it
is often the case that the source code for such thorns make use of
\texttt{\#ifdef} statements to check whether \texttt{Cartoon2D} has
been compiled in. Then, to check whether a cartoon grid is active,
other thorn-specific parameters need to be set. For instance, if the
\texttt{ADM\_BSSN} thorn is being used for evolution, then the
parameter
\begin{verbatim}
  adm_bssn::cartoon = "yes"
\end{verbatim}
must be set. Similarly, the \texttt{ADM} evolution system requires
\texttt{adm::cartoon} to be set. The \texttt{AHFinder} thorn requires
that the parameter
\begin{verbatim}
  ahfinder::ahf_cartoon = "yes"
\end{verbatim}
be set in order to use a cartoon grid.

\emph{Note that many thorns will require cartoon-specific
modifications in order to be used with the \texttt{Cartoon2D}
thorn. It should not be assumed that a given thorn will work with
\texttt{Cartoon2D} unless it is specifically mentioned in the
documentation or source code.}

\section{Source code}

The interface to the \texttt{Cartoon2D} thorn is through the routines
contained in the \texttt{Cartoon2DBC.c} source file. These routines
can be used by other thorns (eg. evolution thorns) to apply cartoon
boundary conditions to grid functions whenever it is appropriate to do
so. The three interface functions are:

\begin{description}

  \item[\texttt{BndCartoon2DGN(cGH *GH, int tensortype, const char*
      group)}]
    This function applies the cartoon boundary condition to the grid
    functions in the group specified by the \emph{group} parameter.
    The \emph{tensortype} argument parameter should be one of
    \begin{description}
      \item[\texttt{TENSORTYPE\_SCALAR}] a scalar;
      \item[\texttt{TENSORTYPE\_U}] a vector (single, upper index);
      \item[\texttt{TENSORTYPE\_DDSYM}] a symmetric tensor with two
	lower indices.
    \end{description}

  \item[\texttt{BndCartoon2DVN(cGH *GH, int tensortype, const char
      *impvarname)}]
    This function is like \texttt{BndCartoon2DGN()}, but operates on
    individual grid functions rather than groups.

  \item[\texttt{BndCartoon2DVI(cGH *GH, int tensortype, int vi)}]
    This function is like \texttt{BndCartoon2DGN()}, but operates
    takes a grid function index to indicate the grid function to which
    the condition should be applied.

\end{description}

A corresponding Fortran wrapper for each of these functions is also
included.

Interpolation operators are defined in the file
\texttt{interpolate.c}.

The file \texttt{SetSym.c} contains routines which re-label the flat
faces of the cartoon grid as \texttt{CARTOON\_NOSYM} boundaries. This
prevents any other boundary condition (eg. physical or symmetry
conditions) from being applied to these faces, as they are determined
by the \texttt{Cartoon2D} thorn. These routines need to be scheduled
before the first time boundary conditions are applied to any grid
function.

The file \texttt{SetGrid.c} contains the code to reset the grid
dimensions in a cartoon-friendly way (see Section \ref{sec:regrid}) if
the \texttt{byspacing} grid type is used. In this case, appropriate
coordinate grid ranges are calculated so that the $(dx,dy,dz)$ values
are preserved, and the parameters specifying the grid are reset. These
are non-steerable, and so the routine must be run during the
\texttt{CCTK\_RECOVER\_PARAMETERS} time bin in order to work. (This time
bin is run even when checkpoint recovery is not being used.)

\begin{thebibliography}{9}
  \bibitem{Alcubierre-etal-2001}
    Miguel Alcubierre, Steven Brandt, Bernd Br\"ugmann, Daniel Holz,
    Edward Seidel, Ryoji Takahashi, Jonathan Thornburg (2001)
    \emph{Symmetry without symmetry: Numerical simulation of
    Axisymmetric Systems using Cartesian Grids}, Int. J. Mod. Phys. D,
    \textbf{10}, 273--289, (gr-qc/9908012).
\end{thebibliography}

% Do not delete next line
% END CACTUS THORNGUIDE

\end{document}

```



Reference answer:
src_code: /*@@
   @file      Cartoon2DBC.c
   @date      Thu Nov  4 13:35:00 MET 1999
   @author    Sai Iyer
   @desc 
              Apply Cartoon2D boundary conditions
              An implementation of Steve Brandt's idea for doing
              axisymmetry with 3d stencils.
              Cactus 4 version of Bernd Bruegmann's code.             
   @enddesc
 @@*/

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>

#include "cctk.h"
#include "cctk_Arguments.h"
#include "cctk_Parameters.h"
#include "cctk_FortranString.h"

#include "Cartoon2D_tensors.h"
#include "SpaceMask.h"

static const char *rcsid = "$Id$";

CCTK_FILEVERSION(BetaThorns_Cartoon2D_Cartoon2DBC_c);

int BndCartoon2DGN(const cGH *GH, int tensortype, const char *group);
int BndCartoon2DVN(const cGH *GH, int tensortype, const char *var);
int BndCartoon2DVI(const cGH *GH, int tensortype, int prolongtype, int vi);
static CCTK_REAL Cartoon2DInterp(const cGH *GH, CCTK_REAL *f, int i, int di,
                             int ijk, CCTK_REAL x);
static CCTK_REAL Cartoon2DInterp_ENO(const cGH *cctkGH, 
                                    CCTK_REAL *f, int i, int di, 
                                    int ijk, CCTK_REAL x);
CCTK_REAL interpolate_local(int order, CCTK_REAL x0, CCTK_REAL dx, 
                            CCTK_REAL y[], CCTK_REAL x, 
                            CCTK_INT excised_points[]);
CCTK_REAL interpolate_eno(CCTK_INT order, CCTK_REAL x0, CCTK_REAL dx,
                          CCTK_REAL y[], CCTK_REAL x,
                          CCTK_INT excised_points[]);
void CCTK_FCALL CCTK_FNAME(BndCartoon2DVI)(int *retval, const cGH **GH, 
                int *tensortype, int *vi);
void CCTK_FCALL CCTK_FNAME(BndCartoon2DVN)(int *retval, const cGH **GH,
                int *tensortype, ONE_FORTSTRING_ARG);
void CCTK_FCALL CCTK_FNAME(BndCartoon2DGN)
                          (int *retval, const cGH **GH, int *tensortype, ONE_FORTSTRING_ARG);


void Cartoon2D_InitExcisionVars(CCTK_ARGUMENTS);
void SetupExcisionVars(CCTK_ARGUMENTS);

/* set boundaries of a grid tensor assuming axisymmetry 
   - handles lower boundary in x
   - does not touch other boundaries
   - coordinates and rotation coefficients are independent of z and should
     be precomputed
   - this is also true for the constants in interpolator, but this may 
     be too messy
   - minimizes conceptual warpage, not computationally optimized
*/
/* uses rotation matrix and its inverse as linear transformation on
   arbitrary tensor indices -- I consider this a good compromise
   between doing index loops versus using explicit formulas in
   cos(phi) etc with messy signs 
*/

/* BndCartoon2DVI is called only on the first variable of a group.  It
   then is automatically applied to the remaining variables in the
   group, using the tensortype argument to determine how many there
   are and how to treat them. */

int BndCartoon2DVI(const cGH *cctkGH, int tensortype, int prolongtype, int vi)

{
  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;

  char * groupname;

  int i, j, k, di, dj, ijk;
  int jj, n, s;
  int lnx, lnz, ny;
  int gi, var0;
  int n_vars;
  int timelevel;
  CCTK_REAL x, y, rho;
  CCTK_REAL lx0, dx0, dy0;
  CCTK_REAL rxx, rxy, ryx, ryy;
  CCTK_REAL sxx, sxy, syx, syy;
  CCTK_REAL f[100], fx, fy, fz, fxx, fxy, fxz, fyy, fyz, fzz;
  CCTK_REAL *t, *tx, *ty, *tz, *txx, *txy, *txz, *tyy, *tyz, *tzz;

  gi = CCTK_GroupIndexFromVarI (vi);
  assert (gi >= 0);
  n_vars = CCTK_NumVarsInGroupI (gi);
  assert (n_vars > 0);
  var0 = CCTK_FirstVarIndexI (gi);
  assert (var0 >= 0);

  if (n_vars > 100)
  {
    groupname = CCTK_GroupName (gi);
    CCTK_VWarn (1, __LINE__, __FILE__, CCTK_THORNSTRING,
                "Group \"%s\" has more than 100 variables -- cannot apply Cartoon boundary condition",
                groupname);
    free (groupname);
    return -1;
  }

  switch (tensortype) {
  case TENSORTYPE_SCALAR:
    break;
  case TENSORTYPE_U:
    if (n_vars != 3)
    {
      groupname = CCTK_GroupName (gi);
      CCTK_VWarn (1, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Group \"%s\" is declared with the tensor type U, but does not contain 3 variables -- cannot apply Cartoon boundary condition",
                  groupname);
      free (groupname);
      return -1;
    }
    break;
  case TENSORTYPE_DDSYM:
    if (n_vars != 6)
    {
      groupname = CCTK_GroupName (gi);
      CCTK_VWarn (1, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Group \"%s\" is declared with the tensor type DDSYM, but does not contain 6 variables -- cannot apply Cartoon boundary condition",
                  groupname);
      free (groupname);
      return -1;
    }
    break;
  default:
    CCTK_WARN(0,"Tensor type not recognized by Cartoon2D.");
  }
    
  s = cctkGH->cctk_nghostzones[0];
  lnx = cctkGH->cctk_lsh[0];
  lnz = cctkGH->cctk_lsh[2];
  ny  = cctkGH->cctk_gsh[1];

  dx0 = CCTK_DELTA_SPACE(0);
  dy0 = CCTK_DELTA_SPACE(1);
  lx0 = CCTK_ORIGIN_SPACE(0) + dx0 * cctk_lbnd[0];

  timelevel = 0; 

  /* Cactus loop in C:  grid points with y = 0 */
  /* compare thorn_BAM_Elliptic */
  /* strides used in stencils, should be provided by cactus */
  di = 1;
  dj = lnx;

  /* y = 0 */
  assert (ny % 2 == 1);
  j = ny/2;

  /* make sure that the input data is synced */
  CCTK_SyncGroupI(cctkGH, gi);

  /* z-direction: include lower and upper boundary */
  for (k = 0; k < lnz; k++)

  /* y-direction: as many zombies as the evolution stencil needs */
#if 0
  for (jj = 1, dj = jj*lnx; jj <= ny/2; jj++, dj = jj*lnx) 
#else
  for (jj = 0, dj = jj*lnx; jj <= ny/2; jj++, dj = jj*lnx) 
#endif

  /* x-direction: zombie for x < 0, including upper boundary for extrapol */
#if 0
  for (i = -s; i < lnx; i++) {
#else
  for (i = 0; i < lnx; i++)
    if (! (i >= s && jj == 0)) {
#endif

    /* index into linear memory */
    ijk = CCTK_GFINDEX3D(cctkGH,i,j,k);
   
    /* what a neat way to hack in the zombie for x < 0, y = 0 */
    if (i < 0) {i += s; ijk += s; dj = 0;}

    /* compute coordinates (could also use Cactus grid function) */
    x = lx0 + dx0 * i;
#if 0
    y = (dj) ? dy0 * jj : 0;
#else
    y = dy0 * jj;
#endif
    rho = sqrt(x*x + y*y);

    /* compute rotation matrix
       note that this also works for x <= 0 (at lower boundary in x)
       note that rho is nonzero by definition if cactus checks dy ... 
    */
    rxx = x/rho;
    ryx = y/rho;
    rxy = -ryx;
    ryy = rxx;

    /* inverse rotation matrix, assuming detr = 1 */
    sxx = ryy;
    syx = -ryx;
    sxy = -rxy;
    syy = rxx;

    /* interpolate grid functions */
    switch (prolongtype)
    {
      case PROLONG_NONE:
        /* no-op */
        return (0);
        break;
      case PROLONG_LAGRANGE:
        for (n = 0; n < n_vars; n++)
          f[n] = Cartoon2DInterp(cctkGH, cctkGH->data[var0+n][timelevel], 
                                 i, di, ijk, rho); 
        break;
      case PROLONG_ENO:
        for (n = 0; n < n_vars; n++)
          f[n] = Cartoon2DInterp_ENO(cctkGH, cctkGH->data[var0+n][timelevel], i, 
                                     di, ijk, rho); 
        break;
      default:
        CCTK_WARN(0, "Prolongation type not recognized by Cartoon2D.");
    }

    /* rotate grid tensor by matrix multiplication */
    if (tensortype == TENSORTYPE_SCALAR) {
      /* Scalar groups can have arbitrary many components; these
         "components" are then all scalars.  */
      for (n = 0; n < n_vars; n++)
      {
        t = cctkGH->data[var0 + n][timelevel];
        t[ijk+dj] = f[n];
        t[ijk-dj] = f[n];
      }
    }
    else if (tensortype == TENSORTYPE_U) {
      tx = cctkGH->data[vi][timelevel];
      ty = cctkGH->data[vi+1][timelevel];
      tz = cctkGH->data[vi+2][timelevel];
      fx = f[0];
      fy = f[1];
      fz = f[2];

      tx[ijk+dj] = rxx * fx + rxy * fy;
      ty[ijk+dj] = ryx * fx + ryy * fy;
      tz[ijk+dj] = fz;

      tx[ijk-dj] = sxx * fx + sxy * fy;
      ty[ijk-dj] = syx * fx + syy * fy;
      tz[ijk-dj] = fz;
    }
    else if (tensortype == TENSORTYPE_DDSYM) {
      txx = cctkGH->data[vi][timelevel];
      txy = cctkGH->data[vi+1][timelevel];
      txz = cctkGH->data[vi+2][timelevel];
      tyy = cctkGH->data[vi+3][timelevel];
      tyz = cctkGH->data[vi+4][timelevel];
      tzz = cctkGH->data[vi+5][timelevel];
      fxx = f[0];
      fxy = f[1];
      fxz = f[2];
      fyy = f[3];
      fyz = f[4];
      fzz = f[5];
      
      txx[ijk+dj] = fxx*sxx*sxx + 2*fxy*sxx*syx + fyy*syx*syx;
      tyy[ijk+dj] = fxx*sxy*sxy + 2*fxy*sxy*syy + fyy*syy*syy;
      txy[ijk+dj] = fxx*sxx*sxy + fxy*(sxy*syx+sxx*syy) + fyy*syx*syy;
      txz[ijk+dj] = fxz*sxx + fyz*syx;
      tyz[ijk+dj] = fxz*sxy + fyz*syy;
      tzz[ijk+dj] = fzz;

      txx[ijk-dj] = fxx*rxx*rxx + 2*fxy*rxx*ryx + fyy*ryx*ryx;
      tyy[ijk-dj] = fxx*rxy*rxy + 2*fxy*rxy*ryy + fyy*ryy*ryy;
      txy[ijk-dj] = fxx*rxx*rxy + fxy*(rxy*ryx+rxx*ryy) + fyy*ryx*ryy;
      txz[ijk-dj] = fxz*rxx + fyz*ryx;
      tyz[ijk-dj] = fxz*rxy + fyz*ryy;
      tzz[ijk-dj] = fzz;
    } 
    else {
      return(-1);
    }

#if 0
    /* what a neat way to hack out the zombies for x < 0, y = 0 */
    if (dj == 0) {i -= s; ijk -= s; dj = jj*lnx;}
#endif
  }

  /* syncs needed after interpolation (actually only for x direction) */
  CCTK_SyncGroupI(cctkGH, gi);

  return(0);
}

void CCTK_FCALL CCTK_FNAME(BndCartoon2DVI)
                          (int *retval, const cGH **GH, int *tensortype, int *vi)
{
  *retval = BndCartoon2DVI(*GH, *tensortype, PROLONG_LAGRANGE, *vi);
}

int BndCartoon2DVN(const cGH *GH, int tensortype, const char *inpvarname)
{
  DECLARE_CCTK_PARAMETERS

  int vi;

  if (verbose) printf("Cartoon2D called for %s\n", inpvarname);
  vi = CCTK_VarIndex(inpvarname);

  return(BndCartoon2DVI(GH, tensortype, PROLONG_LAGRANGE, vi));
}

void CCTK_FCALL CCTK_FNAME(BndCartoon2DVN)
                          (int *retval, const cGH **GH, int *tensortype, ONE_FORTSTRING_ARG)
{
  ONE_FORTSTRING_CREATE(impvarname)
  *retval = BndCartoon2DVN(*GH, *tensortype, impvarname);
  free(impvarname);
}

int BndCartoon2DGN(const cGH *GH, int tensortype, const char *group)
{
  DECLARE_CCTK_PARAMETERS

  if (verbose && GH->cctk_iteration==1) CCTK_VInfo(CCTK_THORNSTRING,"Cartoon2D called for %s (message appears only once)", group);
  
  return(BndCartoon2DVI(GH, tensortype, PROLONG_LAGRANGE, 
                        CCTK_FirstVarIndex(group)));
}

void CCTK_FCALL CCTK_FNAME(BndCartoon2DGN)
                          (int *retval, const cGH **GH, int *tensortype, ONE_FORTSTRING_ARG)
{
  ONE_FORTSTRING_CREATE(group)
  *retval = BndCartoon2DGN(*GH, *tensortype, group);
  free(group);
}

/* interpolation on x-axis */
static CCTK_REAL Cartoon2DInterp(const cGH *cctkGH, CCTK_REAL *f, 
                                 int i, int di, int ijk, CCTK_REAL x)
{
  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;

  CCTK_REAL x0;
  CCTK_REAL lx0, dx0;
  CCTK_REAL y[6], r;
  int lnx;
  int n, offset;

  CCTK_REAL *old_mask;
  CCTK_INT *new_mask;

  CCTK_INT excised_points[6];

  if (*excision_active < 0)
  {
    SetupExcisionVars((cGH*)cctkGH);
  }

  if (old_excision)
  {
    old_mask = (CCTK_REAL*)CCTK_VarDataPtrI(cctkGH, 0, *old_mask_vi);
  }
  else
  {
    old_mask = NULL;
  }
  
  if (new_excision)
  {
    new_mask = (CCTK_INT*)CCTK_VarDataPtrI(cctkGH, 0, *new_mask_vi);
  }
  else
  {
    new_mask = NULL;
  }

  /* 
     If this boundary point is excised then we return 
     without doing anything 
  */

  if (*excision_active)
  {
    if ((*excision_active == 1)||(*excision_active == 3))
    {
      if (old_mask[ijk] < 0.75)
      {
        return f[ijk];
      }
    }
    if ((*excision_active == 2)||(*excision_active == 3))
    {
      if (SpaceMask_CheckStateBits(new_mask, (ijk),
                                   *new_excision_field,
                                   *new_excision_descriptor))
      {
        return f[ijk];
      }
    }
  }  

  lnx = cctkGH->cctk_lsh[0];
    
  dx0 = CCTK_DELTA_SPACE(0);
  lx0 = CCTK_ORIGIN_SPACE(0) + dx0 * cctk_lbnd[0];

  /* find i such that x(i) < x <= x(i+1)
     for rotation on entry always x > x(i), but sometimes also x > x(i+1) */
  while (x > lx0 + dx0 * (i+1)) {i++; ijk++;}

  /* first attempt to interface to JT's interpolator */

  /* offset of stencil, note that rotation leads to x close to x0 
       for large x */
  offset = order/2;
  
  /* shift stencil at boundaries */
  /* note that for simplicity we don't distinguish between true boundaries
     and decomposition boundaries: the sync fixes that! */
  if (i-offset < 0) offset = i;
  if (i-offset+order >= lnx) offset = i+order-lnx+1;
  
  /* fill in info */
  /* fills in old data near axis, but as long as stencil at axis is 
     centered, no interpolation happens anyway */
  x0 = lx0 + dx0 * (i-offset);
  for (n = 0; n <= order; n++) {
    y[n] = f[ijk-offset+n];
    excised_points[n] = 0;
    if (*excision_active)
    {
      if ((*excision_active == 1)||(*excision_active == 3))
      {
        excised_points[n] = (old_mask[ijk-offset+n] > 0.75) ? 0 : 1;
      }
      if ((*excision_active == 2)||(*excision_active == 3))
      {
        excised_points[n] |= 
          (SpaceMask_CheckStateBits(new_mask, (ijk-offset+n),
                                    *new_excision_field,
                                    *new_excision_descriptor));
      }
    }
  }
  
  /* call interpolator */
  r = interpolate_local(order, x0, dx0, y, x, excised_points);
  
  return r;
}

static CCTK_REAL Cartoon2DInterp_ENO(const cGH *cctkGH, 
                                    CCTK_REAL *f, int i, int di, 
                                    int ijk, CCTK_REAL x)
{
  
  DECLARE_CCTK_PARAMETERS;
  DECLARE_CCTK_ARGUMENTS;
  
  CCTK_REAL x0;
  CCTK_REAL lx0, dx0;
  CCTK_REAL y[11], r;
  int lnx;
  int n, offset;
  /* int tmp_order = 2; */

  CCTK_REAL *old_mask;
  CCTK_INT *new_mask;

  CCTK_INT excised_points[11];

  if (*excision_active < 0)
  {
    SetupExcisionVars((cGH*)cctkGH);
  }

  if (old_excision)
  {
    old_mask = (CCTK_REAL*)CCTK_VarDataPtrI(cctkGH, 0, *old_mask_vi);
  }
  else
  {
    old_mask = NULL;
  }
  
  if (new_excision)
  {
    new_mask = (CCTK_INT*)CCTK_VarDataPtrI(cctkGH, 0, *new_mask_vi);
  }
  else
  {
    new_mask = NULL;
  }

  /* 
     If this boundary point is excised then we return 
     without doing anything 
  */

  if (*excision_active)
  {
    if ((*excision_active == 1)||(*excision_active == 3))
    {
      if (old_mask[ijk] < 0.75)
      {
        return f[ijk];
      }
    }
    if ((*excision_active == 2)||(*excision_active == 3))
    {
      if (SpaceMask_CheckStateBits(new_mask, (ijk),
                                   *new_excision_field,
                                   *new_excision_descriptor))
      {
        return f[ijk];
      }
    }
  }
  
  lnx = cctkGH->cctk_lsh[0];
  
  dx0 = CCTK_DELTA_SPACE(0);
  lx0 = CCTK_ORIGIN_SPACE(0) + dx0 * cctk_lbnd[0];
  
  /* find i such that x(i) < x <= x(i+1)
     for rotation on entry always x > x(i), but sometimes also x > x(i+1) */
  while (x > lx0 + dx0 * (i+1)) {i++; ijk++;}
  
  /* first attempt to interface to JT's interpolator */
  
  /* offset of stencil, note that rotation leads to x close to x0 
     for large x */
  offset = eno_order;
/*   offset = tmp_order; */
  
  /* shift stencil at boundaries */
  /* note that for simplicity we don't distinguish between true boundaries
     and decomposition boundaries: the sync fixes that! */
  if (i-offset < 0) offset = i;
/*   if (i-offset+4 >= lnx) offset = i+5-lnx; */
/*   if (i-offset+2*tmp_order >= lnx) offset = i+2*tmp_order+1-lnx; */
  if (i-offset+2*eno_order >= lnx) offset = i+2*eno_order+1-lnx;
  
  /* fill in info */
  /* fills in old data near axis, but as long as stencil at axis is 
     centered, no interpolation happens anyway */  
  x0 = lx0 + dx0 * (i-offset);
/*   for (n = 0; n < 2 * tmp_order + 1; ++n)  */
  for (n = 0; n < 2 * eno_order + 1; ++n) 
  {
    y[n] = f[ijk-offset+n];
    excised_points[n] = 0;
    if (*excision_active)
    {
      if ((*excision_active == 1)||(*excision_active == 3))
      {
        excised_points[n] = (old_mask[ijk-offset+n] > 0.75) ? 1 : 0;
      }
      if ((*excision_active == 2)||(*excision_active == 3))
      {
        excised_points[n] |= 
          (SpaceMask_CheckStateBits(new_mask, (ijk-offset+n),
                                    *new_excision_field,
                                    *new_excision_descriptor));
      }
    }
  }
  
  /* call interpolator */
/*   r = interpolate_eno(tmp_order, x0, dx0, y, x); */
  r = interpolate_eno(eno_order, x0, dx0, y, x, excised_points);
  
  return r;
}

void Cartoon2D_InitExcisionVars(CCTK_ARGUMENTS)
{
  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;

  *excision_active = -1;
  *old_mask_vi = -1;
  *new_mask_vi = -1;
  *new_excision_field = -1;
  *new_excision_descriptor = -1;

}

void SetupExcisionVars(CCTK_ARGUMENTS)
{
  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;

  if (old_excision)
  {
    *old_mask_vi = CCTK_VarIndex(old_style_excision_var);
  }
  else
  {
    *old_mask_vi = -1;
  }
  if (new_excision)
  {
    *new_mask_vi = CCTK_VarIndex(new_style_excision_var);
  }
  else
  {
    *new_mask_vi = -1;
  }

  if (*old_mask_vi > -1)
  {
    if (*new_mask_vi > -1)
    {
      *excision_active = 3;
      *new_excision_field = SpaceMask_GetTypeBits(new_mask_field_name);
      *new_excision_descriptor = SpaceMask_GetStateBits(new_mask_field_name,
                                                        new_mask_excised_name);
    }
    else
    {
      *excision_active = 1;
    }
  }
  else
  {
    if (*new_mask_vi > -1)
    {
      *excision_active = 2;
      *new_excision_field = SpaceMask_GetTypeBits(new_mask_field_name);
      *new_excision_descriptor = SpaceMask_GetStateBits(new_mask_field_name,
                                                        new_mask_excised_name);
    }
    else
    {
      *excision_active = 0;
    }
  }
  
}