Task: Implement surface_integral.c for EinsteinAnalysis/ADMMass thorn

thorn_name: EinsteinAnalysis/ADMMass
src_filename: surface_integral.c
interface: # Interface definition for thorn ADMMass
# $Header$

implements: ADMMass
inherits: ADMBase ADMMacros StaticConformal SpaceMask

USES INCLUDE: SpaceMask.h

CCTK_INT FUNCTION GetDomainSpecification \
  (CCTK_INT IN size, \
   CCTK_REAL OUT ARRAY physical_min, \
   CCTK_REAL OUT ARRAY physical_max, \
   CCTK_REAL OUT ARRAY interior_min, \
   CCTK_REAL OUT ARRAY interior_max, \
   CCTK_REAL OUT ARRAY exterior_min, \
   CCTK_REAL OUT ARRAY exterior_max, \
   CCTK_REAL OUT ARRAY spacing)
REQUIRES FUNCTION GetDomainSpecification

CCTK_INT ADMMass_LoopCounterG type = SCALAR
{
  ADMMass_LoopCounter
} "ADMMass LoopCounter"

CCTK_REAL ADMMass_Masses[ADMMass_number] type = SCALAR tags='checkpoint="no"'
{
  ADMMass_SurfaceMass
  ADMMass_SurfaceMass_Lapse
  ADMMass_VolumeMass
} "ADMMass Scalars"

CCTK_REAL ADMMass_GFs_surface type = GF Timelevels = 3 tags='Prolongation="none" tensortypealias="Scalar" checkpoint="no"'
{
  ADMMass_SurfaceMass_GF
} "ADMMass gridfunctions for surface integration"

CCTK_REAL ADMMass_GFs_volume type = GF Timelevels = 3 tags='Prolongation="none" tensortypealias="Scalar" checkpoint="no"'
{
  ADMMass_VolumeMass_pot_x
  ADMMass_VolumeMass_pot_y
  ADMMass_VolumeMass_pot_z
  ADMMass_VolumeMass_GF
} "ADMMass gridfunctions for volume integration"

CCTK_REAL  ADMMass_box type = scalar tags='checkpoint="no"'
{
 ADMMass_box_x_min
 ADMMass_box_x_max
 ADMMass_box_y_min
 ADMMass_box_y_max
 ADMMass_box_z_min
 ADMMass_box_z_max
} "Physical coordinates of the surface on which the integral is computed"

real grid_spacing_product type=SCALAR tags='checkpoint="no"' "product of cctk_delta_space, to be computed in local mode and later used in global mode (carpet problems)"

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

STORAGE:ADMMass_LoopCounterG
STORAGE:ADMMass_Masses
###############################################################################
# The storage of the following grid functions at all times is necessary only
# because carpet would allocate them only for the finest level, if the STORAGE
# specification were only in the ADMMass group, as it was earlier. This will
# be changed back again, when carpet will be modified.
###############################################################################
STORAGE:ADMMass_GFs_surface[3] 
STORAGE:ADMMass_GFs_volume[3] 

schedule ADMMass_InitLoopCounter AT INITIAL
{
  LANG: C
  OPTIONS: global
} "Initialise the loop counter for ADMMass"

schedule ADMMass_SetLoopCounter AT POSTSTEP AFTER OutsideMask_UpdateMask
{
  LANG: C
  OPTIONS: global
} "Set the loop counter to the value of the parameter ADMMass:ADMMass_number"

###############################################################################
# We must schedule the local routines to compute the integrals in
# global-loop-local (as opposed to local) mode, in order to make sure that the
# scheduling condition "AFTER", which describes the ADMMass group, is respected.
# This group may depend on local routines (excision, emask) and must be run
# after all local routines. If we had no such a dependence, we could have
# scheduled the integral computations simply in local mode.
###############################################################################
schedule GROUP ADMMass AT POSTSTEP AFTER ADMMass_SetLoopCounter WHILE ADMMass::ADMMass_LoopCounter
{
  STORAGE:ADMMass_GFs_surface[3]
  STORAGE:ADMMass_GFs_volume[3]
  STORAGE:ADMMass_box
  STORAGE:grid_spacing_product
} "ADMMass loop"

schedule ADMMass_Loop IN ADMMass
{
  LANG: C
  OPTIONS: global
} "Decrement loop counter"

schedule ADMMass_Surface IN ADMMass AFTER ADMMass_Loop
{
  LANG: C
  OPTIONS: global loop-local
  SYNC: ADMMass_GFs_surface
} "Calculate the ADMmass using a surface integral: local routine"

schedule ADMMass_Surface_Global IN ADMMass AFTER ADMMass_Surface
{
  LANG: C
  OPTIONS: global
} "Calculate the ADMmass using a surface integral: global routine"

schedule ADMMass_Surface_Lapse IN ADMMass AFTER ADMMass_Surface_Global
{
  LANG: C
  OPTIONS: global loop-local
  SYNC: ADMMass_GFs_surface
} "Calculate the ADMmass*lapse using a surface integral: local routine"

schedule ADMMass_Surface_Lapse_Global IN ADMMass AFTER ADMMass_Surface_Lapse
{
  LANG: C
  OPTIONS: global
} "Calculate the ADMmass*lapse using a surface integral: global routine"

schedule ADMMass_Volume IN ADMMass AFTER ADMMass_Surface_Lapse_Global
{
  LANG: C
  OPTIONS: global loop-local
  SYNC: ADMMass_GFs_volume
} "Calculate the ADMmass using a volume integral: local routine"

schedule ADMMass_Volume_Global IN ADMMass AFTER ADMMass_Volume
{
  LANG: C
  OPTIONS: global
} "Calculate the ADMmass using a volume integral: global routine"


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

CCTK_INT ADMMass_number "number of measurements" STEERABLE=ALWAYS
{
  0: :: "0 or positive"
} 1

CCTK_REAL ADMMass_x_min[100] "x position of the leftmost yz-plane for the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} -100.0
CCTK_REAL ADMMass_x_max[100] "x position of the righttmost yz-plane for the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} 100.0

CCTK_REAL ADMMass_y_min[100] "y position of the leftmost xz-plane for the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} -100.0
CCTK_REAL ADMMass_y_max[100] "y position of the rightmost xz-plane for the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} 100.0

CCTK_REAL ADMMass_z_min[100] "z position of the leftmost xy-plane for the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} -100.0
CCTK_REAL ADMMass_z_max[100] "z position of the rightmost xy-plane for the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} 100.0

CCTK_REAL ADMMass_x_pos[100] "x position of the center of the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} 0.0
CCTK_REAL ADMMass_y_pos[100] "y position of the center of the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} 0.0
CCTK_REAL ADMMass_z_pos[100] "z position of the center of the integration box" STEERABLE=ALWAYS
{
  :	:: "anything"
} 0.0

CCTK_REAL ADMMass_distance_from_grid_boundary[100] "distance between the grid boundaries and the surface of the integration box" STEERABLE=ALWAYS
{
  :	:: "<=0 for disable, positive otherwise"
} -1.0

CCTK_REAL ADMMass_surface_distance[100] "distance between the above-defined center of the integration (cubic) box and its surface" STEERABLE=ALWAYS
{
  :	:: "<=0 for disable, positive otherwise"
} -1.0

CCTK_REAL ADMMass_volume_radius[100] "radius of the sphere inside which the volume integral is computed" STEERABLE=ALWAYS
{
  :	:: "<=0 for disable, positive otherwise"
} -1.0

BOOLEAN ADMMass_use_surface_distance_as_volume_radius "Use ADMMass_surface_distance instead of ADMMass_volume_radius" STEERABLE=ALWAYS
{
} "yes"

BOOLEAN ADMMass_use_all_volume_as_volume_radius "Use the whole grid for volume integration" STEERABLE=ALWAYS
{
} "no"

BOOLEAN ADMMass_Excise_Horizons "Should we exclude the region inside the AH to the volume integral" STEERABLE=ALWAYS
{
} "no"

BOOLEAN ADMMass_Debug "Enable some info at runtime" STEERABLE=ALWAYS
{
} "no"

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

void ADMMass_InitLoopCounter(CCTK_ARGUMENTS);

void ADMMass_SetLoopCounter(CCTK_ARGUMENTS);

void ADMMass_Loop(CCTK_ARGUMENTS);


/* Initialise the loop counter */
void ADMMass_InitLoopCounter(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

          *ADMMass_LoopCounter = 0;
}

/* Set the loop counter to the value of the parameter ADMMass:ADMMass_number */
void ADMMass_SetLoopCounter(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

      *ADMMass_LoopCounter = ADMMass_number;
}

/* Decrements the counter to loop over all radii/distances set */
void ADMMass_Loop(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

    (*ADMMass_LoopCounter)--;

}

```

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

# Source files in this directory
SRCS = surface_integral.c volume_integral.c loop.c

# Subdirectories containing source files
SUBDIRS = 

```

volume_integral.c:
```
 /*@@
   @file      volume_integral.c
   @date      Wed Oct 5 10:18:34 2005
   @author    Frank Loeffler and Luca Baiotti
   @desc 
          Computes the ADM mass as a volume integral.
   @enddesc 
 @@*/

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

#include <cctk.h>
#include <cctk_Arguments.h>
#include <cctk_Parameters.h>

#include "SpaceMask.h"

#define PI 3.1415926535897932384626433832795028841971693993751058209749445923

void ADMMass_Volume(CCTK_ARGUMENTS);

void ADMMass_Volume_Global(CCTK_ARGUMENTS);


void ADMMass_Volume(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

    CCTK_INT i,j,k, ijk, ghost, ti, tj, tk;
    CCTK_REAL u[3][3], dg[3][3][3];
    CCTK_REAL radius;

    CCTK_INT mask_descriptor = -1, state_descriptor_outside = -1;

    int avoid_origin_parameter;

#include "EinsteinBase/ADMMacros/src/macro/UPPERMET_declare.h"

    /* grid-function strides */
    const CCTK_INT di = 1;
    const CCTK_INT dj = cctk_lsh[0];
    const CCTK_INT dk = cctk_lsh[0]*cctk_lsh[1];

    /* deonminators for derivatives */
    const CCTK_REAL OneOverTwoDX = 1.0 / (2.0 * CCTK_DELTA_SPACE(0));
    const CCTK_REAL OneOverTwoDY = 1.0 / (2.0 * CCTK_DELTA_SPACE(1));
    const CCTK_REAL OneOverTwoDZ = 1.0 / (2.0 * CCTK_DELTA_SPACE(2));

    if (ADMMass_Excise_Horizons)
    {
      mask_descriptor = SpaceMask_GetTypeBits("OutsideMask");
      if (mask_descriptor < 0)
          CCTK_WARN(0, "Thorn OutsideMask not activated, but "
                       "ADMMass_Excise_Horizons requires it.");
      state_descriptor_outside = SpaceMask_GetStateBits("OutsideMask",
                                                        "outside");
      if (state_descriptor_outside < 0)
          CCTK_WARN(0, "Error in obtaining OutsideMask state descriptors");
    }

    if (ADMMass_use_surface_distance_as_volume_radius &&
        (ADMMass_volume_radius[*ADMMass_LoopCounter] < 0.0))
        radius = ADMMass_surface_distance[*ADMMass_LoopCounter];
    else
        radius = ADMMass_volume_radius[*ADMMass_LoopCounter];

    if ((radius <= 0.0)&&(!ADMMass_use_all_volume_as_volume_radius))
    {
        CCTK_WARN(2, "radius < 0 / not set, not calculating "
                     "the volume integral to get the ADM mass.");
        return;
    }

    if (CCTK_IsThornActive("PUGH"))
      {
        const void *ghost_ptr = CCTK_ParameterGet("ghost_size","PUGH",NULL);
        assert( ghost_ptr != NULL );
        ghost = *(const int *)ghost_ptr;
      }
    else /* carpet */ 
      {
        const void *ghost_ptr = CCTK_ParameterGet("ghost_size","Carpet",NULL);
        assert( ghost_ptr != NULL );
        ghost = *(const int *)ghost_ptr;
      }

    for(i=0; i<cctk_lsh[0]; i++)
     for(j=0; j<cctk_lsh[1]; j++)
      for(k=0; k<cctk_lsh[2]; k++)   
      {
          ijk = CCTK_GFINDEX3D(cctkGH, i, j, k);
          ADMMass_VolumeMass_GF[ijk] = 0.0;
          ADMMass_VolumeMass_pot_x[ijk] = 0.0;
          ADMMass_VolumeMass_pot_y[ijk] = 0.0;
          ADMMass_VolumeMass_pot_z[ijk] = 0.0;
      }

    for(i=1; i<cctk_lsh[0]-1; i++)
     for(j=1; j<cctk_lsh[1]-1; j++)
      for(k=1; k<cctk_lsh[2]-1; k++)   
      {
          ijk = CCTK_GFINDEX3D(cctkGH, i, j, k);

#include "EinsteinBase/ADMMacros/src/macro/UPPERMET_guts.h"
          u[0][0] = UPPERMET_UXX;
          u[0][1] = UPPERMET_UXY;
          u[0][2] = UPPERMET_UXZ;
          u[1][0] = UPPERMET_UXY;
          u[1][1] = UPPERMET_UYY;
          u[1][2] = UPPERMET_UYZ;
          u[2][0] = UPPERMET_UXZ;
          u[2][1] = UPPERMET_UYZ;
          u[2][2] = UPPERMET_UZZ;

          dg[0][0][0] = ( gxx[di+ijk] - gxx[-di+ijk] ) * OneOverTwoDX;
          dg[0][0][1] = ( gxx[dj+ijk] - gxx[-dj+ijk] ) * OneOverTwoDY;
          dg[0][0][2] = ( gxx[dk+ijk] - gxx[-dk+ijk] ) * OneOverTwoDZ;

          dg[0][1][0] = ( gxy[di+ijk] - gxy[-di+ijk] ) * OneOverTwoDX;
          dg[0][1][1] = ( gxy[dj+ijk] - gxy[-dj+ijk] ) * OneOverTwoDY;
          dg[0][1][2] = ( gxy[dk+ijk] - gxy[-dk+ijk] ) * OneOverTwoDZ;

          dg[0][2][0] = ( gxz[di+ijk] - gxz[-di+ijk] ) * OneOverTwoDX;
          dg[0][2][1] = ( gxz[dj+ijk] - gxz[-dj+ijk] ) * OneOverTwoDY;
          dg[0][2][2] = ( gxz[dk+ijk] - gxz[-dk+ijk] ) * OneOverTwoDZ;

          dg[1][0][0] = dg[0][1][0];
          dg[1][0][1] = dg[0][1][1];
          dg[1][0][2] = dg[0][1][2];

          dg[1][1][0] = ( gyy[di+ijk] - gyy[-di+ijk] ) * OneOverTwoDX;
          dg[1][1][1] = ( gyy[dj+ijk] - gyy[-dj+ijk] ) * OneOverTwoDY;
          dg[1][1][2] = ( gyy[dk+ijk] - gyy[-dk+ijk] ) * OneOverTwoDZ;

          dg[1][2][0] = ( gyz[di+ijk] - gyz[-di+ijk] ) * OneOverTwoDX;
          dg[1][2][1] = ( gyz[dj+ijk] - gyz[-dj+ijk] ) * OneOverTwoDY;
          dg[1][2][2] = ( gyz[dk+ijk] - gyz[-dk+ijk] ) * OneOverTwoDZ;

          dg[2][0][0] = dg[0][2][0];
          dg[2][0][1] = dg[0][2][1];
          dg[2][0][2] = dg[0][2][2];

          dg[2][1][0] = dg[1][2][0];
          dg[2][1][1] = dg[1][2][1];
          dg[2][1][2] = dg[1][2][2];
          
          dg[2][2][0] = ( gzz[di+ijk] - gzz[-di+ijk] ) * OneOverTwoDX;
          dg[2][2][1] = ( gzz[dj+ijk] - gzz[-dj+ijk] ) * OneOverTwoDY;
          dg[2][2][2] = ( gzz[dk+ijk] - gzz[-dk+ijk] ) * OneOverTwoDZ;

          for (ti = 0; ti < 3; ti++)
           for (tj = 0; tj < 3; tj++)
            for (tk = 0; tk < 3; tk++)
            {
                ADMMass_VolumeMass_pot_x[ijk] +=
                    u[ti][tj] * u[tk][0] *
                  ( dg[ti][tk][tj] - dg[ti][tj][tk] );
                ADMMass_VolumeMass_pot_y[ijk] +=
                    u[ti][tj] * u[tk][1] *
                  ( dg[ti][tk][tj] - dg[ti][tj][tk] );
                ADMMass_VolumeMass_pot_z[ijk] +=
                    u[ti][tj] * u[tk][2] *
                  ( dg[ti][tk][tj] - dg[ti][tj][tk] );
            }
          ADMMass_VolumeMass_pot_x[ijk] *= alp[ijk] * sqrt(DETG_DETG);
          ADMMass_VolumeMass_pot_y[ijk] *= alp[ijk] * sqrt(DETG_DETG);
          ADMMass_VolumeMass_pot_z[ijk] *= alp[ijk] * sqrt(DETG_DETG);
      }

    /* Do not compute in ghost zones */
    for(i=ghost; i<cctk_lsh[0]-ghost; i++)
     for(j=ghost; j<cctk_lsh[1]-ghost; j++)
      for(k=ghost; k<cctk_lsh[2]-ghost; k++)
      {
          ijk = CCTK_GFINDEX3D(cctkGH, i, j, k);

          if ((ADMMass_use_all_volume_as_volume_radius)||
              ((!ADMMass_Excise_Horizons ||
               SpaceMask_CheckStateBits(space_mask, ijk,
                                        mask_descriptor,
                                        state_descriptor_outside)) &&
              ((x[ijk]-ADMMass_x_pos[*ADMMass_LoopCounter])*
               (x[ijk]-ADMMass_x_pos[*ADMMass_LoopCounter]) +
               (y[ijk]-ADMMass_y_pos[*ADMMass_LoopCounter])*
               (y[ijk]-ADMMass_y_pos[*ADMMass_LoopCounter]) +
               (z[ijk]-ADMMass_z_pos[*ADMMass_LoopCounter])*
               (z[ijk]-ADMMass_z_pos[*ADMMass_LoopCounter]) <=
               radius * radius)))
          {
              ADMMass_VolumeMass_GF[ijk] =
               ((ADMMass_VolumeMass_pot_x[CCTK_GFINDEX3D(cctkGH,i+1,j,k)]-
                 ADMMass_VolumeMass_pot_x[CCTK_GFINDEX3D(cctkGH,i-1,j,k)])*
                OneOverTwoDX+
                (ADMMass_VolumeMass_pot_y[CCTK_GFINDEX3D(cctkGH,i,j+1,k)]-
                 ADMMass_VolumeMass_pot_y[CCTK_GFINDEX3D(cctkGH,i,j-1,k)])*
                OneOverTwoDY+
                (ADMMass_VolumeMass_pot_z[CCTK_GFINDEX3D(cctkGH,i,j,k+1)]-
                 ADMMass_VolumeMass_pot_z[CCTK_GFINDEX3D(cctkGH,i,j,k-1)])*
                OneOverTwoDZ);
          }
      }


    /* Carpet does the following itself, but pugh does not. */

    if (CCTK_IsThornActive("PUGH"))     
    {
      /* for short hand, right and left modified stencil values */ 
        const int lst  = ghost;
        const int rst0 = cctk_lsh[0] - ghost;
        const int rst1 = cctk_lsh[1] - ghost;
        const int rst2 = cctk_lsh[2] - ghost;
                
        /*find the value of the "avoid_origin"  parameter */
        const void *avoid_origin_parameter_ptr =
            CCTK_ParameterGet("avoid_origin","CartGrid3D",NULL);
        assert( avoid_origin_parameter_ptr != NULL );
        avoid_origin_parameter = *(const CCTK_INT *)avoid_origin_parameter_ptr;
    
        /* if  avoid_origin = yes (default), then do not devide by two. 
           This gives the correct result for the trapezoidal integration rule
           on the symmetry boundaries, while (even if it is wrong not to devide
           by two the points on the faces that are 
           physical outer boundaries) it should not spoil the result. */
        
        if (! avoid_origin_parameter)
        {        
	  if (cctk_lbnd[2] == 0)
	    for(j = lst; j <= rst1; j++)
	      for(i = lst; i <= rst0; i++)
		{
		  ijk = CCTK_GFINDEX3D(cctkGH, i, j, lst);
		  ADMMass_VolumeMass_GF[ijk] *= 0.5;
		}
	  /* Recompute the integrand on the symmetry and physical boundaries:
	     devide by 2 on the boundary faces cctk_lbnd values start from zero,
	     so must add one to get the total number of points.
	     If cctk_ubnd[2]=n, it means that it is the (n+1)-st point
	     (C notation) */
	  
	  if (cctk_ubnd[2]+1 == cctk_gsh[2])
	    for(j = lst; j <= rst1; j++)
	      for(i = lst; i <= rst0; i++)
		{
		  ijk = CCTK_GFINDEX3D(cctkGH, i, j, rst2);
		  ADMMass_VolumeMass_GF[ijk] *= 0.5;
		}       
	  if (cctk_lbnd[1] == 0)
	    for(k = lst; k<= rst2; k++)
	      for(i = lst; i<= rst0; i++)
		{
		  ijk = CCTK_GFINDEX3D(cctkGH, i, lst, k);
		  ADMMass_VolumeMass_GF[ijk] *= 0.5;
		}
	  if (cctk_ubnd[1]+1 == cctk_gsh[1])
	    for(k = lst; k<= rst2; k++)
	      for(i = lst; i<= rst0; i++)
		{
		  ijk = CCTK_GFINDEX3D(cctkGH, i, rst1, k);
		  ADMMass_VolumeMass_GF[ijk] *= 0.5;
		}
	  if (cctk_lbnd[0] == 0)
	    for(k = lst; k <= rst2; k++)
	      for(j = lst; j <= rst1; j++)
		{
		  ijk = CCTK_GFINDEX3D(cctkGH, lst, j, k);
		  ADMMass_VolumeMass_GF[ijk] *= 0.5;
		}
	  if (cctk_ubnd[0]+1 == cctk_gsh[0])
	    for(k = lst; k <= rst2; k++)
	      for(j = lst; j <= rst1; j++)
		{
		  ijk = CCTK_GFINDEX3D(cctkGH, rst0, j, k);
		  ADMMass_VolumeMass_GF[ijk] *= 0.5;
		}
	} /* end if avoid_origin */
    } /* end if PUGH */
    
    *grid_spacing_product = cctk_delta_space[0]*
                            cctk_delta_space[1]*
                            cctk_delta_space[2];
     
#include "EinsteinBase/ADMMacros/src/macro/UPPERMET_undefine.h"
}      

void ADMMass_Volume_Global(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

    CCTK_INT reduction_handle;

    reduction_handle = CCTK_ReductionHandle("sum");
    if (reduction_handle < 0)
        CCTK_WARN(0, "Unable to get reduction handle.");

    if (CCTK_Reduce(cctkGH, -1, reduction_handle, 1,
                    CCTK_VARIABLE_REAL,
                    &ADMMass_VolumeMass[*ADMMass_LoopCounter], 1,
                    CCTK_VarIndex("ADMMass::ADMMass_VolumeMass_GF")))
        CCTK_WARN(0, "Error while reducing ADMMass_VolumeMass_GF");

    ADMMass_VolumeMass[*ADMMass_LoopCounter] *=
      *grid_spacing_product / (16.0*PI);

    if (ADMMass_use_surface_distance_as_volume_radius)
    {
        if (ADMMass_Debug)
        CCTK_VInfo(CCTK_THORNSTRING,
                   "detector %d: ADM mass  %g, volume (sphere of radius) %g",
                   (int)*ADMMass_LoopCounter,
                   ADMMass_VolumeMass[*ADMMass_LoopCounter],
                   ADMMass_surface_distance[*ADMMass_LoopCounter]);
    }
    else if (ADMMass_use_all_volume_as_volume_radius)
    {
        if (ADMMass_Debug)
        CCTK_VInfo(CCTK_THORNSTRING," detector %d: ADM mass %g, volume: the whole grid",
               (int)*ADMMass_LoopCounter, ADMMass_VolumeMass[*ADMMass_LoopCounter]);
    }
    else
    {
        if (ADMMass_Debug)
        CCTK_VInfo(CCTK_THORNSTRING," detector %d: ADM mass %g, volume (sphere of radius) %g",
               (int)*ADMMass_LoopCounter, ADMMass_VolumeMass[*ADMMass_LoopCounter],
               ADMMass_volume_radius[*ADMMass_LoopCounter]);
    }
}


```


combined_doc_context: ## README (README):
```
Cactus Code Thorn ADMMass
Author(s)    : Frank Löffler
Maintainer(s): Frank Löffler 
Licence      : GPLv2+
--------------------------------------------------------------------------

1. Purpose

This thorn can be used to calculate different approximations of the ADM mass
on a finite grid.

It requires C99 to compile. For gcc or icc, add -std=c99 to the compiler
options.

Copyright
---------

This thorn is copyright (C) 2005
by Frank Loeffler <frank.loeffler@aei.mpg.de>

This thorn is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This thorn is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this thorn (see the file COPYING in this directory);
if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA  02111-1307  USA


```

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

```tex
=== documentation.tex ===
% *======================================================================*
%  Cactus Thorn template for ThornGuide documentation
%  Author: Ian Kelley
%  Date: Sun Jun 02, 2002
%  $Header$
%
%  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
% $Header$

\documentclass{article}

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

\begin{document}

% The author of the documentation
\author{Frank Löffler}

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

% the date your document was last changed, if your document is in CVS,
% please use:
%    \date{$ $Date: 2004-01-07 14:12:39 -0600 (Wed, 07 Jan 2004) $ $}
\date{2012-05-07}

\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}
Thorn ADMMass can compute the ADM mass from quantities in ADMBase.
\end{abstract}

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

\section{Introduction}
The ADM mass evaluated as either surface integrals at infinity or volume
integrals over entire hypersurfaces give a measure of the total energy in the
spacetime.

 The ADM mass can be defined~\cite{Omurchadha74} as a surface integral over a
 sphere with infinite radius:
 \begin{equation}
 \label{eq:ADM_mass}
  M_{\mbox{\tiny ADM}}=\frac{1}{16\pi}\oint_\infty
  \sqrt{\gamma}\gamma^{ij}\gamma^{kl}
  (\gamma_{ik,j}-\gamma_{ij,k})\textrm{d}\! S_l
 \end{equation}.
 This is, assuming $\alpha=1$ at infinity, equivalent to
 \begin{equation}
 \label{eq:ADM_mass_volume}
  M_{\mbox{\tiny ADM}}=\frac{1}{16\pi}\int
  \left(\alpha\sqrt{\gamma}\gamma^{ij}\gamma^{kl}
  (\gamma_{ik,j}-\gamma_{ij,k})\right)_{,l}\textrm{d}\!^{\,3}\!\!x
 \end{equation}.
In practice, the following equation can also be used within the thorn:
  \begin{equation}
   \label{eq:ADM_mass_lapse}
   M_{\mbox{\tiny ADM}}=\frac{1}{16\pi}\oint_\infty
   \alpha\sqrt{\gamma}\gamma^{ij}\gamma^{kl}
   (\gamma_{ik,j}-\gamma_{ij,k})\textrm{d}\! S_l.
  \end{equation}
  This differs from equation~(\ref{eq:ADM_mass}) by the factor $\alpha$ inside
  the integral. For evaluations of those equations at infinity, $\alpha=1$ is
  assumed, and they are equal. For evaluations at a finite distance, however,
  this is usually not the case and the approximation of the ADM mass is
  gauge-dependent~\cite{Omurchadha74}. Depending on circumstances,
  either~(\ref{eq:ADM_mass}),~(\ref{eq:ADM_mass_lapse} or~~(\ref{eq:ADM_mass_volume})
  might give better results.

\section{Using This Thorn}

Multiple measurements can be done for both volume and surface integral, but the
limit for both is 100 (change param.ccl if you need more). You need to
specify the number of integrations with \texttt{ADMMass\_number} (and ADMMass
will perform both integrations that many times).

Also note that this thorn uses the ADMMacros for derivatives. Thus, converegence
of results is limited to the order of these derivatives (ADMMacros::spatial\_order).

ADMMass provides several possibilities to specify the (finite) integration domain,
both for surface and volume integral, which we list in the following:

\begin{itemize}
 \item Surface Integral (over rectangular domain)
  \begin{itemize}
   \item \texttt{ADMMass\_distance\_from\_grid\_boundary}: specifies the distance
         between the physical domain boundary and the integration domain. If
         this is set, this fully specifies the domain boundary.
   \item \texttt{ADMMass\_surface\_distance}: specifies a distance of the integration
         boundary from a given point, specified using \texttt{ADMMass\_x\_pos[3]}.
   \item Otherwise, \texttt{ADMMass\_[xyz]\_[min|max]} specify a rectangular
         integration domain.
  \end{itemize}
 \item Volume Integral (over sphere)
  \begin{itemize}
   \item If \texttt{ADMMass\_use\_all\_volume\_as\_volume\_radius} is set,
         the whole volume is used for integration.
   \item If \texttt{ADMMass\_use\_surface\_distance\_as\_volume\_radius} is set
         and \texttt{ADMMass\_volume\_radius} is not (negative),
         \texttt{ADMMass\_surface\_distance} is used to specify the integration
         radius.
   \item Otherwise, \texttt{ADMMass\_volume\_radius} specifies this radius.
   \item \texttt{ADMMass\_[xyz]\_pos} specify the position of the integration
         sphere.
   \item Use \texttt{ADMMass\_Excise\_Horizons} to exclude domains where
         thorn \texttt{OutsideMask} didn't specify domain as \texttt{outside}.
         This can be used to, e.g. excise black hole apparent horizons.
  \end{itemize}
\end{itemize}

You should output \texttt{ADMMass::ADMMass\_Masses} for the result of the
integrations, which will include the results for the volume integral, the
usual surface integral and the sorface integral including the lapse.

% Do not delete next line
% END CACTUS THORNGUIDE

\end{document}


```



Reference solution:
src_code:  /*@@
   @file      surface_integral.c
   @date      Wed Oct 5 10:02:12 2005
   @author    Frank Loeffler and Luca Baiotti
   @desc 
          Computes the ADM mass as a surface integral.
   @enddesc 
 @@*/

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

#include <cctk.h>
#include <cctk_Arguments.h>
#include <cctk_Parameters.h>

#define PI 3.1415926535897932384626433832795028841971693993751058209749445923

int find_closest(const cGH *cctkGH, const int *cctk_lsh,
                 const CCTK_REAL *cctk_delta_space, int ghost,
                 CCTK_REAL *coord, CCTK_REAL coord_min, int dir);

void ADMMass_Surface(CCTK_ARGUMENTS);

void ADMMass_Surface_Global(CCTK_ARGUMENTS);

void ADMMass_Surface_Lapse(CCTK_ARGUMENTS);

void ADMMass_Surface_Lapse_Global(CCTK_ARGUMENTS);




int find_closest(const cGH *cctkGH, const int *cctk_lsh,
                 const CCTK_REAL *cctk_delta_space, int ghost,
                 CCTK_REAL *coord, CCTK_REAL coord_min, int dir)
{
    int i, ijk, min_i = -1;
    CCTK_REAL min = 1.e100;
    
    for(i=ghost; i<cctk_lsh[dir]-ghost; i++)
    {
        ijk = CCTK_GFINDEX3D(cctkGH, (dir==0)?i:0, (dir==1)?i:0, (dir==2)?i:0);

        if (fabs(coord[ijk] - coord_min) < min)
        {
            min = fabs(coord[ijk] - coord_min);
            min_i = i;
        }
    }
    return min_i;
}

void ADMMass_Surface(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

    CCTK_INT i,j,k, ijk, ierr, ghost, ti, tj, tk, tl;
    CCTK_INT  x_min_i, x_max_i, y_min_j, y_max_j, z_min_k, z_max_k;
    CCTK_REAL ds[3];
    CCTK_REAL u[3][3], dg[3][3][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[3];

#include "EinsteinBase/ADMMacros/src/macro/UPPERMET_declare.h"

    /* grid-function strides for ADMMacros */
    const CCTK_INT di = 1;
    const CCTK_INT dj = cctk_lsh[0];
    const CCTK_INT dk = cctk_lsh[0]*cctk_lsh[1];

    /* deonminators for derivatives */
    const CCTK_REAL OneOverTwoDX = 1.0 / (2.0 * CCTK_DELTA_SPACE(0));
    const CCTK_REAL OneOverTwoDY = 1.0 / (2.0 * CCTK_DELTA_SPACE(1));
    const CCTK_REAL OneOverTwoDZ = 1.0 / (2.0 * CCTK_DELTA_SPACE(2));

    const CCTK_REAL oneDX =  CCTK_DELTA_SPACE(0);
    const CCTK_REAL oneDY =  CCTK_DELTA_SPACE(1);
    const CCTK_REAL oneDZ =  CCTK_DELTA_SPACE(2);


    x_min_i = -INT_MAX;
    x_max_i =  INT_MAX; 
    y_min_j = -INT_MAX; 
    y_max_j =  INT_MAX;
    z_min_k = -INT_MAX;
    z_max_k =  INT_MAX;

    if (ADMMass_distance_from_grid_boundary[*ADMMass_LoopCounter] > 0.0)
    { 
        if (!CCTK_EQUALS(CCTK_ParameterValString("type", "cartgrid3d"),
                         "coordbase"))
            CCTK_WARN(0,"This thorn used with the ADMMass_distance_from_grid_boundary parameter requires to set coordinates through coordbase.");

        /* Find the physical coordinates of the boundaries */
        ierr = GetDomainSpecification( 3, physical_min, physical_max,
                                          interior_min, interior_max,
                                          exterior_min, exterior_max, spacing);
        if (ierr)
          CCTK_VWarn ( 0, __LINE__, __FILE__, "CartGrid3D", "error returned from function GetDomainSpecification");
        *ADMMass_box_x_min =
            physical_min[0] +
            ADMMass_distance_from_grid_boundary[*ADMMass_LoopCounter];
        *ADMMass_box_x_max =
            physical_max[0] -
            ADMMass_distance_from_grid_boundary[*ADMMass_LoopCounter];
        *ADMMass_box_y_min =
            physical_min[1] +
            ADMMass_distance_from_grid_boundary[*ADMMass_LoopCounter]; 
        *ADMMass_box_y_max =
            physical_max[1] -
            ADMMass_distance_from_grid_boundary[*ADMMass_LoopCounter];
        *ADMMass_box_z_min =
            physical_min[2] +
            ADMMass_distance_from_grid_boundary[*ADMMass_LoopCounter];
        *ADMMass_box_z_max =
            physical_max[2] -
            ADMMass_distance_from_grid_boundary[*ADMMass_LoopCounter];
    }
    else if (ADMMass_surface_distance[*ADMMass_LoopCounter] > 0.0)
    {
        *ADMMass_box_x_min = ADMMass_x_pos[*ADMMass_LoopCounter] -
                             ADMMass_surface_distance[*ADMMass_LoopCounter];
        *ADMMass_box_x_max = ADMMass_x_pos[*ADMMass_LoopCounter] +
                             ADMMass_surface_distance[*ADMMass_LoopCounter];
        *ADMMass_box_y_min = ADMMass_y_pos[*ADMMass_LoopCounter] -
                             ADMMass_surface_distance[*ADMMass_LoopCounter];
        *ADMMass_box_y_max = ADMMass_y_pos[*ADMMass_LoopCounter] +
                             ADMMass_surface_distance[*ADMMass_LoopCounter];
        *ADMMass_box_z_min = ADMMass_z_pos[*ADMMass_LoopCounter] -
                             ADMMass_surface_distance[*ADMMass_LoopCounter];
        *ADMMass_box_z_max = ADMMass_z_pos[*ADMMass_LoopCounter] +
                             ADMMass_surface_distance[*ADMMass_LoopCounter];
    }
    else
    {
        *ADMMass_box_x_min = ADMMass_x_min[*ADMMass_LoopCounter];
        *ADMMass_box_x_max = ADMMass_x_max[*ADMMass_LoopCounter];
        *ADMMass_box_y_min = ADMMass_y_min[*ADMMass_LoopCounter];
        *ADMMass_box_y_max = ADMMass_y_max[*ADMMass_LoopCounter];
        *ADMMass_box_z_min = ADMMass_z_min[*ADMMass_LoopCounter];
        *ADMMass_box_z_max = ADMMass_z_max[*ADMMass_LoopCounter];
    }

    if (CCTK_IsThornActive("PUGH"))
    {
        const void *ghost_ptr = CCTK_ParameterGet("ghost_size","PUGH",NULL);
        assert( ghost_ptr != NULL );
        ghost = *(const int *)ghost_ptr;
    }
    else /* carpet */ 
    {
        const void *ghost_ptr = CCTK_ParameterGet("ghost_size","Carpet",NULL);
        assert( ghost_ptr != NULL );
        ghost = *(const int *)ghost_ptr;
    }

    ijk = CCTK_GFINDEX3D(cctkGH, cctk_ubnd[0]-cctk_lbnd[0],
                                 cctk_ubnd[1]-cctk_lbnd[1],
                                 cctk_ubnd[2]-cctk_lbnd[2] );

    if ( (x[0] < *ADMMass_box_x_min) && (*ADMMass_box_x_min < x[ijk]) )
        x_min_i = find_closest(cctkGH, cctk_lsh, cctk_delta_space, ghost,
                               x, *ADMMass_box_x_min, 0);
    if ( (x[0] < *ADMMass_box_x_max) && (*ADMMass_box_x_max < x[ijk]) )
        x_max_i = find_closest(cctkGH, cctk_lsh, cctk_delta_space, ghost,
                               x, *ADMMass_box_x_max, 0);
    if ( (y[0] < *ADMMass_box_y_min) && (*ADMMass_box_y_min < y[ijk]) )
        y_min_j = find_closest(cctkGH, cctk_lsh, cctk_delta_space, ghost,
                               y, *ADMMass_box_y_min, 1);
    if ( (y[0] < *ADMMass_box_y_max) && (*ADMMass_box_y_max < y[ijk]) )
        y_max_j = find_closest(cctkGH, cctk_lsh, cctk_delta_space, ghost,
                               y, *ADMMass_box_y_max, 1);
    if ( (z[0] < *ADMMass_box_z_min) && (*ADMMass_box_z_min < z[ijk]) )
        z_min_k = find_closest(cctkGH, cctk_lsh, cctk_delta_space, ghost,
                               z, *ADMMass_box_z_min, 2);
    if ( (z[0] < *ADMMass_box_z_max) && (*ADMMass_box_z_max < z[ijk]) )
        z_max_k = find_closest(cctkGH, cctk_lsh, cctk_delta_space, ghost,
                               z, *ADMMass_box_z_max, 2);

    for(i=0; i<cctk_lsh[0]; i++)
     for(j=0; j<cctk_lsh[1]; j++)
      for(k=0; k<cctk_lsh[2]; k++)
      {
          ijk = CCTK_GFINDEX3D(cctkGH, i, j, k);
          ADMMass_SurfaceMass_GF[ijk] = 0.0;
      }
    for(i=ghost; i<cctk_lsh[0]-ghost; i++)
     for(j=ghost; j<cctk_lsh[1]-ghost; j++)
      for(k=ghost; k<cctk_lsh[2]-ghost; k++)
      {
          ijk = CCTK_GFINDEX3D(cctkGH, i, j, k);
          
          /* delimit the cube on whose surface we want to integrate */
          if ((i >= x_min_i) &&
              (i <= x_max_i) &&
              (j >= y_min_j) &&
              (j <= y_max_j) &&
              (k >= z_min_k) &&
              (k <= z_max_k))
          {
              ds[0] = 0.0;
              ds[1] = 0.0;
              ds[2] = 0.0;

              /* Select the points on the surfaces of the requested cube */
              CCTK_INT n_bound = 0;
              if (i == x_min_i)
              {
                  n_bound++;
                  ds[0] = -oneDY*oneDZ;
              }
              if (i == x_max_i)
              {
                  n_bound++;
                  ds[0] = oneDY*oneDZ;
              }

              if (j == y_min_j)
              {
                  n_bound++;
                  ds[1] = -oneDX*oneDZ;
              }
              if (j == y_max_j)
              {
                  n_bound++;
                  ds[1] = oneDX*oneDZ;
              }

              if (k == z_min_k)
              {
                  n_bound++;
                  ds[2] = -oneDX*oneDY;
              }
              if (k == z_max_k)
              {
                  n_bound++;
                  ds[2] = oneDX*oneDY;
              }

              /* Take care of corners and edges */
              if (n_bound == 2)
                  for (ti=0; ti<3; ti++)
                      ds[ti] /= 2;
              if (n_bound == 3)
                  for (ti=0; ti<3; ti++)
                      ds[ti] /= 4;

              if ((ds[0] != 0.0) || (ds[1] != 0.0) || (ds[2] != 0.0))
              {
#include "EinsteinBase/ADMMacros/src/macro/UPPERMET_guts.h"
                  u[0][0] = UPPERMET_UXX;
                  u[0][1] = UPPERMET_UXY;
                  u[0][2] = UPPERMET_UXZ;
                  u[1][0] = UPPERMET_UXY;
                  u[1][1] = UPPERMET_UYY;
                  u[1][2] = UPPERMET_UYZ;
                  u[2][0] = UPPERMET_UXZ;
                  u[2][1] = UPPERMET_UYZ;
                  u[2][2] = UPPERMET_UZZ;

                  dg[0][0][0] = ( gxx[di+ijk] - gxx[-di+ijk] ) * OneOverTwoDX;
                  dg[0][0][1] = ( gxx[dj+ijk] - gxx[-dj+ijk] ) * OneOverTwoDY;
                  dg[0][0][2] = ( gxx[dk+ijk] - gxx[-dk+ijk] ) * OneOverTwoDZ;
                  
                  dg[0][1][0] = ( gxy[di+ijk] - gxy[-di+ijk] ) * OneOverTwoDX;
                  dg[0][1][1] = ( gxy[dj+ijk] - gxy[-dj+ijk] ) * OneOverTwoDY;
                  dg[0][1][2] = ( gxy[dk+ijk] - gxy[-dk+ijk] ) * OneOverTwoDZ;
                  
                  dg[0][2][0] = ( gxz[di+ijk] - gxz[-di+ijk] ) * OneOverTwoDX;
                  dg[0][2][1] = ( gxz[dj+ijk] - gxz[-dj+ijk] ) * OneOverTwoDY;
                  dg[0][2][2] = ( gxz[dk+ijk] - gxz[-dk+ijk] ) * OneOverTwoDZ;
                  
                  dg[1][0][0] = dg[0][1][0];
                  dg[1][0][1] = dg[0][1][1];
                  dg[1][0][2] = dg[0][1][2];
                  
                  dg[1][1][0] = ( gyy[di+ijk] - gyy[-di+ijk] ) * OneOverTwoDX;
                  dg[1][1][1] = ( gyy[dj+ijk] - gyy[-dj+ijk] ) * OneOverTwoDY;
                  dg[1][1][2] = ( gyy[dk+ijk] - gyy[-dk+ijk] ) * OneOverTwoDZ;
                  
                  dg[1][2][0] = ( gyz[di+ijk] - gyz[-di+ijk] ) * OneOverTwoDX;
                  dg[1][2][1] = ( gyz[dj+ijk] - gyz[-dj+ijk] ) * OneOverTwoDY;
                  dg[1][2][2] = ( gyz[dk+ijk] - gyz[-dk+ijk] ) * OneOverTwoDZ;
                  
                  dg[2][0][0] = dg[0][2][0];
                  dg[2][0][1] = dg[0][2][1];
                  dg[2][0][2] = dg[0][2][2];
                  
                  dg[2][1][0] = dg[1][2][0];
                  dg[2][1][1] = dg[1][2][1];
                  dg[2][1][2] = dg[1][2][2];
                  
                  dg[2][2][0] = ( gzz[di+ijk] - gzz[-di+ijk] ) * OneOverTwoDX;
                  dg[2][2][1] = ( gzz[dj+ijk] - gzz[-dj+ijk] ) * OneOverTwoDY;
                  dg[2][2][2] = ( gzz[dk+ijk] - gzz[-dk+ijk] ) * OneOverTwoDZ;

                  for (ti = 0; ti < 3; ti++)
                   for (tj = 0; tj < 3; tj++)
                    for (tk = 0; tk < 3; tk++)
                     for (tl = 0; tl < 3; tl++)
                         ADMMass_SurfaceMass_GF[ijk] +=
                           u[ti][tj] * u[tk][tl] *
                             ( dg[ti][tk][tj] - dg[ti][tj][tk] ) * ds[tk];
                  ADMMass_SurfaceMass_GF[ijk] *= sqrt(DETG_DETG);
              }
          }
      }

#include "EinsteinBase/ADMMacros/src/macro/UPPERMET_undefine.h"
}

void ADMMass_Surface_Global(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

    CCTK_INT reduction_handle;

    reduction_handle = CCTK_ReductionHandle("sum");
    if (reduction_handle < 0)
        CCTK_WARN(0, "Unable to get reduction handle.");

    if (CCTK_Reduce(cctkGH, -1, reduction_handle, 1,
                    CCTK_VARIABLE_REAL,
                    &ADMMass_SurfaceMass[*ADMMass_LoopCounter], 1,
                    CCTK_VarIndex("ADMMass::ADMMass_SurfaceMass_GF")))
        CCTK_WARN(0, "Error while reducing ADMMass_SurfaceMass_GF");

    ADMMass_SurfaceMass[*ADMMass_LoopCounter] /=  16.0*PI;

    if (ADMMass_Debug)
    CCTK_VInfo(CCTK_THORNSTRING,
               "detector %d: ADM mass  %g, surface (cube): xmin %g, xmax %g, ymin %g, ymax %g, zmin %g, zmax %g",
               (int)*ADMMass_LoopCounter,
               ADMMass_SurfaceMass[*ADMMass_LoopCounter],
               *ADMMass_box_x_min,*ADMMass_box_x_max,
               *ADMMass_box_y_min,*ADMMass_box_y_max,
               *ADMMass_box_z_min,*ADMMass_box_z_max);
}

void ADMMass_Surface_Lapse(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

    CCTK_INT i,j,k, ijk, ghost;

    if (CCTK_IsThornActive("PUGH"))
      {
        const void *ghost_ptr = CCTK_ParameterGet("ghost_size","PUGH",NULL);
        assert( ghost_ptr != NULL );
        ghost = *(const int *)ghost_ptr;
      }
    else /* carpet */ 
      {
        const void *ghost_ptr = CCTK_ParameterGet("ghost_size","Carpet",NULL);
        assert( ghost_ptr != NULL );
        ghost = *(const int *)ghost_ptr;
      }

    for(i=ghost; i<cctk_lsh[0]-ghost; i++)
     for(j=ghost; j<cctk_lsh[1]-ghost; j++)
      for(k=ghost; k<cctk_lsh[2]-ghost; k++)
      {
          ijk = CCTK_GFINDEX3D(cctkGH, i, j, k);
          ADMMass_SurfaceMass_GF[ijk] *= alp[ijk];
      }
}

void ADMMass_Surface_Lapse_Global(CCTK_ARGUMENTS)
{
    DECLARE_CCTK_ARGUMENTS
    DECLARE_CCTK_PARAMETERS

    CCTK_INT reduction_handle;

    reduction_handle = CCTK_ReductionHandle("sum");
    if (reduction_handle < 0)
        CCTK_WARN(0, "Unable to get reduction handle.");

    if (CCTK_Reduce(cctkGH, -1, reduction_handle, 1,
                    CCTK_VARIABLE_REAL,
                    &ADMMass_SurfaceMass_Lapse[*ADMMass_LoopCounter], 1,
                    CCTK_VarIndex("ADMMass::ADMMass_SurfaceMass_GF")))
        CCTK_WARN(0, "Error while reducing ADMMass_SurfaceMass_GF");

    ADMMass_SurfaceMass_Lapse[*ADMMass_LoopCounter] /=  16.0*PI;    

    if (ADMMass_Debug)
    CCTK_VInfo(CCTK_THORNSTRING,
               "detector %d: ADM mass with lapse  %g, surface (cube): xmin %g, xmax %g, ymin %g, ymax %g, zmin %g, zmax %g",
               (int)*ADMMass_LoopCounter,
               ADMMass_SurfaceMass_Lapse[*ADMMass_LoopCounter],
               *ADMMass_box_x_min,*ADMMass_box_x_max,
               *ADMMass_box_y_min,*ADMMass_box_y_max,
               *ADMMass_box_z_min,*ADMMass_box_z_max);
}