Task: Implement rotatingsymmetry90.h for CactusNumerical/RotatingSymmetry90 thorn

thorn_name: CactusNumerical/RotatingSymmetry90
src_filename: rotatingsymmetry90.h
interface: # Interface definition for thorn RotatingSymmetry90

IMPLEMENTS: RotatingSymmetry90

USES INCLUDE HEADER: Slab.h



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 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



CCTK_INT FUNCTION                         \
    GetRegriddingEpoch                    \
        (CCTK_POINTER_TO_CONST IN cctkGH)
USES FUNCTION GetRegriddingEpoch

CCTK_INT FUNCTION                         \
    GetRefinementLevel                    \
        (CCTK_POINTER_TO_CONST IN cctkGH)
USES FUNCTION GetRefinementLevel

CCTK_INT FUNCTION                         \
    GetRefinementLevels                   \
        (CCTK_POINTER_TO_CONST IN cctkGH)
USES FUNCTION GetRefinementLevels

CCTK_INT FUNCTION                         \
    GetLocalComponents                    \
        (CCTK_POINTER_TO_CONST IN cctkGH)
USES FUNCTION GetLocalComponents



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

schedule: # Schedule definitions for thorn RotatingSymmetry90

SCHEDULE Rot90_CheckTensorTypes AT paramcheck
{
  LANG: C
} "Check tensor type definitions for consistency"

SCHEDULE Rot90_RegisterSymmetry IN SymmetryRegister
{
  LANG: C
} "Register symmetry boundaries"

SCHEDULE Rot90_ComputeLevelExtent IN BoundaryConditions BEFORE Rot90_ApplyBC
{
  LANG: C
  OPTIONS: LEVEL
} "Compute extent of level"

SCHEDULE Rot90_ApplyBC IN BoundaryConditions
{
  LANG: C
} "Apply 90 degree rotational symmetry boundary condition"

param: # Parameter definitions for thorn RotatingSymmetry90

BOOLEAN verbose "Produce screen output while applying boundary conditions"
{
} "no"



BOOLEAN poison_boundaries "Fill the symmetry boundary with a poison value before the symmetry is applied" STEERABLE=always
{
} "no"

CCTK_INT poison_value "Integer value (0..255) used to poison new timelevels (with memset)" STEERABLE=always
{
  0:255 :: "Must fit into a byte.  Use 0 for zero, 255 for nan, and e.g. 113 for a large value."
} 254

BOOLEAN check_boundaries "Check the boundaries for poison" STEERABLE=always
{
} "no"

configuration: # Configuration definition for thorn RotatingSymmetry90

REQUIRES THORNS: Slab

context: interpolate.c:
```
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

#include "rotatingsymmetry90.h"



/* 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 == ipow (atensor->dim, atensor->rank));
  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
Rot90_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, vectorlength;
    int table;
    int ierr;
    cGroup group;
    
    numvars = CCTK_NumVarsInGroupI(gi);
    if (numvars == 0) continue;
    assert (numvars>0);
    firstvar = CCTK_FirstVarIndexI(gi);
    assert (firstvar>=0);
    ierr = CCTK_GroupData (gi, &group);
    assert (!ierr);
    vectorlength = group.vectorlength;
    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 (4, __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 (4, __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 (4, __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, special case */
      assert ((numvars == DIM*vectorlength) ||
              (numvars == DIM && vectorlength == DIM));
    } else if (CCTK_EQUALS (tensortypealias, "4u")
               || CCTK_EQUALS (tensortypealias, "4d"))
    {
      /* 4-vector */
      assert ((numvars == (DIM+1)*vectorlength) ||
              (numvars == (DIM+1) && vectorlength == (DIM+1)));
    } else if (CCTK_EQUALS (tensortypealias, "uu")
               || CCTK_EQUALS (tensortypealias, "ud")
               || CCTK_EQUALS (tensortypealias, "du")
               || CCTK_EQUALS (tensortypealias, "dd")) {
      /* tensor */
      assert (numvars == DIM*DIM*vectorlength);
    } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
               || CCTK_EQUALS (tensortypealias, "dd_sym")) {
      /* symmetric tensor */
      assert (numvars == DIM*(DIM+1)/2*vectorlength);
    } else if (CCTK_EQUALS (tensortypealias, "ddd_sym")) {
      /* 3rd rank tensor, symmetric in last 2 indices */
      assert (numvars == DIM*DIM*(DIM+1)/2*vectorlength);
    } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
               || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
      /* symmetric 4-tensor */
      assert (numvars == (DIM+1)*(DIM+2)/2*vectorlength);
    } else {
      char * groupname = CCTK_GroupName(gi);
      assert (groupname);
      CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "Illegal tensor type alias for group \"%s\" (%s)",
                  groupname, tensortypealias);
      free (groupname);
    }
    
  }
}
  


/* Symmetry interpolation */
CCTK_INT
Rot90_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));
  new_faces = faces;
  new_faces &= ~ (1 << 0);
  new_faces &= ~ (1 << 2);
  
  
  
  /* Copy coordinates */
  for (d=0; d<DIM; ++d) {
    new_interp_coords[d] = malloc (N_interp_points * sizeof(CCTK_REAL));
    assert (N_interp_points == 0 || new_interp_coords[d]);
  }
  
  /* Fold coordinates */
  for (n=0; n<N_interp_points; ++n) {
    /* Is the point outside the domain? */
    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];
    if (x < 0) {
      /* Rotate the point by 180 degrees */
      x = -x;
      y = -y;
    }
    if (y < 0) {
      /* Rotate the point clockwise by 90 degrees */
      CCTK_REAL const t = x;
      x = -y;
      y =  t;
    }
    ((CCTK_REAL *)new_interp_coords[0])[n] = x;
    ((CCTK_REAL *)new_interp_coords[1])[n] = y;
    ((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 (N_interp_points == 0 || new_output_arrays[m]);
  }
  
  
  
  /* 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 spatial 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]==-1
            || (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) {
  if (output_array_indices[m]!=-1) {
    
    int vi, gi;
    int numvars, firstvar, vectorlength;
    cGroup group;
    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);
    ierr = CCTK_GroupData (gi, &group);
    assert (!ierr);
    vectorlength = group.vectorlength;
    
    /* 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 (4, __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, "4scalar")) {
      /* 4-scalar */
      if (numvars != 1) {
        char * groupname = CCTK_GroupName(gi);
        assert (groupname);
        CCTK_VWarn (4, __LINE__, __FILE__, CCTK_THORNSTRING,
                    "Group \"%s\" has the tensor type alias \"4-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*vectorlength) ||
              (numvars == DIM && vectorlength == DIM));
      tensortype = &vector;
      basevar = firstvar;
      if (numvars == vectorlength) {
        var = (vi - basevar);
      } else {
        var = (vi - basevar) / vectorlength;
      }
    } else if (CCTK_EQUALS (tensortypealias, "4u")
               || CCTK_EQUALS (tensortypealias, "4d"))
    {
      /* 4-vector */
      assert (numvars == (DIM+1)*vectorlength);
      if ((vi - firstvar) / vectorlength == 0) {
        /* temporal component */
        int const off = 0;
        tensortype = &scalar;
        basevar = firstvar + off * vectorlength;
        var = (vi - basevar) / vectorlength;
      } else {
        /* spatial components */
        int const off = 1;
        tensortype = &vector;
        basevar = firstvar + off * vectorlength;
        var = (vi - basevar) / vectorlength;
      }
    } else if (CCTK_EQUALS (tensortypealias, "uu")
               || CCTK_EQUALS (tensortypealias, "ud")
               || CCTK_EQUALS (tensortypealias, "du")
               || CCTK_EQUALS (tensortypealias, "dd")) {
      /* tensor */
      assert (numvars == DIM*DIM*vectorlength);
      tensortype = &tensor;
      basevar = firstvar;
      var = (vi - basevar) / vectorlength;
    } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
               || CCTK_EQUALS (tensortypealias, "dd_sym")) {
      /* symmetric tensor */
      assert (numvars == DIM*(DIM+1)/2*vectorlength);
      tensortype = &symmtensor;
      basevar = firstvar;
      var = (vi - basevar) / vectorlength;
    } else if (CCTK_EQUALS (tensortypealias, "ddd_sym")) {
      /* 3rd rank tensor, symmetric in last 2 indices */
      assert (numvars == DIM*DIM*(DIM+1)/2*vectorlength);
      tensortype = &symmtensor3b;
      basevar = firstvar;
      var = (vi - basevar) / vectorlength;
    } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
               || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
      /* symmetric 4-tensor */
      assert (numvars == (DIM+1)*(DIM+2)/2*vectorlength);
      if ((vi - firstvar) / vectorlength == 0) {
        /* temporal-temporal component */
        int const off = 0;
        tensortype = &scalar;
        basevar = firstvar + off * vectorlength;
        var = (vi - basevar) / vectorlength;
      } else if (vi < firstvar+DIM+1) {
        /* temporal-spatial components */
        int const off = 1;
        tensortype = &vector;
        basevar = firstvar + off * vectorlength;
        var = (vi - basevar) / vectorlength;
      } else {
        /* spatial-spatial components */
        int const off = DIM+1;
        tensortype = &symmtensor;
        basevar = firstvar + off * vectorlength;
        var = (vi - basevar) / vectorlength;
      }
    } 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) {
  if (output_array_indices[m]!=-1) {
    
    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 x = ((CCTK_REAL const *)interp_coords[0])[n];
      CCTK_REAL y = ((CCTK_REAL const *)interp_coords[1])[n];
      
      int myindices[MAX_RANK+1];
      int parity;
      
      for (r=0; r<tensortype->rank; ++r) {
        myindices[r] = indices[r];
      }
      parity = +1;
      
      /* Is the point outside the domain? */
      if (x<0) {
        /* Undo 180 degree rotation */
        for (r=0; r<tensortype->rank; ++r) {
          switch (myindices[r]) {
          case 0: parity *= -1; break;
          case 1: parity *= -1; break;
          case 2: break;
          default: assert (0);
          }
        }
        x = -x;
        y = -y;
      }
      if (y<0) {
        /* Undo clockwise 90 degree rotation (i.e. rotate
           counterclockwise) */
        for (r=0; r<tensortype->rank; ++r) {
          switch (myindices[r]) {
          case 0: myindices[r]=1; parity *= +1; break;
          case 1: myindices[r]=0; parity *= -1; break;
          case 2: break;
          default: assert (0);
          }
        }
        {
          CCTK_REAL const t = x;
          x = -y;
          y =  t;
        }
      }
      
      {
        int mycomponent;
        int myvar;
        int mm;
        
        mycomponent = 0;
        for (r=0; r<tensortype->rank; ++r) {
          mycomponent = mycomponent * tensortype->dim + myindices[r];
        }
        assert (mycomponent>=0 && mycomponent<tensortype->ncomps);
        myvar = tensortype->vars[mycomponent];
        assert (myvar>=0 && myvar<tensortype->nvars);
        mm = vars[myvar];
        assert (mm>=0 && mm<N_output_arrays);
        
        ((CCTK_REAL *)output_arrays[m])[n]
          = parity * ((CCTK_REAL const *)new_output_arrays[mm])[n];
      }
      
    } /* 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;
}

```

make.code.defn:
```
# Main make.code.defn file for thorn RotatingSymmetry90

# Source files in this directory
SRCS = interpolate.c rotatingsymmetry90.c registersymmetry.c

# Subdirectories containing source files
SUBDIRS = 

```

registersymmetry.c:
```
#include "cctk.h"
#include "cctk_Arguments.h"
#include "cctk_Parameters.h"

#include "rotatingsymmetry90.h"


void Rot90_RegisterSymmetry (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");
  }
  
  for (f=0; f<6; ++f) {
    faces[f] = 0;
    width[f] = 0;
  }
  
  faces[0] = 1;
  width[0] = nboundaryzones[0];
  faces[2] = 1;
  width[2] = nboundaryzones[2];
  
  handle = SymmetryRegister ("rotating_symmetry_90");
  if (handle < 0) {
    CCTK_WARN (0, "Could not register symmetry boundary condition");
  }
  
  ierr = SymmetryRegisterGrid (cctkGH, handle, faces, width);
  if (ierr < 0) {
    CCTK_WARN (0, "Could not register the symmetry boundaries -- probably some other thorn has already registered the same boundary faces for a different symmetry");
  }
  
  ierr = SymmetryRegisterGridInterpolator
    (cctkGH, handle, Rot90_SymmetryInterpolate);
  if (ierr < 0) {
    CCTK_WARN (0, "Could not register the symmetry interpolator");
  }
}

```

rotatingsymmetry90.c:
```
#include <cctk.h>
#include <cctk_Arguments.h>
#include <cctk_Parameters.h>

#include <util_ErrorCodes.h>
#include <util_Table.h>

#include <Slab.h>

#include "rotatingsymmetry90.h"

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



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



static int convert_index (int const step,
                          int const index,
                          int const * restrict const alldirs,
                          int * restrict const parity)
{
  int srcindex;
  assert (index>=0 && index<DIM);
  assert (alldirs);
  assert (alldirs[0]>=0 && alldirs[0]<DIM);
  assert (alldirs[1]>=0 && alldirs[1]<DIM);
  assert (alldirs[1] != alldirs[0]);
  assert (parity);
  assert (abs(*parity) == 1);
  switch (step) {
  case 0:
    /* x face: counterclockwise, 90 degrees */
    if      (index == alldirs[0]) srcindex = alldirs[1], *parity *= -1;
    else if (index == alldirs[1]) srcindex = alldirs[0], *parity *= +1;
    else                          srcindex = index     , *parity *= +1;
    break;
  case 1:
    /* y face: clockwise, 90 degrees */
    if      (index == alldirs[0]) srcindex = alldirs[1], *parity *= +1;
    else if (index == alldirs[1]) srcindex = alldirs[0], *parity *= -1;
    else                          srcindex = index     , *parity *= +1;
    break;
  case 2:
    /* xy edge: 180 degrees */
    if      (index == alldirs[0]) srcindex = alldirs[0], *parity *= -1;
    else if (index == alldirs[1]) srcindex = alldirs[1], *parity *= -1;
    else                          srcindex = index     , *parity *= +1;
    break;
  default:
    assert (0);
  }
  return srcindex;
}

/* bbox, lbnd and ubnd of combined level box */
static int global_bbox[2*DIM];
static int global_lbnd[DIM], global_ubnd[DIM];
static int extent_valid_for_iteration = -1;

int BndRot90VI (cGH const * restrict const cctkGH,
                int const nvars,
                CCTK_INT const * restrict const vis)
{
  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;
  
  int * restrict gis;
  cGroup group;
  cGroupDynamicData data;
  char * restrict fullname;
  void * restrict * restrict varptrs;
  int * restrict vartypes;
  int * restrict vectorlengths;
  int firstvar;
  
  char tensortypealias[100];
  int numvars;
  int index;
  int srcvi;
  void const * restrict * restrict srcptrs;
  int * restrict parities;
  
  int fake_bbox[2*DIM];
  
  CCTK_REAL x0[DIM], dx[DIM];
  CCTK_REAL origin[DIM], dorigin[DIM];
  int avoid_origin[DIM];
  int offset[DIM];              /* offset 0..1 due to avoid_origin */
  
  struct xferinfo * restrict xferinfo;
  int options;
  
  int have_global_bbox, have_local_bbox;
  
  int var;
  
  int alldirs[2];
  int step;
  int ndirs;                    /* 1 for face, 2 for edge */
  int dir[DIM];                 /* current face or edge */
  int otherdir[DIM];            /* the other direction of the rotation */
  int q;                        /* 0..ndirs-1 */
  int d;                        /* 0..group.dim-1 */
  int ierr;
  
  /* Check arguments */
  assert (cctkGH);
  assert (nvars==0 || vis);
  for (var=0; var<nvars; ++var) {
    assert (vis[var]>=0 && vis[var]<CCTK_NumVars());
  }
  
  if (verbose) {
    for (var=0; var<nvars; ++var) {
      fullname = CCTK_FullName(vis[var]);
      assert (fullname);
      CCTK_VInfo (CCTK_THORNSTRING,
                  "Applying 90 degree rotating symmetry boundary conditions to \"%s\"",
                  fullname);
      free (fullname);
    }
  }
  
  /* Return early if there is nothing to do */
  if (nvars == 0) return 0;
  
  /* Get and check group info */
  assert (nvars>0);
  gis = malloc (nvars * sizeof *gis);
  assert (nvars==0 || gis);
  varptrs = malloc (nvars * sizeof *varptrs);
  assert (nvars==0 || varptrs);
  vartypes = malloc (nvars * sizeof  *vartypes);
  assert (nvars==0 || vartypes);
  vectorlengths = malloc (nvars * sizeof  *vectorlengths);
  assert (nvars==0 || vectorlengths);
  for (var=0; var<nvars; ++var) {
    gis[var] = CCTK_GroupIndexFromVarI (vis[var]);
    assert (gis[var]>=0 && gis[var]<CCTK_NumGroups());
    
    ierr = CCTK_GroupData (gis[var], &group);
    assert (!ierr);
    assert (group.grouptype == CCTK_GF);
    assert (group.disttype == CCTK_DISTRIB_DEFAULT);
    
    ierr = CCTK_GroupDynamicData (cctkGH, gis[var], &data);
    assert (!ierr);
    
    varptrs[var] = CCTK_VarDataPtrI (cctkGH, 0, vis[var]);
    assert (varptrs[var]);
    
    vartypes[var] = group.vartype;
    assert (vartypes[var] >= 0);

    vectorlengths[var] = group.vectorlength;
    assert (vectorlengths[var]>=0);
    assert (vectorlengths[var]==1 || group.vectorgroup);
  }
  
  for (d=0; d<group.dim; ++d) {
    x0[d] = CCTK_ORIGIN_SPACE(d);
    dx[d] = CCTK_DELTA_SPACE(d);
  }
  
  {
    assert(extent_valid_for_iteration == cctk_iteration);
  
    for (d=0; d<DIM; ++d) {
      fake_bbox[2*d  ] = data.lbnd[d] == global_lbnd[d];
      fake_bbox[2*d+1] = data.ubnd[d] == global_ubnd[d];
    }
  }
  
  /* directions */
  alldirs[0] = 0;
  alldirs[1] = 1;
  
  /* Find grid point that corresponds to the origin */
  for (q=0; q<2; ++q) {
    d = alldirs[q];
    /* x0 + dx * origin == 0 */
    origin[d] = - x0[d] / dx[d];
    dorigin[d] = origin[d] - floor(origin[d]);
    if (fabs(dorigin[d]) < 1.0e-6 || fabs(dorigin[d] - 1.0) < 1.0e-6) {
      avoid_origin[d] = 0;
    } else if (fabs(dorigin[d] - 0.5) < 1.0e-6) {
      avoid_origin[d] = 1;
    } else {
      CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                  "The coordinate origin in the %c-direction falls neither onto a grid point nor into the middle between two grid points.",
                  "xyz"[d]);
    }
    offset[d] = avoid_origin[d] ? 0 : 1;
  }
  
  parities = malloc (nvars * sizeof *parities);
  assert (nvars==0 || parities);
  srcptrs = malloc (nvars * sizeof *srcptrs);
  assert (nvars==0 || srcptrs);
  
  for (step=0; step<3; ++step) {
    switch (step) {
    case 0:
      /* x face */
      ndirs = 1;
      dir[0] = 0;
      otherdir[0] = 1;
      break;
    case 1:
      /* y face */
      ndirs = 1;
      dir[0] = 1;
      otherdir[0] = 0;
      break;
    case 2:
      /* xy edge */
      ndirs = 2;
      dir[0] = 0;
      dir[1] = 1;
      otherdir[0] = 1;
      otherdir[1] = 0;
      break;
    default:
      assert (0);
    }
    
    for (var=0; var<nvars; ++var) {
      
      /* Cactus lays out variables of a group
       * CCTK_REAL vel[3] {vx,vy,vz}
       * like this:
       * vx[0], vx[1], vx[2], vy[0], vy[1], ...
       * so the component x,y,z is given by index / vectorlength and the
       * vector index vx[i] by index % vectorlength.
       */
      const int vectorlength = vectorlengths[var];

      /* Find tensor type, source variable, and parity */
      {
        int table;

        numvars = CCTK_NumVarsInGroupI(gis[var]);
        assert (numvars>0);
        firstvar = CCTK_FirstVarIndexI(gis[var]);
        assert (firstvar>=0);
        index = vis[var] - firstvar;
        assert (index>=0 && index<numvars);
        table = CCTK_GroupTagsTableI(gis[var]);
        assert (table>=0);

        ierr = Util_TableGetString
          (table, sizeof tensortypealias, tensortypealias, "tensortypealias");
        if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
          /* assume a scalar */
          if (numvars != 1) {
            static int * restrict didwarn = 0;
            if (! didwarn) {
              didwarn = calloc (CCTK_NumGroups(), sizeof *didwarn);
            }
            if (! didwarn[gis[var]]) {
              char * groupname = CCTK_GroupName(gis[var]);
              assert (groupname);
              CCTK_VWarn (2, __LINE__, __FILE__, CCTK_THORNSTRING,
                          "Group \"%s\" has no tensor type and contains more than one element -- treating these as \"scalar\"",
                          groupname);
              free (groupname);
              didwarn[gis[var]] = 1;
            }
          }
          strcpy (tensortypealias, "scalar");
        } else if (ierr<0) {
          char * groupname = CCTK_GroupName(gis[var]);
          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, "scalar")) {
          /* scalar */
        } else if (CCTK_EQUALS (tensortypealias, "4scalar")) {
          /* 4-scalar */
        } else if (CCTK_EQUALS (tensortypealias, "u")
                   || CCTK_EQUALS (tensortypealias, "d")) {
          /* vector */
          assert ((numvars == DIM*vectorlength) ||
                  (numvars == DIM && vectorlength == DIM));
        } else if (CCTK_EQUALS (tensortypealias, "4u")
                   || CCTK_EQUALS (tensortypealias, "4d")) {
          /* 4-vector */
          assert (numvars == (DIM+1)*vectorlength);
        } else if (CCTK_EQUALS (tensortypealias, "uu")
                   || CCTK_EQUALS (tensortypealias, "ud")
                   || CCTK_EQUALS (tensortypealias, "du")
                   || CCTK_EQUALS (tensortypealias, "dd")) {
          /* tensor */
          assert (numvars == DIM*DIM*vectorlength);
        } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
                   || CCTK_EQUALS (tensortypealias, "dd_sym")) {
          /* symmetric tensor */
          assert (numvars == DIM*(DIM+1)/2*vectorlength);
        } else if (CCTK_EQUALS (tensortypealias, "ddd_sym")) {
          /* 3rd rank tensor, symmetric in last 2 indices */
          assert (numvars == DIM*DIM*(DIM+1)/2*vectorlength);
        } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
                   || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
          /* symmetric 4-tensor */
          assert (numvars == (DIM+1)*(DIM+2)/2*vectorlength);
        } else if (CCTK_EQUALS (tensortypealias, "weylscalars_real")) {
          /* Weyl scalars, stored as 10 real numbers */
          assert (numvars == 10*vectorlength);
        } else {
          char * groupname = CCTK_GroupName(gis[var]);
          assert (groupname);
          CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                      "Illegal tensor type alias for group \"%s\"",
                      groupname);
          free (groupname);
        }
      }
      
      parities[var] = +1;
      if (CCTK_EQUALS (tensortypealias, "scalar")) {
        /* scalar */
        srcvi = vis[var];
        /* do nothing */
      } else if (CCTK_EQUALS (tensortypealias, "4scalar")) {
        /* 4-scalar */
        srcvi = vis[var];
        /* do nothing */
      } else if (CCTK_EQUALS (tensortypealias, "u")
                 || CCTK_EQUALS (tensortypealias, "d")) {
        /* vector */
        int srcindex;
        /* special case for vel[i] */
        assert (index>=0 && index<DIM*vectorlength);
        if(numvars == DIM && vectorlength == DIM) {
          srcindex = convert_index (step, index, alldirs, &parities[var]);
        } else {
          srcindex = index % vectorlength + vectorlength *
                     convert_index (step, index/vectorlength, alldirs, &parities[var]);
        }
        srcvi = firstvar + srcindex;
      } else if (CCTK_EQUALS (tensortypealias, "4u")
                 || CCTK_EQUALS (tensortypealias, "4d")) {
        /* 4-vector */
        assert (index>=0 && index<(DIM+1)*vectorlength);
        if (index/vectorlength==0) {
          /* temporal component */
          srcvi = firstvar;
        } else {
          /* spatial components */
          int srcindex;
          int const off = 1;
          srcindex = index % vectorlength + vectorlength * (off +
                     convert_index (step, index/vectorlength-off, alldirs, &parities[var]));
          srcvi = firstvar + srcindex;
        }
      } else if (CCTK_EQUALS (tensortypealias, "uu")
                 || CCTK_EQUALS (tensortypealias, "ud")
                 || CCTK_EQUALS (tensortypealias, "du")
                 || CCTK_EQUALS (tensortypealias, "dd")) {
        /* tensor */
        assert (numvars == DIM*DIM);
        int index1, index2;
        int srcindex1, srcindex2;
        int srcindex;
        assert (index>=0 && index<DIM*DIM*vectorlength);
        index1 = (index / vectorlength) / DIM;
        index2 = (index / vectorlength) % DIM;
        srcindex1 = convert_index (step, index1, alldirs, &parities[var]);
        srcindex2 = convert_index (step, index2, alldirs, &parities[var]);
        srcindex = index % vectorlength +
                   (srcindex1 * DIM + srcindex2) * vectorlength;
        srcvi = firstvar + srcindex;
      } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
                 || CCTK_EQUALS (tensortypealias, "dd_sym")) {
        /* symmetric tensor */
        int index1, index2;
        int srcindex1, srcindex2;
        int srcindex;
        assert (index>=0 && index<DIM*(DIM+1)/2*vectorlength);
        {
          int const expand1[DIM*(DIM+1)/2] = { 0,0,0,1,1,2 };
          int const expand2[DIM*(DIM+1)/2] = { 0,1,2,1,2,2 };
          index1 = expand1[index/vectorlength];
          index2 = expand2[index/vectorlength];
        }
        srcindex1 = convert_index (step, index1, alldirs, &parities[var]);
        srcindex2 = convert_index (step, index2, alldirs, &parities[var]);
        {
          int const compact[DIM][DIM] = { { 0,1,2 }, { 1,3,4 }, { 2,4,5 } };
          srcindex = index % vectorlength +
                     compact[srcindex1][srcindex2]*vectorlength;
        }
        srcvi = firstvar + srcindex;
      } else if (CCTK_EQUALS (tensortypealias, "ddd_sym")) {
        /* 3rd rank tensor, symmetric in last 2 indices */
        int index1, index2, index3;
        int srcindex1, srcindex2, srcindex3;
        int srcindex;
        assert (index>=0 && index<DIM*DIM*(DIM+1)/2*vectorlength);
        {
          int const expand1[DIM*DIM*(DIM+1)/2] =
            { 0,0,0,0,0,0, 1,1,1,1,1,1, 2,2,2,2,2,2 };
          int const expand2[DIM*DIM*(DIM+1)/2] =
            { 0,0,0,1,1,2, 0,0,0,1,1,2, 0,0,0,1,1,2 };
          int const expand3[DIM*DIM*(DIM+1)/2] =
            { 0,1,2,1,2,2, 0,1,2,1,2,2, 0,1,2,1,2,2 };
          index1 = expand1[index/vectorlength];
          index2 = expand2[index/vectorlength];
          index3 = expand3[index/vectorlength];
        }
        srcindex1 = convert_index (step, index1, alldirs, &parities[var]);
        srcindex2 = convert_index (step, index2, alldirs, &parities[var]);
        srcindex3 = convert_index (step, index3, alldirs, &parities[var]);
        {
          int const compact[DIM][DIM][DIM] = 
            { { {  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 } } };
          srcindex = index % vectorlength +
                     compact[srcindex1][srcindex2][srcindex3]*vectorlength;
        }
        srcvi = firstvar + srcindex;
      } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
                 || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
        /* symmetric 4-tensor */
        assert (index>=0 && index<(DIM+1)*(DIM+2)/2*vectorlength);
        if (index/vectorlength==0) {
          /* temporal-temporal component */
          srcvi = firstvar;
        } else if (index<(DIM+1)*vectorlength) {
          /* temporal-spatial components */
          int srcindex;
          int const off = 1;
          srcindex = index % vectorlength + vectorlength * (off +
                     convert_index (step, index/vectorlength-off, alldirs, &parities[var]));
          srcvi = firstvar + srcindex;
        } else {
          /* spatial-spatial components */
          int index1, index2;
          int srcindex1, srcindex2;
          int srcindex;
          int const off = DIM+1;
          {
            int const expand1[DIM*(DIM+1)/2] = { 0,0,0,1,1,2 };
            int const expand2[DIM*(DIM+1)/2] = { 0,1,2,1,2,2 };
            index1 = expand1[index/vectorlength-off];
            index2 = expand2[index/vectorlength-off];
          }
          srcindex1 = convert_index (step, index1, alldirs, &parities[var]);
          srcindex2 = convert_index (step, index2, alldirs, &parities[var]);
          {
            int const compact[DIM][DIM] = { { 0,1,2 }, { 1,3,4 }, { 2,4,5 } };
            srcindex = index % vectorlength + vectorlength *
                       (off + compact[srcindex1][srcindex2]);
          }
          srcvi = firstvar + srcindex;
        }
      } else if (CCTK_EQUALS (tensortypealias, "weylscalars_real")) {
        /* Weyl scalars, stored as 10 reals */
        assert (index>=0 && index<10*vectorlength);
        srcvi = vis[var];
      } else {
        assert (0);
      }
      
      srcptrs[var] = CCTK_VarDataPtrI (cctkGH, 0, srcvi);
      assert (srcptrs[var]);
      
    } /* for var */
    
    have_global_bbox = 1;
    for (q=0; q<ndirs; ++q) {
      have_global_bbox = have_global_bbox && global_bbox[2*dir[q]];
    }
    if (have_global_bbox) {
      
      for (q=0; q<ndirs; ++q) {
        assert (data.nghostzones[dir[q]] >= 0);
        
        if (data.gsh[otherdir[q]] < data.nghostzones[dir[q]] + data.nghostzones[otherdir[q]] + offset[otherdir[q]]) {
          assert (nvars > 0);
          var = 0;
          CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
                      "The group \"%s\" has in the %c-direction only %d grid points.  "
                      "This is not large enough for a 90 degree rotating symmetry boundary that is %d grid points wide in the %c-direction.  "
                        "The group needs to have at least %d grid points in the %c-direction.",
                      CCTK_GroupNameFromVarI(vis[var]), "xyz"[dir[q]], data.gsh[dir[q]],
                      data.nghostzones[dir[q]],  "xyz"[otherdir[q]],
                      data.nghostzones[dir[q]] + data.nghostzones[otherdir[q]] + offset[otherdir[q]], "xyz"[dir[q]]);
        }
      }
      
      if (poison_boundaries) {
        /* poison destination grid points */
        int have_bnd = 1;
        for (q=0; q<ndirs; ++q) {
          have_bnd = have_bnd && cctkGH->cctk_bbox[2*dir[q]];
        }
        if (have_bnd) {
          
          int imin[3], imax[3];
          int i, j, k;
          for (d=0; d<3; ++d) {
            imin[d] = 0;
            imax[d] = cctk_lsh[d];
          }
          for (q=0; q<ndirs; ++q) {
            imax[dir[q]] = cctk_nghostzones[dir[q]];
          }
          
          var = 0;
          
          assert (group.dim == 3);
          switch (group.vartype) {
          case CCTK_VARIABLE_INT:
            /* do nothing */
            break;
          case CCTK_VARIABLE_REAL: {
            CCTK_REAL * restrict const varptr = varptrs[var];
            for (k=imin[0]; k<imax[2]; ++k) {
              for (j=imin[1]; j<imax[1]; ++j) {
                for (i=imin[2]; i<imax[0]; ++i) {
                  const int ind = CCTK_GFINDEX3D(cctkGH, i, j, k);
                  memset (&varptr[ind], poison_value, sizeof varptr[ind]);
                }
              }
            }
            break;
          }
          case CCTK_VARIABLE_COMPLEX:
            /* do nothing */
            break;
          default:
            assert (0);
          } /* switch grouptype */
        } /* if bbox */
      } /* if poison_boundaries */
      
      /* Allocate slab transfer description */
      xferinfo = malloc(group.dim * sizeof *xferinfo);
      assert (xferinfo);
      
      /* Fill in slab transfer description */
      for (d=0; d<group.dim; ++d) {
        xferinfo[d].src.gsh         = data.gsh        [d];
        xferinfo[d].src.lbnd        = data.lbnd       [d];
        xferinfo[d].src.lsh         = data.lsh        [d];
        xferinfo[d].src.ash         = data.ash        [d];
        xferinfo[d].src.lbbox       = fake_bbox       [2*d  ];
        xferinfo[d].src.ubbox       = fake_bbox       [2*d+1];
        xferinfo[d].src.nghostzones = data.nghostzones[d];
        xferinfo[d].src.off         = 0;
        xferinfo[d].src.str         = 1;
        xferinfo[d].src.len         = data.gsh        [d];
        
        xferinfo[d].dst.gsh         = data.gsh        [d];
        xferinfo[d].dst.lbnd        = data.lbnd       [d];
        xferinfo[d].dst.lsh         = data.lsh        [d];
        xferinfo[d].dst.ash         = data.ash        [d];
        xferinfo[d].dst.lbbox       = fake_bbox       [2*d  ];
        xferinfo[d].dst.ubbox       = fake_bbox       [2*d+1];
        xferinfo[d].dst.nghostzones = data.nghostzones[d];
        xferinfo[d].dst.off         = 0;
        xferinfo[d].dst.str         = 1;
        xferinfo[d].dst.len         = data.gsh        [d];
        
        xferinfo[d].xpose = d;
        xferinfo[d].flip  = 0;
      }
      
      switch (ndirs) {
      case 1:
        /* face */
        for (q=0; q<ndirs; ++q) {
          xferinfo[otherdir[q]].src.off = data.nghostzones[otherdir[q]] + offset[otherdir[q]];
          xferinfo[otherdir[q]].src.len = data.nghostzones[     dir[q]];
          xferinfo[     dir[q]].dst.off = 0;
          xferinfo[     dir[q]].dst.len = data.nghostzones[     dir[q]];
          
          xferinfo[     dir[q]].xpose = otherdir[q];
          xferinfo[otherdir[q]].xpose =      dir[q];
          /* Note: For rotations other than in the xy plane, the
             flipping might be different */
          xferinfo[     dir[q]].flip = 1;
        }
        break;
      case 2:
        /* edge */
        for (q=0; q<ndirs; ++q) {
          xferinfo[otherdir[q]].src.off = data.nghostzones[otherdir[q]] + offset[otherdir[q]];
          xferinfo[otherdir[q]].src.len = data.nghostzones[     dir[q]];
          xferinfo[     dir[q]].dst.off = 0;
          xferinfo[     dir[q]].dst.len = data.nghostzones[     dir[q]];
          
          xferinfo[dir[q]].flip = 1;
        }
        break;
      default:
        assert (0);
      }
      
      if (CCTK_IsFunctionAliased("GetLocalComponents")) {
        int const num_local_components = GetLocalComponents(cctkGH);
        if (num_local_components > 1) {
          CCTK_WARN (CCTK_WARN_ABORT,
                     "TAT/Slab can only be used if there is a single local component per MPI process");
        }
      }
      
      options = Util_TableCreateFromString ("useghosts=1");
      assert (options>=0);
      
      /* Can we use the more efficient interface? */
      if (CCTK_IsFunctionAliased ("GetRegriddingEpoch")) {
        static int epoch = -1;
        static struct slabsetup *restrict *restrict slab_setups[3] =
          { NULL, NULL, NULL};
        static int num_reflevels = 0;
        int const new_epoch = GetRegriddingEpoch (cctkGH);
        if (new_epoch > epoch) {
          epoch = new_epoch;
          /* Delete old slabbing setups */
          for (int s=0; s<3; ++s) {
            for (int rl=0; rl<num_reflevels; ++rl) {
              if (slab_setups[s][rl]) {
                Slab_MultiTransfer_Finalize (cctkGH, slab_setups[s][rl]);
              }
            }
            free (slab_setups[s]);
          }
          /* Allocate space for new slabbing setups */
          num_reflevels = GetRefinementLevels (cctkGH);
          for (int s=0; s<3; ++s) {
            slab_setups[s] = malloc (num_reflevels * sizeof *slab_setups[s]);
            assert (slab_setups[s]);
            for (int rl=0; rl<num_reflevels; ++rl) {
              slab_setups[s][rl] = NULL;
            }
          }
        }
        /* Use existing slabbing setup */
        assert (slab_setups[step]);
        int const reflevel = GetRefinementLevel (cctkGH);
        assert (reflevel>=0 && reflevel<num_reflevels);
        if (!slab_setups[step][reflevel]) {
          slab_setups[step][reflevel] = 
            Slab_MultiTransfer_Init (cctkGH, group.dim, xferinfo, options);
        }
        assert (slab_setups[step][reflevel]);
        ierr = Slab_MultiTransfer_Apply
          (cctkGH, slab_setups[step][reflevel],
           nvars, vartypes, srcptrs, vartypes, varptrs);
        assert (!ierr);
      } else {
        /* We can't use the more efficient interface, so fall back to
           the old one */
        ierr = Slab_MultiTransfer
          (cctkGH, group.dim, xferinfo, options,
           nvars, vartypes, srcptrs, vartypes, varptrs);
        assert (!ierr);
      }
      
      ierr = Util_TableDestroy (options);
      assert (!ierr);
      
      if (check_boundaries) {
        /* check destination grid points for poison */
        int have_bnd = 1;
        for (q=0; q<ndirs; ++q) {
          have_bnd = have_bnd && cctkGH->cctk_bbox[2*dir[q]];
        }
        if (have_bnd) {
          
          int imin[3], imax[3];
          int i, j, k;
          for (d=0; d<3; ++d) {
            imin[d] = 0;
            imax[d] = cctk_lsh[d];
          }
          for (q=0; q<ndirs; ++q) {
            imax[dir[q]] = cctk_nghostzones[dir[q]];
          }
          
          var = 0;
          
          assert (group.dim == 3);
          switch (group.vartype) {
          case CCTK_VARIABLE_INT:
            /* do nothing */
            break;
          case CCTK_VARIABLE_REAL: {
            int poison_found = 0;
            int nonfinite_found = 0;
            CCTK_REAL const * restrict const varptr = varptrs[var];
            CCTK_REAL poison;
            memset (&poison, poison_value, sizeof poison);
            for (k=imin[0]; k<imax[2]; ++k) {
              for (j=imin[1]; j<imax[1]; ++j) {
                for (i=imin[2]; i<imax[0]; ++i) {
                  const int ind = CCTK_GFINDEX3D(cctkGH, i, j, k);
                  if (memcmp (&varptr[ind], &poison, sizeof poison) == 0) {
                    printf ("   poison ijk=[%d,%d,%d] val=%g\n",
                            i, j, k, (double)varptr[ind]);
                    poison_found = 1;
                  }
                  if (! isfinite(varptr[ind])) {
                    printf ("   nonfinite ijk=[%d,%d,%d] val=%g\n",
                            i, j, k, (double)varptr[ind]);
                    nonfinite_found = 1;
                  }
                }
              }
            }
            if (poison_found || nonfinite_found) {
              if (poison_found) {
                printf ("Poison found:\n");
              }
              if (nonfinite_found) {
                printf ("Non-finite number found:\n");
              }
              printf ("   levfac=[%d,%d,%d]\n", cctk_levfac[0], cctk_levfac[1], cctk_levfac[2]);
              printf ("   origin_space=[%g,%g,%g]\n", cctk_origin_space[0], cctk_origin_space[1], cctk_origin_space[2]);
              printf ("   delta_space=[%g,%g,%g]\n", cctk_delta_space[0], cctk_delta_space[1], cctk_delta_space[2]);
              printf ("   lbnd=[%d,%d,%d]\n", cctk_lbnd[0], cctk_lbnd[1], cctk_lbnd[2]);
              printf ("   lsh=[%d,%d,%d]\n", cctk_lsh[0], cctk_lsh[1], cctk_lsh[2]);
              printf ("   gsh=[%d,%d,%d]\n", cctk_gsh[0], cctk_gsh[1], cctk_gsh[2]);
              printf ("   bbox=[%d,%d,%d,%d,%d,%d]\n", cctk_bbox[0], cctk_bbox[1], cctk_bbox[2], cctk_bbox[3], cctk_bbox[4], cctk_bbox[5]);
              if (poison_found) {
                CCTK_WARN (CCTK_WARN_ABORT, "Poison found in symmetry regions -- there is an error in this thorn");
              }
              if (nonfinite_found) {
                CCTK_WARN (CCTK_WARN_ALERT, "Non-finite number found in symmetry regions");
              }
            }
            break;
          }
          case CCTK_VARIABLE_COMPLEX:
            /* do nothing */
            break;
          default:
            assert (0);
          } /* switch grouptype */
        } /* if bbox */
      } /* if check_boundaries */
      
      /* take parity into account */
      have_local_bbox = 1;
      for (q=0; q<ndirs; ++q) {
        have_local_bbox = have_local_bbox && cctk_bbox[2*dir[q]];
      }
      if (have_local_bbox) {
        for (var=0; var<nvars; ++var) {
          assert (abs(parities[var]) == 1);
          if (parities[var] == -1) {
            int imin[DIM], imax[DIM];
            int i, j, k;
            for (d=0; d<DIM; ++d) {
              imin[d] = xferinfo[d].dst.off;
              imax[d] = xferinfo[d].dst.off + xferinfo[d].dst.len;
              imin[d] -= cctk_lbnd[d];
              imax[d] -= cctk_lbnd[d];
              if (imin[d] < 0) imin[d] = 0;
              if (imax[d] >= cctk_lsh[d]) imax[d] = cctk_lsh[d];
            }
            assert (group.dim == DIM);
            switch (group.vartype) {
            case CCTK_VARIABLE_INT: {
              CCTK_INT * restrict const varptr = varptrs[var];
              for (k=imin[2]; k<imax[2]; ++k) {
                for (j=imin[1]; j<imax[1]; ++j) {
                  for (i=imin[0]; i<imax[0]; ++i) {
                    const int ind = CCTK_GFINDEX3D(cctkGH, i, j, k);
                    varptr[ind] *= -1;
                  }
                }
              }
              break;
            }
            case CCTK_VARIABLE_REAL: {
              CCTK_REAL * restrict const varptr = varptrs[var];
              for (k=imin[2]; k<imax[2]; ++k) {
                for (j=imin[1]; j<imax[1]; ++j) {
                  for (i=imin[0]; i<imax[0]; ++i) {
                    const int ind = CCTK_GFINDEX3D(cctkGH, i, j, k);
                    varptr[ind] *= -1;
                  }
                }
              }
              break;
            }
            case CCTK_VARIABLE_COMPLEX: {
              CCTK_COMPLEX const czero = CCTK_Cmplx (0.0, 0.0);
              CCTK_COMPLEX * restrict const varptr = varptrs[var];
              for (k=imin[2]; k<imax[2]; ++k) {
                for (j=imin[1]; j<imax[1]; ++j) {
                  for (i=imin[0]; i<imax[0]; ++i) {
                    const int ind = CCTK_GFINDEX3D(cctkGH, i, j, k);
                    CCTK_COMPLEX * restrict const ptr = & varptr[ind];
                    * ptr = CCTK_CmplxSub (czero, * ptr);
                  }
                }
              }
              break;
            }
            default:
              assert (0);
            } /* switch grouptype */
          }
        } /* for var */
      
      } /* if bbox */
      
      free (xferinfo);
      
    } /* if have_global_bbox */
    
  } /* for step */
  
  free (gis);
  free (varptrs);
  free (vectorlengths);
  free (vartypes);
  free (parities);
  free (srcptrs);
  
  return 0;
}



void Rot90_ComputeLevelExtent (CCTK_ARGUMENTS)
{
  DECLARE_CCTK_ARGUMENTS;
  
  int nvars;

  assert (cctkGH);
  
  nvars = Boundary_SelectedGVs (cctkGH, 0, 0, 0, 0, 0, 0);
  assert (nvars>=0);
  
  if (nvars==0) return;

  {
    int max_handle;
    int d;                        /* 0..group.dim-1 */
    int ierr;
    CCTK_INT local[4*DIM], global[4*DIM];
    max_handle = CCTK_ReductionArrayHandle ("maximum");
    if (max_handle<0) CCTK_WARN (0, "Could not obtain reduction handle");
    
    for (d=0; d<2*DIM; ++d) local[      d] =  cctkGH->cctk_bbox[d];
    for (d=0; d<  DIM; ++d) local[2*DIM+d] = -cctkGH->cctk_lbnd[d];
    for (d=0; d<  DIM; ++d) local[3*DIM+d] =  cctkGH->cctk_ubnd[d];
    ierr = CCTK_ReduceLocArrayToArray1D
      (cctkGH, -1, max_handle, local, global, 4*DIM, CCTK_VARIABLE_INT);
    assert(!ierr);
    for (d=0; d<2*DIM; ++d) global_bbox[d] =  global[      d];
    for (d=0; d<  DIM; ++d) global_lbnd[d] = -global[2*DIM+d];
    for (d=0; d<  DIM; ++d) global_ubnd[d] =  global[3*DIM+d];

    /* record when we ran to have some sanity check against using old data in
     * global variables */
    extent_valid_for_iteration = cctk_iteration;
  }
}



void Rot90_ApplyBC (CCTK_ARGUMENTS)
{
  DECLARE_CCTK_ARGUMENTS;
  
  int nvars;
  CCTK_INT * restrict indices;
  CCTK_INT * restrict faces;
  CCTK_INT * restrict widths;
  CCTK_INT * restrict tables;
  int var;
  int ierr;
  
  assert (cctkGH);
  
  nvars = Boundary_SelectedGVs (cctkGH, 0, 0, 0, 0, 0, 0);
  assert (nvars>=0);
  
  if (nvars==0) return;
  
  indices = malloc (nvars * sizeof *indices);
  assert (indices);
  faces = malloc (nvars * sizeof *faces);
  assert (faces);
  widths = malloc (nvars * sizeof *widths);
  assert (widths);
  tables = malloc (nvars * sizeof *tables);
  assert (tables);
  
  ierr =  Boundary_SelectedGVs
    (cctkGH, nvars, indices, faces, widths, tables, 0);
  assert (ierr == nvars);
  
  for (var=0; var<nvars; ++var) {
    assert (indices[var]>=0 && indices[var]<CCTK_NumVars());
    assert (widths[var]>=0);
  }
  
  ierr = BndRot90VI (cctkGH, nvars, indices);
  assert (!ierr);
  
  free (indices);
  free (faces);
  free (widths);
  free (tables);
}

```


combined_doc_context: ## README (README):
```
Cactus Code Thorn RotatingSymmetry90
Author(s)    : Erik Schnetter <schnetter@cct.lsu.edu>
Maintainer(s): Erik Schnetter <schnetter@cct.lsu.edu>
Licence      : GPL
--------------------------------------------------------------------------

1. Purpose

Provide a 90 degree rotational symmetry boundary condition.  This is
the kind of symmetry that exists e.g. in a single black hole evolution
when the black hole has spin.  The same setup also admits an
additional bitant symmetry.

Currently the symmetry is fixed to be located at the lower x and y
boundaries, so that there is a rotation about the z axis.

The coordinate thorn does not know about this symmetry.  You will have
to set up the domain such that you have an upper quadrant in the x and
y directions.

This symmetry boundary condition can be mixed with arbitrary symmetry
conditions in the z direction, e.g. bitant or periodicity.

```

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

```tex
=== documentation.tex ===
% *======================================================================*
%  Cactus Thorn template for ThornGuide documentation
%  Author: Ian Kelley
%  Date: Sun Jun 02, 2002
%
%  Thorn documentation in the latex file doc/documentation.tex
%  will be included in ThornGuides built with the Cactus make system.
%  The scripts employed by the make system automatically include
%  pages about variables, parameters and scheduling parsed from the
%  relevant thorn CCL files.
%
%  This template contains guidelines which help to assure that your
%  documentation will be correctly added to ThornGuides. More
%  information is available in the Cactus UsersGuide.
%
%  Guidelines:
%   - Do not change anything before the line
%       % START CACTUS THORNGUIDE",
%     except for filling in the title, author, date, etc. fields.
%        - Each of these fields should only be on ONE line.
%        - Author names should be separated with a \\ or a comma.
%   - You can define your own macros, but they must appear after
%     the START CACTUS THORNGUIDE line, and must not redefine standard
%     latex commands.
%   - To avoid name clashes with other thorns, 'labels', 'citations',
%     'references', and 'image' names should conform to the following
%     convention:
%       ARRANGEMENT_THORN_LABEL
%     For example, an image wave.eps in the arrangement CactusWave and
%     thorn WaveToyC should be renamed to CactusWave_WaveToyC_wave.eps
%   - Graphics should only be included using the graphicx package.
%     More specifically, with the "\includegraphics" command.  Do
%     not specify any graphic file extensions in your .tex file. This
%     will allow us to create a PDF version of the ThornGuide
%     via pdflatex.
%   - References should be included with the latex "\bibitem" command.
%   - Use \begin{abstract}...\end{abstract} instead of \abstract{...}
%   - Do not use \appendix, instead include any appendices you need as
%     standard sections.
%   - For the benefit of our Perl scripts, and for future extensions,
%     please use simple latex.
%
% *======================================================================*
%
% Example of including a graphic image:
%    \begin{figure}[ht]
%       \begin{center}
%          \includegraphics[width=6cm]{MyArrangement_MyThorn_MyFigure}
%       \end{center}
%       \caption{Illustration of this and that}
%       \label{MyArrangement_MyThorn_MyLabel}
%    \end{figure}
%
% Example of using a label:
%   \label{MyArrangement_MyThorn_MyLabel}
%
% Example of a citation:
%    \cite{MyArrangement_MyThorn_Author99}
%
% Example of including a reference
%   \bibitem{MyArrangement_MyThorn_Author99}
%   {J. Author, {\em The Title of the Book, Journal, or periodical}, 1 (1999),
%   1--16. {\tt http://www.nowhere.com/}}
%
% *======================================================================*

% If you are using CVS use this line to give version information

\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}

% The author of the documentation
\author{Erik Schnetter \textless schnetter@cct.lsu.edu\textgreater}

% The title of the document (not necessarily the name of the Thorn)
\title{RotatingSymmetry180}

% the date your document was last changed, if your document is in CVS,
% please use:
\date{2004-04-08}

\maketitle

% Do not delete next line
% START CACTUS THORNGUIDE

% Add all definitions used in this documentation here
%   \def\mydef etc

% Add an abstract for this thorn's documentation
\begin{abstract}

\end{abstract}

% The following sections are suggestive only.
% Remove them or add your own.

\section{Introduction}

\section{Physical System}

\section{Numerical Implementation}

\section{Using This Thorn}

\subsection{Obtaining This Thorn}

\subsection{Basic Usage}

\subsection{Special Behaviour}

\subsection{Interaction With Other Thorns}

\subsection{Examples}

\subsection{Support and Feedback}

\section{History}

\subsection{Thorn Source Code}

\subsection{Thorn Documentation}

\subsection{Acknowledgements}


\begin{thebibliography}{9}

\end{thebibliography}

% Do not delete next line
% END CACTUS THORNGUIDE

\end{document}

```



Reference answer:
src_code: #ifndef ROTATINGSYMMETRY90_H
#define ROTATINGSYMMETRY90_H

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

void Rot90_RegisterSymmetry (CCTK_ARGUMENTS);

int BndRot90VI (cGH const * restrict const cctkGH,
                int const nvars,
                CCTK_INT const * restrict const vis);

void Rot90_ApplyBC (CCTK_ARGUMENTS);

CCTK_INT
Rot90_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);

#endif /* ! defined ROTATINGSYMMETRY90_H */