Task: Implement interpolate.c for CactusNumerical/RotatingSymmetry180 thorn

thorn_name: CactusNumerical/RotatingSymmetry180
src_filename: interpolate.c
interface: # Interface definition for thorn RotatingSymmetry180

IMPLEMENTS: RotatingSymmetry180

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 ARRAY IN which_faces,         \
         CCTK_INT ARRAY IN 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 cctkGH,       \
         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                               \
    GetDomainSpecification                      \
       (CCTK_INT IN size,                       \
        CCTK_REAL ARRAY OUT physical_min,       \
        CCTK_REAL ARRAY OUT physical_max,       \
        CCTK_REAL ARRAY OUT interior_min,       \
        CCTK_REAL ARRAY OUT interior_max,       \
        CCTK_REAL ARRAY OUT exterior_min,       \
        CCTK_REAL ARRAY OUT exterior_max,       \
        CCTK_REAL ARRAY OUT spacing)
USES FUNCTION GetDomainSpecification



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                                       \
    Hyperslab_GlobalMappingByIndex                      \
        (CCTK_POINTER_TO_CONST IN cctkGH,               \
         CCTK_INT IN vindex,                            \
         CCTK_INT IN hdim,                              \
         CCTK_INT ARRAY IN direction,                   \
         CCTK_INT ARRAY IN origin,                      \
         CCTK_INT ARRAY IN extent,                      \
         CCTK_INT ARRAY IN downsample,                  \
         CCTK_INT IN table_handle,                      \
         CCTK_INT CCTK_FPOINTER IN                      \
             conversion_fn                              \
                 (CCTK_INT IN nelems,                   \
                  CCTK_INT IN src_stride,               \
                  CCTK_INT IN dst_stride,               \
                  CCTK_INT IN src_type,                 \
                  CCTK_INT IN dst_type,                 \
                  CCTK_POINTER_TO_CONST IN from,        \
                  CCTK_POINTER IN to),                  \
         CCTK_INT ARRAY OUT hsize)
USES FUNCTION Hyperslab_GlobalMappingByIndex

CCTK_INT FUNCTION                               \
    Hyperslab_GetList                           \
        (CCTK_POINTER_TO_CONST IN cctkGH,       \
         CCTK_INT IN mapping_handle,            \
         CCTK_INT IN num_arrays,                \
         CCTK_INT ARRAY IN procs,               \
         CCTK_INT ARRAY IN vindices,            \
         CCTK_INT ARRAY IN timelevels,          \
         CCTK_INT ARRAY IN hdatatypes,          \
         CCTK_POINTER ARRAY IN hdata,           \
         CCTK_INT ARRAY OUT retvals)
USES FUNCTION Hyperslab_GetList

CCTK_INT FUNCTION                               \
    Hyperslab_FreeMapping                       \
        (CCTK_INT IN mapping_handle)
USES FUNCTION Hyperslab_FreeMapping



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 RotatingSymmetry180

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

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

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

SCHEDULE Rot180_ApplyBC IN BoundaryConditions
{
  LANG: C
} "Apply 180 degree rotational symmetry boundary condition"

param: # Parameter definitions for thorn RotatingSymmetry180

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



KEYWORD hyperslabber "Hyperslabbing mechanism" STEERABLE=always
{
  "TAT/Slab"     :: ""
  "GetHyperslab" :: ""
} "TAT/Slab"



BOOLEAN use_coordbase "Get symmetry boundary location from CoordBase (and ignore symmetry_boundary_* parameters)"
{
} "no"

CCTK_REAL symmetry_boundary_x "x-coordinate of symmetry boundary (ignored if use_coordbase is set)"
{
  *:* :: ""
} 0.0

CCTK_REAL symmetry_boundary_y "y-coordinate of symmetry boundary (ignored if use_coordbase is set)"
{
  *:* :: ""
} 0.0



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 RotatingSymmetry180

REQUIRES THORNS: Slab

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

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

#include <Slab.h>

#include "rotatingsymmetry180.h"

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

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


int BndRot180VI (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 paritiess)[3];
  
  int fake_bbox[6];
  
  CCTK_REAL x0[3], dx[3];
  CCTK_REAL symbnd[3];          /* location of symmetry boundary */
  CCTK_REAL origin[3], dorigin[3];
  int avoid_origin[3], iorigin[3];
  int offset[3];                /* offset 0..1 due to avoid_origin */
  
  struct xferinfo * restrict xferinfo;
  
  int var;
  
  int alldirs[2];
  int dir;                      /* direction of the symmetry face */
  int otherdir;                 /* the other direction of the rotation */
  int q;
  int d;
  int icnt;
  int ierr;
  
  /* Check arguments */
  assert (cctkGH);
  assert (nvars >= 0);
  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 180 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);
  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);
  }
  
  for (d=0; d<group.dim; ++d) {
    x0[d] = CCTK_ORIGIN_SPACE(d);
    dx[d] = CCTK_DELTA_SPACE(d);
  }
  
  /* find parity */
  paritiess = malloc (nvars * sizeof *paritiess);
  assert (nvars==0 || paritiess);
  
  for (var=0; var<nvars; ++var) {
    
    char tensortypealias[100];
    int firstvar, numvars;
    int table;
    int index;
    
    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_VError(__LINE__, __FILE__, CCTK_THORNSTRING,
                  "Error in tensor type alias declaration for group \"%s\"",
                  groupname);
    }
    
    if (CCTK_EQUALS (tensortypealias, "scalar")) {
      paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
    } else if (CCTK_EQUALS (tensortypealias, "4scalar")) {
      paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
    } else if (CCTK_EQUALS (tensortypealias, "u")
               || CCTK_EQUALS (tensortypealias, "d")) {
      assert (numvars == 3);
      paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
      paritiess[var][index] = -1;
    } else if (CCTK_EQUALS (tensortypealias, "4u")
               || CCTK_EQUALS (tensortypealias, "4d")) {
      assert (numvars == 4);
      if (index == 0) {
        paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
      } else {
        paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
        paritiess[var][index-1] = -1;
      }
    } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
               || CCTK_EQUALS (tensortypealias, "dd_sym")) {
      assert (numvars == 6);
      paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
      switch (index) {
      case 0: break;
      case 1: paritiess[var][0] = paritiess[var][1] = -1; break;
      case 2: paritiess[var][0] = paritiess[var][2] = -1; break;
      case 3: break;
      case 4: paritiess[var][1] = paritiess[var][2] = -1; break;
      case 5: break;
      default: CCTK_BUILTIN_UNREACHABLE();
      }
    } else if (CCTK_EQUALS (tensortypealias, "uu")
               || CCTK_EQUALS (tensortypealias, "ud")
               || CCTK_EQUALS (tensortypealias, "du")
               || CCTK_EQUALS (tensortypealias, "dd")) {
      assert (numvars == 9);
      paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
      int const d1 = index % 3;
      int const d2 = index / 3;
      paritiess[var][d1] *= -1;
      paritiess[var][d2] *= -1;
    } else if (CCTK_EQUALS (tensortypealias, "ddd_sym")) {
      assert (numvars == 18);
      paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
      switch (index % 6) {
      case 0: break;
      case 1: paritiess[var][0] = paritiess[var][1] = -1; break;
      case 2: paritiess[var][0] = paritiess[var][2] = -1; break;
      case 3: break;
      case 4: paritiess[var][1] = paritiess[var][2] = -1; break;
      case 5: break;
      default: CCTK_BUILTIN_UNREACHABLE();
      }
      paritiess[var][index/6] *= -1;
    } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
               || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
      assert (numvars == 10);
      if (index == 0) {
        paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
      } else if (index < 4) {
        paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
        paritiess[var][index-1] = -1;
      } else {
        paritiess[var][0] = paritiess[var][1] = paritiess[var][2] = +1;
        switch (index-4) {
        case 0: break;
        case 1: paritiess[var][0] = paritiess[var][1] = -1; break;
        case 2: paritiess[var][0] = paritiess[var][2] = -1; break;
        case 3: break;
        case 4: paritiess[var][1] = paritiess[var][2] = -1; break;
        case 5: break;
        default: CCTK_BUILTIN_UNREACHABLE();
        }
      }
    } else if (CCTK_EQUALS (tensortypealias, "weylscalars_real")) {
      assert (numvars == 10);
      {
        static int const weylparities[10][3] =
          {{+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}};
        for (d=0; d<3; ++d) {
          paritiess[var][d] = weylparities[index][d];
        }
      }
    } else if (CCTK_EQUALS (tensortypealias, "ManualCartesian")) {
	RotatingSymmetry180_GetManualParities(table, gis[var], paritiess[var]);
    } else {
      char * groupname = CCTK_GroupName(gis[var]);
      assert (groupname);
      CCTK_VError(__LINE__, __FILE__, CCTK_THORNSTRING,
                  "Illegal tensor type alias for group \"%s\"",
                  groupname);
    }
    
    assert (abs(paritiess[var][0])==1 && abs(paritiess[var][1])==1
            && abs(paritiess[var][2])==1);
    
  } /* for var */
  
  {
    assert(extent_valid_for_iteration == cctk_iteration);
  
    for (d=0; d<3; ++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 location of symmetry boundary */
  if (use_coordbase) {
    CCTK_INT const size = 3;
    CCTK_REAL physical_min[3];
    CCTK_REAL physical_max[3];
    CCTK_REAL interior_min[3];
    CCTK_REAL interior_max[3];
    CCTK_REAL exterior_min[3];
    CCTK_REAL exterior_max[3];
    CCTK_REAL spacing;
    GetDomainSpecification
      (size,
       physical_min, physical_max,
       interior_min, interior_max,
       exterior_min, exterior_max,
       & spacing);
    symbnd[0] = physical_min[0];
    symbnd[1] = physical_min[1];
    symbnd[2] = physical_min[2];
  } else {
    symbnd[0] = symmetry_boundary_x;
    symbnd[1] = symmetry_boundary_y;
    symbnd[2] = 0;              /* unused */
  }
  
  /* Find grid point that corresponds to the origin */
  for (q=0; q<2; ++q) {
    d = alldirs[q];
    /* x0 + dx * origin == symbnd */
    origin[d] = (symbnd[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_VError(__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]);
    }
    iorigin[d] = floor(origin[d] + (avoid_origin[d] ? 0.5 : 0.0) + 0.5);
    offset[d] = avoid_origin[d] ? 0 : 1;
  }
  
  /* x direction */
  dir = 0;
  otherdir = 1;
  
  assert (data.nghostzones[dir] >= 0);
  
  if (global_bbox[2*dir]) {
    
    if (2*iorigin[otherdir] + offset[otherdir] != cctk_gsh[otherdir]) {
      CCTK_VError(__LINE__, __FILE__, CCTK_THORNSTRING,
                  "The coordinate origin in the %c-direction is not in the centre of the domain.  The boundary condition cannot be applied.",
                  "xyz"[otherdir]);
    }
    
    if (iorigin[dir] != data.nghostzones[dir]) {
      assert (nvars > 0);
      var = 0;
      CCTK_VError(__LINE__, __FILE__, CCTK_THORNSTRING,
                  "The group \"%s\" has in the %c-direction %d symmetry points (grid points outside of the symmetry boundary).  "
                  "This is not equal to the number of ghost zones, which is %d.  "
                  "The number of symmetry points must be equal to the number of ghost zones.",
                  CCTK_GroupNameFromVarI(vis[var]), "xyz"[dir],
                  iorigin[dir], data.nghostzones[dir]);
    }
    
    if (data.gsh[dir] < 2*data.nghostzones[dir] + offset[dir]) {
      assert (nvars > 0);
      var = 0;
      CCTK_VError(__LINE__, __FILE__, CCTK_THORNSTRING,
                  "The group \"%s\" has in the %c-direction only %d grid points.  "
                  "This is not large enough for a 180 degree rotating symmetry boundary that is %d grid points wide.  "
                  "The group needs to have at least %d grid points in that direction.",
                  CCTK_GroupNameFromVarI(vis[var]), "xyz"[dir], data.gsh[dir],
                  data.nghostzones[dir],
                  2*data.nghostzones[dir] + offset[dir]);
    }
    
    if (poison_boundaries) {
      /* poison destination grid points */
      if (cctkGH->cctk_bbox[2*dir]) {
        
        int imin[3], imax[3];
        int i, j, k;
        for (d=0; d<3; ++d) {
          imin[d] = 0;
          imax[d] = cctk_lsh[d];
        }
        imax[dir] = cctk_nghostzones[dir];
        
        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:
          CCTK_BUILTIN_UNREACHABLE();
        } /* 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;
    }
    
    xferinfo[dir].src.off = data.nghostzones[dir] + offset[dir];
    xferinfo[dir].src.len = data.nghostzones[dir];
    xferinfo[dir].dst.off = 0;
    xferinfo[dir].dst.len = data.nghostzones[dir];
    
    xferinfo[     dir].flip = 1;
    xferinfo[otherdir].flip = 1;
    
    if (CCTK_EQUALS (hyperslabber, "TAT/Slab")) {
      
      if (CCTK_IsFunctionAliased("GetLocalComponents")) {
        int const num_local_components = GetLocalComponents(cctkGH);
        if (num_local_components > 1) {
          CCTK_ERROR("TAT/Slab can only be used if there is a single local component per MPI process");
        }
      }
      
      int options;
      options = Util_TableCreateFromString ("useghosts=1");
      assert (options>=0);
      
      /* Can we use the more efficient interface? */
      if (CCTK_IsFunctionAliased ("GetRegriddingEpoch")) {
        static int old_epoch = -1;
        static struct slabsetup *restrict *restrict slab_setups = NULL;
        static int num_slab_setups = 0;
        int const epoch = GetRegriddingEpoch (cctkGH);
        if (epoch > old_epoch) {
          old_epoch = epoch;
          /* Delete old slabbing setups */
          for (int rl=0; rl<num_slab_setups; ++rl) {
            if (slab_setups[rl]) {
              Slab_MultiTransfer_Finalize (cctkGH, slab_setups[rl]);
            }
          }
          free (slab_setups);
          num_slab_setups = GetRefinementLevels (cctkGH);
          slab_setups = malloc (num_slab_setups * sizeof *slab_setups);
          assert (slab_setups);
          for (int rl=0; rl<num_slab_setups; ++rl) {
            slab_setups[rl] = NULL;
          }
        }
        /* Use existing slabbing setup */
        assert (slab_setups);
        int const reflevel = GetRefinementLevel (cctkGH);
        assert (reflevel>=0 && reflevel<num_slab_setups);
        if (!slab_setups[reflevel]) {
          slab_setups[reflevel] = 
            Slab_MultiTransfer_Init (cctkGH, group.dim, xferinfo, options);
        }
        assert (slab_setups[reflevel]);
        ierr = Slab_MultiTransfer_Apply
          (cctkGH, slab_setups[reflevel],
           nvars, vartypes, varptrs, 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, varptrs, vartypes, varptrs);
        assert (!ierr);
      }
      
      ierr = Util_TableDestroy (options);
      assert (!ierr);
      
    } else if (CCTK_EQUALS (hyperslabber, "GetHyperslab")) {
      
      CCTK_INT const direction[3*3] = {1, 0, 0,   0, 1, 0,   0, 0, 1};
      CCTK_INT const origin[3] =
        {xferinfo[0].src.off, xferinfo[1].src.off, xferinfo[2].src.off};
      CCTK_INT const extent[3] =
        {xferinfo[0].src.len, xferinfo[1].src.len, xferinfo[2].src.len};
      CCTK_INT const downsample[3] =
        {xferinfo[0].src.str, xferinfo[1].src.str, xferinfo[2].src.str};
      int mapping;
      CCTK_INT hsize[3], total_hsize;
      CCTK_POINTER hdata[nvars];
      
      assert (nvars > 0);
      mapping = Hyperslab_GlobalMappingByIndex
        (cctkGH, vis[0], 3, direction, origin, extent, downsample,
         -1, NULL, hsize);
      assert (mapping>=0);
      
      total_hsize = hsize[0] * hsize[1] * hsize[2];
      assert (total_hsize >= 0);
      for (var=0; var<nvars; ++var) {
        hdata[var] = malloc(total_hsize * CCTK_VarTypeSize(CCTK_VarTypeI(var)));
        assert (total_hsize==0 || hdata[var]);
      }
      
      icnt = Hyperslab_GetList
        (cctkGH, mapping, nvars, NULL, vis, NULL, NULL, hdata, NULL);
      assert (icnt == nvars);
      
      // copy hyperslabs into grid functions
      assert (0);
      
      for (var=0; var<nvars; ++var) {
        free (hdata[var]);
      }
      
      ierr = Hyperslab_FreeMapping (mapping);
      assert (!ierr);
      
    } else {
      CCTK_BUILTIN_UNREACHABLE();
    }
    
    if (check_boundaries) {
      /* check destination grid points for poison */
      if (cctkGH->cctk_bbox[2*dir]) {
        
        int imin[3], imax[3];
        int i, j, k;
        for (d=0; d<3; ++d) {
          imin[d] = 0;
          imax[d] = cctk_lsh[d];
        }
        imax[dir] = cctk_nghostzones[dir];
        
        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_ERROR("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:
          CCTK_BUILTIN_UNREACHABLE();
        } /* switch grouptype */
      } /* if bbox */
    } /* if check_boundaries */
    
    /* take parity into account */
    if (cctkGH->cctk_bbox[2*dir]) {
      for (var=0; var<nvars; ++var) {
        int parity;
        
        parity = paritiess[var][0] * paritiess[var][1];
        assert (abs(parity) == 1);
        if (parity == -1) {
          int imin[3], imax[3];
          int i, j, k;
          for (d=0; d<3; ++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 == 3);
          switch (group.vartype) {
          case CCTK_VARIABLE_INT: {
            CCTK_INT * 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);
                  varptr[ind] *= -1;
                }
              }
            }
            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);
                  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[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);
                  CCTK_COMPLEX * restrict const ptr = & varptr[ind];
                  * ptr = CCTK_CmplxSub (czero, * ptr);
                }
              }
            }
            break;
          }
          default:
            CCTK_BUILTIN_UNREACHABLE();
          } /* switch grouptype */
        }
      } /* for var */
    } /* if bbox */
    
    free (xferinfo);
    
  } /* if global_bbox */
  
  free (gis);
  free (varptrs);
  free (vartypes);
  free (paritiess);
  
  return 0;
}



void Rot180_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;
  
  {
#if 0
    int min_handle, max_handle;
    int d;                        /* 0..group.dim-1 */
    int ierr;
    CCTK_REAL local[6], global[6];
    min_handle = CCTK_ReductionArrayHandle ("minimum");
    if (min_handle<0) CCTK_ERROR("Could not obtain reduction handle");
    max_handle = CCTK_ReductionArrayHandle ("maximum");
    if (max_handle<0) CCTK_ERROR("Could not obtain reduction handle");
    
    for (d=0; d<6; ++d) local[d] = cctkGH->cctk_bbox[d];
    ierr = CCTK_ReduceLocArrayToArray1D
      (cctkGH, -1, max_handle, local, global, 6, CCTK_VARIABLE_REAL);
    for (d=0; d<6; ++d) global_bbox[d] = (int)global[d];
    
    for (d=0; d<3; ++d) local[d] = cctkGH->cctk_lbnd[d];
    ierr = CCTK_ReduceLocArrayToArray1D
      (cctkGH, -1, min_handle, local, global, 3, CCTK_VARIABLE_REAL);
    for (d=0; d<3; ++d) global_lbnd[d] = (int)global[d];
    
    for (d=0; d<3; ++d) local[d] = cctkGH->cctk_ubnd[d];
    ierr = CCTK_ReduceLocArrayToArray1D
      (cctkGH, -1, max_handle, local, global, 3, CCTK_VARIABLE_REAL);
    for (d=0; d<3; ++d) global_ubnd[d] = (int)global[d];
#else
    int max_handle;
    int d;                        /* 0..group.dim-1 */
    int ierr;
    CCTK_INT local[12], global[12];
    max_handle = CCTK_ReductionArrayHandle ("maximum");
    if (max_handle<0) CCTK_ERROR("Could not obtain reduction handle");
    
    for (d=0; d<6; ++d) local[  d] =  cctkGH->cctk_bbox[d];
    for (d=0; d<3; ++d) local[6+d] = -cctkGH->cctk_lbnd[d];
    for (d=0; d<3; ++d) local[9+d] =  cctkGH->cctk_ubnd[d];
    ierr = CCTK_ReduceLocArrayToArray1D
      (cctkGH, -1, max_handle, local, global, 12, CCTK_VARIABLE_INT);
    assert(!ierr);
    for (d=0; d<6; ++d) global_bbox[d] =  global[  d];
    for (d=0; d<3; ++d) global_lbnd[d] = -global[6+d];
    for (d=0; d<3; ++d) global_ubnd[d] =  global[9+d];

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



void Rot180_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 = BndRot180VI (cctkGH, nvars, indices);
  assert (!ierr);
  
  free (indices);
  free (faces);
  free (widths);
  free (tables);
}

```

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

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

# Subdirectories containing source files
SUBDIRS = 

```

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

#include "rotatingsymmetry180.h"



void Rot180_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];
  
  handle = SymmetryRegister ("rotating_symmetry_180");
  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, Rot180_SymmetryInterpolate);
  if (ierr < 0) {
    CCTK_WARN (0, "Could not register the symmetry interpolator");
  }
}

```

rotatingsymmetry180.h:
```
#ifndef ROTATINGSYMMETRY180_H
#define ROTATINGSYMMETRY180_H

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

void Rot180_RegisterSymmetry (CCTK_ARGUMENTS);

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

void Rot180_ApplyBC (CCTK_ARGUMENTS);

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

void 
RotatingSymmetry180_GetManualParities(int table, int gi, int *parities);

#endif /* ! defined ROTATINGSYMMETRY180_H */

```


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

1. Purpose

Provide a 180 degree rotational symmetry boundary condition.  This is
the kind of symmetry that exists e.g. in a binary black hole evolution
when the two black holes are identical.  The same setup also admits an
additional bitant symmetry.

Currently the symmetry is fixed to be located at the lower x boundary,
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 bitant in the x
direction.

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: #include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

#include "rotatingsymmetry180.h"



void
Rot180_CheckTensorTypes (CCTK_ARGUMENTS)
{
  int gi;
  
  /* 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 (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, "4-scalar")) {
      /* 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 */
      if (numvars != 3) {
        char * groupname = CCTK_GroupName(gi);
        assert (groupname);
        CCTK_VWarn (1, __LINE__, __FILE__, CCTK_THORNSTRING,
                    "Group \"%s\" has the tensor type alias \"u\", but does not contain 3 elements (but %d)",
                    groupname, numvars);
        free (groupname);
      }
      assert (numvars == 3);
    } else if (CCTK_EQUALS (tensortypealias, "4u")
               || CCTK_EQUALS (tensortypealias, "4d")) {
      /* 4-vector */
      assert (numvars == 4);
    } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
               || CCTK_EQUALS (tensortypealias, "dd_sym")) {
      /* symmetric tensor */
      assert (numvars == 6);
    } else if (CCTK_EQUALS (tensortypealias, "uu")
               || CCTK_EQUALS (tensortypealias, "ud")
               || CCTK_EQUALS (tensortypealias, "du")
               || CCTK_EQUALS (tensortypealias, "dd")) {
      /* tensor */
      assert (numvars == 9);
    } else if (CCTK_EQUALS (tensortypealias, "ddd_sym")) {
      /* 3rd rank tensor, symmetric in last 2 indices */
      assert (numvars == 18);
    } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
               || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
      /* symmetric 4-tensor */
      assert (numvars == 10);
    } else if (CCTK_EQUALS (tensortypealias, "weylscalars_real")) {
      /* Weyl scalars, stored as 10 real values */
      assert (numvars == 10);
    } else if (CCTK_EQUALS (tensortypealias, "ManualCartesian")) {
      /* No restriction */
    } 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
Rot180_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[3];
  CCTK_INT new_faces;
  CCTK_INT * restrict operand_indices;
  CCTK_INT * restrict operation_codes;
  CCTK_INT * restrict output_array_indices;
  int parities[3];              /* variable parities */
  int m;                        /* output array */
  int n;                        /* point */
  int d;                        /* dimension */
  int iret;                     /* interpolator return value */
  int ierr;
  
  /* Check arguments */
  assert (N_dims == 3);
  assert (N_interp_points >= 0);
  assert (interp_coords_type >= 0);
  for (d=0; d<3; ++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));
  new_faces = faces;
  new_faces &= ~ (1 << 0);
  
  /* Copy coordinates */
  for (d=0; d<3; ++d) {
    new_interp_coords[d] = malloc (N_interp_points * sizeof(CCTK_REAL));
    assert (N_interp_points == 0 || new_interp_coords[d]);
    memcpy (new_interp_coords[d], interp_coords[d],
            N_interp_points * sizeof(CCTK_REAL));
  }
  
  /* Fold coordinates */
  for (n=0; n<N_interp_points; ++n) {
    /* Is the point outside the domain? */
    if (((CCTK_REAL *)new_interp_coords[0])[n] < 0) {
      /* Rotate the point by 180 degrees */
      ((CCTK_REAL *)new_interp_coords[0])[n] *= -1;
      ((CCTK_REAL *)new_interp_coords[1])[n] *= -1;
    }
  }
  
  
  
  /* 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, output_arrays,
     new_faces);
  
  
  
  /* Free coordinates */
  for (d=0; d<3; ++d) {
    free (new_interp_coords[d]);
  }
  
  
  
  /* Find output variable indices */
  operand_indices = malloc (N_output_arrays * sizeof *operand_indices);
  assert (N_output_arrays == 0 || 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;   /* set output index to input index */
    }
  } else {
    assert (ierr == N_output_arrays);
  }
  
  operation_codes = malloc (N_output_arrays * sizeof *operation_codes);
  assert (N_output_arrays == 0 || 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);
    for (m=0; m<N_output_arrays; ++m) {
      operation_codes[m] = 0;     /* do not take derivatives */
    }
  } else {
    assert (ierr == N_output_arrays);
  }
  
  output_array_indices
    = malloc (N_output_arrays * sizeof *output_array_indices);
  assert (N_output_arrays == 0 || 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()));
  }
  
  
  
  /* Loop over all output arrays */
  for (m=0; m<N_output_arrays; ++m) {
  if (output_array_indices[m]!=-1) {
    
    
    
    /* Find variable parity */
    {
      int vi, gi;
      char tensortypealias[100];
      int firstvar, numvars;
      int table;
      int index;
      
      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);
      index = vi - firstvar;
      assert (index>=0 && index<numvars);
      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 (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);
      }
      
      if (CCTK_EQUALS (tensortypealias, "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);
        }
        parities[0] = parities[1] = parities[2] = +1;
      } else if (CCTK_EQUALS (tensortypealias, "4scalar")) {
        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);
        }
        parities[0] = parities[1] = parities[2] = +1;
      } else if (CCTK_EQUALS (tensortypealias, "u")
                 || CCTK_EQUALS (tensortypealias, "d")) {
        assert (numvars == 3);
        parities[0] = parities[1] = parities[2] = +1;
        parities[index] = -1;
      } else if (CCTK_EQUALS (tensortypealias, "4u")
                 || CCTK_EQUALS (tensortypealias, "4d")) {
        assert (numvars == 4);
        if (vi == firstvar) {
          parities[0] = parities[1] = parities[2] = +1;
        } else {
          parities[0] = parities[1] = parities[2] = +1;
          parities[index-1] = -1;
        }
      } else if (CCTK_EQUALS (tensortypealias, "uu_sym")
                 || CCTK_EQUALS (tensortypealias, "dd_sym")) {
        assert (numvars == 6);
        parities[0] = parities[1] = parities[2] = +1;
        switch (index) {
        case 0: break;
        case 1: parities[0] = parities[1] = -1; break;
        case 2: parities[0] = parities[2] = -1; break;
        case 3: break;
        case 4: parities[1] = parities[2] = -1; break;
        case 5: break;
        default: assert(0);
        }
      } else if (CCTK_EQUALS (tensortypealias, "uu")
                 || CCTK_EQUALS (tensortypealias, "ud")
                 || CCTK_EQUALS (tensortypealias, "du")
                 || CCTK_EQUALS (tensortypealias, "dd")) {
        assert (numvars == 9);
        parities[0] = parities[1] = parities[2] = +1;
        int const d1 = index % 3;
        int const d2 = index / 3;
        parities[d1] *= -1;
        parities[d2] *= -1;
      } else if (CCTK_EQUALS (tensortypealias, "ddd_sym")) {
        assert (numvars == 18);
        parities[0] = parities[1] = parities[2] = +1;
        switch (index%6) {
        case 0: break;
        case 1: parities[0] = parities[1] = -1; break;
        case 2: parities[0] = parities[2] = -1; break;
        case 3: break;
        case 4: parities[1] = parities[2] = -1; break;
        case 5: break;
        default: assert(0);
        }
        parities[index/6] *= -1;
      } else if (CCTK_EQUALS (tensortypealias, "4uu_sym")
                 || CCTK_EQUALS (tensortypealias, "4dd_sym")) {
        assert (numvars == 10);
        if (vi == firstvar) {
          parities[0] = parities[1] = parities[2] = +1;
        } else if (vi < firstvar + 4) {
          parities[0] = parities[1] = parities[2] = +1;
          parities[index-1] = -1;
        } else {
          parities[0] = parities[1] = parities[2] = +1;
          switch (index-4) {
          case 0: break;
          case 1: parities[0] = parities[1] = -1; break;
          case 2: parities[0] = parities[2] = -1; break;
          case 3: break;
          case 4: parities[1] = parities[2] = -1; break;
          case 5: break;
          default: assert(0);
          }
        }
      } else if (CCTK_EQUALS (tensortypealias, "weylscalars_real")) {
        assert (numvars == 10);
        {
          static int const weylparities[10][3] =
            {{+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}};
          for (d=0; d<3; ++d) {
            parities[d] = weylparities[index][d];
          }
        }
      } else if (CCTK_EQUALS (tensortypealias, "ManualCartesian")) {
	RotatingSymmetry180_GetManualParities(table, gi, parities);
      } 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);
      }
      
      for (d=0; d<3; ++d) {
        assert (abs(parities[d])==1);
      }
    } /* Find parity */
    
    
    
    /* Modify parity */
    {
      int code = operation_codes[m];
      while (code) {
        const int dir = code % 10;
        code /= 10;
        assert (dir>=1 && dir<=3);
        parities[dir-1] *= -1;
      }
    }
    
    
    
    /* Loop over all points and unfold */
    if (parities[0] * parities[1] == -1) {
      for (n=0; n<N_interp_points; ++n) {
        /* Is the point outside the domain? */
        if (((const CCTK_REAL *)interp_coords[0])[n] < 0) {
          /* Rotate the tensor components back by 180 degrees */
          ((CCTK_REAL *)output_arrays[m])[n] *= -1;
        }
      }
    }
    
    
    
  }
  } /* for m */
  
  
  
  /* Free output variable descriptions */
  free (operand_indices);
  free (operation_codes);
  free (output_array_indices);
  
  

  return iret;
}

void  RotatingSymmetry180_GetManualParities(int table, int gi, int *parities)
{
  char cartsyms[100];
  char *groupname = NULL;
  int i = 0;
  int ierr = -1;

  /* Get and check tensor type information */
  ierr = Util_TableGetString
    (table, sizeof cartsyms, cartsyms, "cartesianreflectionparities");
  if (ierr == UTIL_ERROR_TABLE_NO_SUCH_KEY) {
    groupname = CCTK_GroupName(gi);
    assert (groupname);
    CCTK_VWarn (1, __LINE__, __FILE__, CCTK_THORNSTRING,
		"Cartesian refection parity not declared for group \"%s\" -- aborting",
		groupname);
    assert(0);
  } else if (ierr<0) {
    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 (strlen(cartsyms) != 3)
  {
    CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
		"Invalid format for cartesianreflectionparities: must be xxx where x is + or - for group %s",
		groupname);
  }

  for (i = 0; i < 3; i++)
  {
    switch (cartsyms[i])
    {
    case '+':
      parities[i] = 1;
      break;
    case '-':
      parities[i] = -1;
      break;
    default:
      CCTK_VWarn (0, __LINE__, __FILE__, CCTK_THORNSTRING,
		  "Invalid format for cartesianreflectionparities: must be xxx where x is + or - for group %s",
		  groupname);
    }
  }
}